aboutsummaryrefslogtreecommitdiffstats
path: root/_spacemacs.d/local
diff options
context:
space:
mode:
Diffstat (limited to '_spacemacs.d/local')
-rw-r--r--_spacemacs.d/local/mu4e/mu4e-actions.el312
-rw-r--r--_spacemacs.d/local/mu4e/mu4e-compose.el778
-rw-r--r--_spacemacs.d/local/mu4e/mu4e-context.el157
-rw-r--r--_spacemacs.d/local/mu4e/mu4e-contrib.el164
-rw-r--r--_spacemacs.d/local/mu4e/mu4e-draft.el474
-rw-r--r--_spacemacs.d/local/mu4e/mu4e-headers.el1694
-rw-r--r--_spacemacs.d/local/mu4e/mu4e-lists.el93
-rw-r--r--_spacemacs.d/local/mu4e/mu4e-main.el225
-rw-r--r--_spacemacs.d/local/mu4e/mu4e-mark.el466
-rw-r--r--_spacemacs.d/local/mu4e/mu4e-message.el284
-rw-r--r--_spacemacs.d/local/mu4e/mu4e-meta.el11
-rw-r--r--_spacemacs.d/local/mu4e/mu4e-proc.el524
-rw-r--r--_spacemacs.d/local/mu4e/mu4e-speedbar.el124
-rw-r--r--_spacemacs.d/local/mu4e/mu4e-utils.el1238
-rw-r--r--_spacemacs.d/local/mu4e/mu4e-vars.el870
-rw-r--r--_spacemacs.d/local/mu4e/mu4e-view.el1541
-rw-r--r--_spacemacs.d/local/mu4e/mu4e.el93
-rw-r--r--_spacemacs.d/local/mu4e/org-mu4e.el323
-rw-r--r--_spacemacs.d/local/mu4e/org-old-mu4e.el289
19 files changed, 9660 insertions, 0 deletions
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 <djcb@djcbsoftware.nl>
+;; Maintainer: Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
+
+;; 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 <http://www.gnu.org/licenses/>.
+
+;;; 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 "<head><meta charset=\"UTF-8\"></head>\n")
+ (insert (or html (concat "<pre>" txt "</pre>")))
+ (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 <djcb@djcbsoftware.nl>
+;; Maintainer: Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
+
+;; 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 <http://www.gnu.org/licenses/>.
+
+;;; 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 ( .... <original message> ))
+
+
+;; 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: <full-path-to-message-in-target-folder>
+
+;; 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 <email>' 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 <filename> :mime-type <mime-type> :disposition <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 <djcb@djcbsoftware.nl>
+;; Maintainer: Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
+
+;; 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 <http://www.gnu.org/licenses/>.
+
+;;; 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 <http://www.gnu.org/licenses/>.
+
+;;; 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 <djcb@djcbsoftware.nl>
+;; Maintainer: Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
+
+;; 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 <http://www.gnu.org/licenses/>.
+
+;;; 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
+ <time>-<random>.<hostname>:2,
+You can append flags."
+ (let* ((sysname (if (fboundp 'system-name)
+ (system-name)
+ (with-no-warnings system-name)))
+ (hostname (downcase
+ (save-match-data
+ (substring sysname
+ (string-match "^[^.]+" sysname)
+ (match-end 0))))))
+ (format "%s.%04x%04x%04x%04x.%s:2,%s"
+ (format-time-string "%s" (current-time))
+ (random 65535) (random 65535) (random 65535) (random 65535)
+ hostname (or flagstr ""))))
+
+(defun mu4e~draft-common-construct ()
+ "Construct the common headers for each message."
+ (concat
+ (mu4e~draft-header "User-agent" mu4e-user-agent-string)
+ (when mu4e-compose-auto-include-date
+ (mu4e~draft-header "Date" (message-make-date)))))
+
+(defconst mu4e~draft-reply-prefix "Re: "
+ "String to prefix replies with.")
+
+(defun mu4e~draft-reply-construct (origmsg)
+ "Create a draft message as a reply to original message
+ORIGMSG. Replying-to-self is a special; in that case, the To and Cc
+fields will be the same as in the original."
+ (let* ((reply-to-self (mu4e-message-contact-field-matches-me origmsg :from))
+ (recipnum
+ (+ (length (mu4e~draft-create-to-lst origmsg))
+ (length (mu4e~draft-create-cc-lst origmsg t))))
+ ;; reply-to-self implies reply-all
+ (reply-all (or reply-to-self (mu4e~draft-reply-all-p origmsg)))
+ (old-msgid (plist-get origmsg :message-id))
+ (subject
+ (concat mu4e~draft-reply-prefix
+ (message-strip-subject-re (or (plist-get origmsg :subject) "")))))
+ (concat
+ (mu4e~draft-header "From" (or (mu4e~draft-from-construct) ""))
+ (mu4e~draft-header "Reply-To" mu4e-compose-reply-to-address)
+
+ (if reply-to-self
+ ;; When we're replying to ourselves, simply keep the same headers.
+ (concat
+ (mu4e~draft-header "To" (mu4e~draft-recipients-list-to-string
+ (mu4e-message-field origmsg :to)))
+ (mu4e~draft-header "Cc" (mu4e~draft-recipients-list-to-string
+ (mu4e-message-field origmsg :cc))))
+
+ ;; if there's no-one in To, copy the CC-list
+ (if (zerop (length (mu4e~draft-create-to-lst origmsg)))
+ (mu4e~draft-header "To" (mu4e~draft-recipients-construct :cc origmsg reply-all))
+ ;; otherwise...
+ (concat
+ (mu4e~draft-header "To" (mu4e~draft-recipients-construct :to origmsg))
+ (mu4e~draft-header "Cc" (mu4e~draft-recipients-construct :cc origmsg
+ reply-all)))))
+ (mu4e~draft-header "Subject" subject)
+ (mu4e~draft-header "References"
+ (mu4e~draft-references-construct origmsg))
+ (mu4e~draft-common-construct)
+ (when old-msgid
+ (mu4e~draft-header "In-reply-to" (format "<%s>" old-msgid)))
+ "\n\n"
+ (mu4e~draft-cite-original origmsg))))
+
+(defconst mu4e~draft-forward-prefix "Fwd: "
+ "String to prefix replies with.")
+
+(defun mu4e~draft-forward-construct (origmsg)
+ "Create a draft forward message for original message ORIGMSG."
+ (let ((subject
+ (or (plist-get origmsg :subject) "")))
+ (concat
+ (mu4e~draft-header "From" (or (mu4e~draft-from-construct) ""))
+ (mu4e~draft-header "Reply-To" mu4e-compose-reply-to-address)
+ (mu4e~draft-header "To" "")
+ (mu4e~draft-common-construct)
+ (mu4e~draft-header "References"
+ (mu4e~draft-references-construct origmsg))
+ (mu4e~draft-header "Subject"
+ (concat
+ ;; if there's no Fwd: yet, prepend it
+ (if (string-match "^Fwd:" subject)
+ ""
+ mu4e~draft-forward-prefix)
+ subject))
+ "\n\n"
+ (mu4e~draft-cite-original origmsg))))
+
+(defun mu4e~draft-newmsg-construct ()
+ "Create a new message."
+ (concat
+ (mu4e~draft-header "From" (or (mu4e~draft-from-construct) ""))
+ (mu4e~draft-header "Reply-To" mu4e-compose-reply-to-address)
+ (mu4e~draft-header "To" "")
+ (mu4e~draft-header "Subject" "")
+ (mu4e~draft-common-construct)))
+
+(defvar mu4e~draft-drafts-folder nil
+ "The drafts-folder for this compose buffer, based on
+`mu4e-drafts-folder', which is evaluated once.")
+
+(defun mu4e~draft-open-file (path)
+ "Open the the draft file at PATH."
+ (if mu4e-compose-in-new-frame
+ (find-file-other-frame path)
+ (find-file path)))
+
+(defun mu4e~draft-determine-path (draft-dir)
+ "Determine the path for a new draft file."
+ (format "%s/%s/cur/%s"
+ mu4e-maildir draft-dir (mu4e~draft-message-filename-construct "DS")))
+
+
+(defun mu4e-draft-open (compose-type &optional msg)
+ "Open a draft file for a new message (when COMPOSE-TYPE is `reply', `forward' or `new'),
+open an existing draft (when COMPOSE-TYPE is `edit'), or re-send
+an existing message (when COMPOSE-TYPE is `resend').
+
+The name of the draft folder is constructed from the
+concatenation of `mu4e-maildir' and `mu4e-drafts-folder' (the
+latter will be evaluated). The message file name is a unique name
+determined by `mu4e-send-draft-file-name'. The initial contents
+will be created from either `mu4e~draft-reply-construct', or
+`mu4e~draft-forward-construct' or `mu4e~draft-newmsg-construct'."
+ (unless mu4e-maildir (mu4e-error "mu4e-maildir not set"))
+ (let ((draft-dir nil))
+ (case compose-type
+
+ (edit
+ ;; case-1: re-editing a draft messages. in this case, we do know the full
+ ;; path, but we cannot really know 'drafts folder'... we make a guess
+ (setq draft-dir (mu4e~guess-maildir (mu4e-message-field msg :path)))
+ (mu4e~draft-open-file (mu4e-message-field msg :path)))
+
+ (resend
+ ;; case-2: copy some exisisting message to a draft message, then edit
+ ;; that.
+ (setq draft-dir (mu4e~guess-maildir (mu4e-message-field msg :path)))
+ (let ((draft-path (mu4e~draft-determine-path draft-dir)))
+ (copy-file (mu4e-message-field msg :path) draft-path)
+ (mu4e~draft-open-file draft-path)))
+
+ ((reply forward new)
+ ;; case-3: creating a new message; in this case, we can determing
+ ;; mu4e-get-drafts-folder
+ (setq draft-dir (mu4e-get-drafts-folder msg))
+ (let ((draft-path (mu4e~draft-determine-path draft-dir))
+ (initial-contents
+ (case compose-type
+ (reply (mu4e~draft-reply-construct msg))
+ (forward (mu4e~draft-forward-construct msg))
+ (new (mu4e~draft-newmsg-construct)))))
+ (mu4e~draft-open-file draft-path)
+ (insert initial-contents)
+ (newline)
+ ;; include the message signature (if it's set)
+ (if (and mu4e-compose-signature-auto-include mu4e-compose-signature)
+ (let ((message-signature mu4e-compose-signature))
+ (save-excursion
+ (message-insert-signature)
+ (mu4e~fontify-signature))))))
+ (t (mu4e-error "unsupported compose-type %S" compose-type)))
+ ;; evaluate mu4e~drafts-drafts-folder once, here, and use that value
+ ;; throughout.
+ (set (make-local-variable 'mu4e~draft-drafts-folder) draft-dir)
+ (put 'mu4e~draft-drafts-folder 'permanent-local t)
+ (unless mu4e~draft-drafts-folder
+ (mu4e-error "failed to determine drafts folder"))))
+
+
+(provide 'mu4e-draft)
diff --git a/_spacemacs.d/local/mu4e/mu4e-headers.el b/_spacemacs.d/local/mu4e/mu4e-headers.el
new file mode 100644
index 0000000..f45d7b4
--- /dev/null
+++ b/_spacemacs.d/local/mu4e/mu4e-headers.el
@@ -0,0 +1,1694 @@
+;;; mu4e-headers.el -- part of mu4e, the mu mail user agent
+;;
+;; Copyright (C) 2011-2016 Dirk-Jan C. Binnema
+
+;; Author: Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
+;; Maintainer: Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
+
+;; This file is not part of GNU Emacs.
+;;
+;; 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 <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; In this file are function related mu4e-headers-mode, to creating the list of
+;; one-line descriptions of emails, aka 'headers' (not to be confused with
+;; headers like 'To:' or 'Subject:')
+
+;; Code:
+(eval-when-compile (byte-compile-disable-warning 'cl-functions))
+(require 'cl)
+
+(require 'fringe)
+(require 'hl-line)
+
+(require 'mu4e-utils) ;; utility functions
+(require 'mu4e-proc)
+(require 'mu4e-vars)
+(require 'mu4e-mark)
+(require 'mu4e-compose)
+(require 'mu4e-actions)
+(require 'mu4e-message)
+
+;; the headers view
+(defgroup mu4e-headers nil
+ "Settings for the headers view."
+ :group 'mu4e)
+
+(defcustom mu4e-headers-fields
+ '( (:human-date . 12)
+ (:flags . 6)
+ (:mailing-list . 10)
+ (:from . 22)
+ (:subject . nil))
+ "A list of header fields to show in the headers buffer.
+Each element has the form (HEADER . WIDTH), where HEADER is one of
+the available headers (see `mu4e-header-info') and WIDTH is the
+respective width in characters. A width of `nil' means
+'unrestricted', and this is best reserved for the rightmost (last)
+field. Note that emacs may become very slow with excessively long
+lines (1000s of characters), so if you regularly get such messages,
+you want to avoid fields with `nil' altogether."
+ :type `(repeat (cons (choice ,@(mapcar (lambda (h)
+ (list 'const :tag
+ (plist-get (cdr h) :help)
+ (car h)))
+ mu4e-header-info))
+ (choice (integer :tag "width")
+ (const :tag "unrestricted width" nil))))
+ :group 'mu4e-headers)
+
+(defcustom mu4e-headers-date-format "%x"
+ "Date format to use in the headers view.
+In the format of `format-time-string'."
+ :type 'string
+ :group 'mu4e-headers)
+
+(defcustom mu4e-headers-time-format "%X"
+ "Time format to use in the headers view.
+In the format of `format-time-string'."
+ :type 'string
+ :group 'mu4e-headers)
+
+(defcustom mu4e-headers-long-date-format "%c"
+ "Date format to use in the headers view tooltip.
+In the format of `format-time-string'."
+ :type 'string
+ :group 'mu4e-headers)
+
+(defcustom mu4e-headers-visible-lines 10
+ "Number of lines to display in the header view when using the
+horizontal split-view. This includes the header-line at the top,
+and the mode-line."
+ :type 'integer
+ :group 'mu4e-headers)
+
+(defcustom mu4e-headers-visible-columns 30
+ "Number of columns to display for the header view when using the
+vertical split-view."
+ :type 'integer
+ :group 'mu4e-headers)
+
+(defcustom mu4e-headers-auto-update t
+ "Whether to automatically update the current headers buffer if an
+indexing operation showed changes."
+ :type 'boolean
+ :group 'mu4e-headers)
+
+(defcustom mu4e-headers-results-limit 500
+ "Maximum number of results to show; this affects performance
+quite a bit, especially when `mu4e-headers-include-related' is
+non-nil. Set to -1 for no limits, and you temporarily (for one
+query) ignore the limit by pressing a C-u before invoking the
+search."
+ :type '(choice (const :tag "Unlimited" -1)
+ (integer :tag "Limit"))
+ :group 'mu4e-headers)
+
+(make-obsolete-variable 'mu4e-search-results-limit
+ 'mu4e-headers-results-limit "0.9.9.5-dev6")
+
+(defcustom mu4e-headers-skip-duplicates nil
+ "With this option set to non-nil, show only one of duplicate
+messages. This is useful when you have multiple copies of the same
+message, which is a common occurence for example when using Gmail
+and offlineimap."
+ :type 'boolean
+ :group 'mu4e-headers)
+
+(defcustom mu4e-headers-include-related nil
+ "With this option set to non-nil, not just return the matches for
+a searches, but also messages that are related (through their
+references) to these messages. This can be useful e.g. to include
+sent messages into message threads."
+ :type 'boolean
+ :group 'mu4e-headers)
+
+(defcustom mu4e-headers-visible-flags
+ '(draft flagged new passed replied seen trashed attach encrypted signed unread)
+ "An ordered list of flags to show in the headers buffer. Each
+element is a symbol in the list (DRAFT FLAGGED NEW PASSED
+REPLIED SEEN TRASHED ATTACH ENCRYPTED SIGNED UNREAD)."
+ :type '(set
+ (const :tag "Draft" draft)
+ (const :tag "Flagged" flagged)
+ (const :tag "New" new)
+ (const :tag "Passed" passed)
+ (const :tag "Replied" replied)
+ (const :tag "Seen" seen)
+ (const :tag "Trashed" trashed)
+ (const :tag "Attach" attach)
+ (const :tag "Encrypted" encrypted)
+ (const :tag "Signed" signed)
+ (const :tag "Unread" unread))
+ :group 'mu4e-headers)
+
+(defcustom mu4e-headers-found-hook nil
+ "Hook run just *after* all of the headers for the last search
+query have been received and are displayed."
+ :type 'hook
+ :group 'mu4e-headers)
+
+(defcustom mu4e-headers-search-bookmark-hook nil
+ "Hook run just after we invoke a bookmarked search. This
+function receives the query as its parameter.
+
+The reason to use this instead of `mu4e-headers-search-hook'
+is if you only want to execute a hook when a search is entered
+via a bookmark, e.g. if you'd like to treat the bookmarks as a
+custom folder and change the options for the search,
+e.g. `mu4e-headers-show-threads', `mu4e-headers-include-related',
+`mu4e-headers-skip-duplicates` or `mu4e-headers-results-limit'."
+ :type 'hook
+ :group 'mu4e-headers)
+
+(defcustom mu4e-headers-search-hook nil
+ "Hook run just before executing a new search operation. This
+function receives the query as its parameter.
+
+This is a more general hook facility than the
+`mu4e-headers-search-bookmark-hook'. It gets called on every
+executed search, not just those that are invoked via bookmarks,
+but also manually invoked searches."
+ :type 'hook
+ :group 'mu4e-headers)
+
+(defvar mu4e-headers-sort-field :date
+ "Field to sort the headers by.
+Field must be a symbol, one of: :date, :subject, :size, :prio,
+:from, :to.")
+
+(defvar mu4e-headers-sort-direction 'descending
+ "Direction to sort by; a symbol either `descending' (sorting
+ Z->A) or `ascending' (sorting A->Z).")
+
+;; marks for headers of the form; each is a cons-cell (basic . fancy)
+;; each of which is basic ascii char and something fancy, respectively
+(defvar mu4e-headers-draft-mark '("D" . "âš’") "Draft.")
+(defvar mu4e-headers-flagged-mark '("F" . "✚") "Flagged.")
+(defvar mu4e-headers-new-mark '("N" . "✱") "New.")
+(defvar mu4e-headers-passed-mark '("P" . "â¯") "Passed (fwd).")
+(defvar mu4e-headers-replied-mark '("R" . "â®") "Replied.")
+(defvar mu4e-headers-seen-mark '("S" . "✔") "Seen.")
+(defvar mu4e-headers-trashed-mark '("T" . "âš") "Trashed.")
+(defvar mu4e-headers-attach-mark '("a" . "âš“") "W/ attachments.")
+(defvar mu4e-headers-encrypted-mark '("x" . "âš´") "Encrypted.")
+(defvar mu4e-headers-signed-mark '("s" . "☡") "Signed.")
+(defvar mu4e-headers-unread-mark '("u" . "⎕") "Unread.")
+
+;; thread prefix marks
+(defvar mu4e-headers-has-child-prefix '("+" . "â—¼ ") "Parent.")
+(defvar mu4e-headers-empty-parent-prefix '("-" . "â—½ ") "Orphan.")
+(defvar mu4e-headers-first-child-prefix '("\\" . "â”—â–¶") "First child.")
+(defvar mu4e-headers-duplicate-prefix '("=" . "≡ ") "Duplicate.")
+(defvar mu4e-headers-default-prefix '("|" . "│ ") "Default.")
+
+(defvar mu4e-headers-actions
+ '( ("capture message" . mu4e-action-capture-message)
+ ("show this thread" . mu4e-action-show-thread))
+ "List of actions to perform on messages in the headers list.
+The actions are of the form (NAME SHORTCUT FUNC) where:
+* NAME is the name of the action (e.g. \"Count lines\")
+* SHORTCUT is a one-character shortcut to call this action
+* FUNC is a function which receives a message plist as an argument.")
+
+(defvar mu4e-headers-custom-markers
+ '(("Older than"
+ (lambda (msg date) (time-less-p (mu4e-msg-field msg :date) date))
+ (lambda () (mu4e-get-time-date "Match messages before: ")))
+ ("Newer than"
+ (lambda (msg date) (time-less-p date (mu4e-msg-field msg :date)))
+ (lambda () (mu4e-get-time-date "Match messages after: ")))
+ ("Bigger than"
+ (lambda (msg bytes) (> (mu4e-msg-field msg :size) (* 1024 bytes)))
+ (lambda () (read-number "Match messages bigger than (Kbytes): "))))
+ "List of custom markers -- functions to mark message that match
+some custom function. Each of the list members has the following format:
+ (NAME PREDICATE-FUNC PARAM-FUNC)
+* NAME is the name of the predicate function, and the first character
+is the shortcut (so keep those unique).
+* PREDICATE-FUNC is a function that takes two parameters, MSG
+and (optionally) PARAM, and should return non-nil when there's a
+match.
+* PARAM-FUNC is function that is evaluated once, and its value is then passed to
+PREDICATE-FUNC as PARAM. This is useful for getting user-input.")
+
+(defvar mu4e-headers-show-threads t
+ "Whether to show threads in the headers list.")
+
+(defvar mu4e-headers-full-search nil
+ "Whether to show all results.
+If this is nil show results up to `mu4e-search-results-limit')")
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+
+;;;; internal variables/constants ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+;; docid cookies
+(defconst mu4e~headers-docid-pre "\376"
+ "Each header starts (invisibly) with the `mu4e~headers-docid-pre',
+followed by the docid, followed by `mu4e~headers-docid-post'.")
+(defconst mu4e~headers-docid-post "\377"
+ "Each header starts (invisibly) with the `mu4e~headers-docid-pre',
+followed by the docid, followed by `mu4e~headers-docid-post'.")
+
+(defvar mu4e~headers-view-win nil
+ "The view window connected to this headers view.")
+
+(defvar mu4e~headers-sort-field-choices
+ '( ("date" . :date)
+ ("from" . :from)
+ ("prio" . :prio)
+ ("zsize" . :size)
+ ("subject" . :subject)
+ ("to" . :to))
+ "List of cells describing the various sort-options.
+In the format needed for `mu4e-read-option'.")
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+(defun mu4e~headers-clear ()
+ "Clear the header buffer and related data structures."
+ (when (buffer-live-p mu4e~headers-buffer)
+ (let ((inhibit-read-only t))
+ (with-current-buffer mu4e~headers-buffer
+ (setq mu4e~view-msg nil)
+ (mu4e~mark-clear)
+ (erase-buffer)))))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; handler functions
+;;
+;; next are a bunch of handler functions; those will be called from mu4e~proc in
+;; response to output from the server process
+
+(defun mu4e~headers-view-handler (msg)
+ "Handler function for displaying a message."
+ (mu4e-view msg mu4e~headers-buffer))
+
+(defun mu4e~headers-view-this-message-p (docid)
+ "Is DOCID currently being viewed?"
+ (let ((viewbuf (get-buffer mu4e~view-buffer-name)))
+ (when (and viewbuf (buffer-live-p viewbuf))
+ (with-current-buffer viewbuf
+ (eq docid (plist-get mu4e~view-msg :docid))))))
+
+(defun mu4e~headers-update-handler (msg is-move)
+ "Update handler, will be called when a message has been updated
+in the database. This function will update the current list of
+headers."
+ (when (buffer-live-p mu4e~headers-buffer)
+ (with-current-buffer mu4e~headers-buffer
+ (let* ((docid (mu4e-message-field msg :docid))
+ (initial-message-at-point (mu4e~headers-docid-at-point))
+ (initial-column (current-column))
+ (point (mu4e~headers-docid-pos docid)))
+ (when point ;; is the message present in this list?
+
+ ;; if it's marked, unmark it now
+ (when (mu4e-mark-docid-marked-p docid)
+ (mu4e-mark-set 'unmark))
+
+ ;; re-use the thread info from the old one; this is needed because
+ ;; *update* messages don't have thread info by themselves (unlike
+ ;; search results)
+ ;; since we still have the search results, re-use
+ ;; those
+ (plist-put msg :thread
+ (mu4e~headers-field-for-docid docid :thread))
+
+ ;; first, remove the old one (otherwise, we'd have two headers with
+ ;; the same docid...
+ (mu4e~headers-remove-handler docid)
+
+ ;; if we're actually viewing this message (in mu4e-view mode), we
+ ;; update it; that way, the flags can be updated, as well as the path
+ ;; (which is useful for viewing the raw message)
+ (when (mu4e~headers-view-this-message-p docid)
+ (mu4e-view msg mu4e~headers-buffer))
+
+ ;; now, if this update was about *moving* a message, we don't show it
+ ;; anymore (of course, we cannot be sure if the message really no
+ ;; longer matches the query, but this seem a good heuristic.
+ ;; if it was only a flag-change, show the message with its updated flags.
+ (unless is-move
+ (mu4e~headers-header-handler msg point))
+
+ (if (and initial-message-at-point
+ (mu4e~headers-goto-docid initial-message-at-point))
+ (progn
+ (move-to-column initial-column)
+ (mu4e~headers-highlight initial-message-at-point))
+ ;; attempt to highlight the corresponding line and make it visible
+ (mu4e~headers-highlight docid))
+ )))))
+
+(defun mu4e~headers-remove-handler (docid)
+ "Remove handler, will be called when a message with DOCID has
+been removed from the database. This function will hide the removed
+message from the current list of headers. If the message is not
+present, don't do anything."
+ (when (buffer-live-p mu4e~headers-buffer)
+ (with-current-buffer mu4e~headers-buffer
+ (mu4e~headers-remove-header docid t)
+
+ ;; if we were viewing this message, close it now.
+ (when (and (mu4e~headers-view-this-message-p docid)
+ (buffer-live-p mu4e~view-buffer))
+ (with-current-buffer mu4e~view-buffer
+ ;; XXX it seems this sometimes fails; investigate;
+ ;; for now, just ignore the error
+ (ignore-errors
+ (kill-buffer-and-window)))))))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+(defsubst mu4e~headers-contact-str (contacts)
+ "Turn the list of contacts CONTACTS (with elements (NAME . EMAIL)
+into a string."
+ (mapconcat
+ (lambda (ct)
+ (let ((name (car ct)) (email (cdr ct)))
+ (or name email "?"))) contacts ", "))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+(defsubst mu4e~headers-thread-prefix (thread)
+ "Calculate the thread prefix based on thread info THREAD."
+ (when thread
+ (let ((get-prefix
+ (lambda (cell) (if mu4e-use-fancy-chars (cdr cell) (car cell)))))
+ (concat
+ (make-string (* (if (plist-get thread :empty-parent) 0 1)
+ (plist-get thread :level)) ?\s)
+ (cond
+ ((plist-get thread :has-child)
+ (funcall get-prefix mu4e-headers-has-child-prefix))
+ ((plist-get thread :empty-parent)
+ (funcall get-prefix mu4e-headers-empty-parent-prefix))
+ ((plist-get thread :first-child)
+ (funcall get-prefix mu4e-headers-first-child-prefix))
+ ((plist-get thread :duplicate)
+ (funcall get-prefix mu4e-headers-duplicate-prefix))
+ (t
+ (funcall get-prefix mu4e-headers-default-prefix)))
+ " "))))
+
+(defsubst mu4e~headers-flags-str (flags)
+ "Get a display string for the flags.
+Note that `mu4e-flags-to-string' is for internal use only; this
+function is for display. (This difference is significant, since
+internally, the Maildir spec determines what the flags look like,
+while our display may be different)."
+ (let ((str "")
+ (get-prefix
+ (lambda (cell) (if mu4e-use-fancy-chars (cdr cell) (car cell)))))
+ (dolist (flag mu4e-headers-visible-flags)
+ (when (member flag flags)
+ (setq str
+ (concat str
+ (case flag
+ ('draft (funcall get-prefix mu4e-headers-draft-mark))
+ ('flagged (funcall get-prefix mu4e-headers-flagged-mark))
+ ('new (funcall get-prefix mu4e-headers-new-mark))
+ ('passed (funcall get-prefix mu4e-headers-passed-mark))
+ ('replied (funcall get-prefix mu4e-headers-replied-mark))
+ ('seen (funcall get-prefix mu4e-headers-seen-mark))
+ ('trashed (funcall get-prefix mu4e-headers-trashed-mark))
+ ('attach (funcall get-prefix mu4e-headers-attach-mark))
+ ('encrypted (funcall get-prefix mu4e-headers-encrypted-mark))
+ ('signed (funcall get-prefix mu4e-headers-signed-mark))
+ ('unread (funcall get-prefix mu4e-headers-unread-mark)))))))
+ str))
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+
+(defconst mu4e-headers-from-or-to-prefix '("" . "To ")
+ "Prefix for the :from-or-to field.
+It's a cons cell with the car element being the From: prefix, the
+cdr element the To: prefix.")
+
+(defsubst mu4e~headers-from-or-to (msg)
+ "When the from address for message MSG is one of the the user's addresses,
+\(as per `mu4e-user-mail-address-list'), show the To address;
+otherwise ; show the from address; prefixed with the appropriate
+`mu4e-headers-from-or-to-prefix'."
+ (let ((addr (cdr-safe (car-safe (mu4e-message-field msg :from)))))
+ (if (mu4e-user-mail-address-p addr)
+ (concat (cdr mu4e-headers-from-or-to-prefix)
+ (mu4e~headers-contact-str (mu4e-message-field msg :to)))
+ (concat (car mu4e-headers-from-or-to-prefix)
+ (mu4e~headers-contact-str (mu4e-message-field msg :from))))))
+
+(defsubst mu4e~headers-human-date (msg)
+ "Show a 'human' date.
+If the date is today, show the time, otherwise, show the
+date. The formats used for date and time are
+`mu4e-headers-date-format' and `mu4e-headers-time-format'."
+ (let ((date (mu4e-msg-field msg :date)))
+ (if (equal date '(0 0 0))
+ "None"
+ (let ((day1 (decode-time date))
+ (day2 (decode-time (current-time))))
+ (if (and
+ (eq (nth 3 day1) (nth 3 day2)) ;; day
+ (eq (nth 4 day1) (nth 4 day2)) ;; month
+ (eq (nth 5 day1) (nth 5 day2))) ;; year
+ (format-time-string mu4e-headers-time-format date)
+ (format-time-string mu4e-headers-date-format date))))))
+
+
+(defsubst mu4e~headers-thread-subject (msg)
+ "Get the subject if it is the first one in a thread; otherwise,
+return the thread-prefix without the subject-text. In other words,
+show the subject of a thread only once, similar to e.g. 'mutt'."
+ (let* ((tinfo (mu4e-message-field msg :thread))
+ (subj (mu4e-msg-field msg :subject)))
+ (concat ;; prefix subject with a thread indicator
+ (mu4e~headers-thread-prefix tinfo)
+ (if (or (not tinfo) (zerop (plist-get tinfo :level))
+ (plist-get tinfo :empty-parent))
+ (truncate-string-to-width subj 600) ""))))
+
+
+(defsubst mu4e~headers-mailing-list (list)
+ "Get some identifier for the mailing list."
+ (if list
+ (propertize (mu4e-get-mailing-list-shortname list) 'help-echo list)
+ ""))
+
+(defun mu4e~headers-custom-field (msg field)
+ "Show some custom header field, or raise an error if it is not
+found."
+ (let* ((item (or (assoc field mu4e-header-info-custom)
+ (mu4e-error "field %S not found" field)))
+ (func (or (plist-get (cdr-safe item) :function)
+ (mu4e-error "no :function defined for field %S %S" field (cdr item)))))
+ (funcall func msg)))
+
+(defun mu4e~headers-field-apply-basic-properties (msg field val width)
+ (case field
+ (:subject
+ (concat ;; prefix subject with a thread indicator
+ (mu4e~headers-thread-prefix (mu4e-message-field msg :thread))
+ ;; "["(plist-get (mu4e-message-field msg :thread) :path) "] "
+ ;; work-around: emacs' display gets really slow when lines are too long;
+ ;; so limit subject length to 600
+ (truncate-string-to-width val 600)))
+ (:thread-subject (mu4e~headers-thread-subject msg))
+ ((:maildir :path :message-id) val)
+ ((:to :from :cc :bcc) (mu4e~headers-contact-str val))
+ ;; if we (ie. `user-mail-address' is the 'From', show
+ ;; 'To', otherwise show From
+ (:from-or-to (mu4e~headers-from-or-to msg))
+ (:date (format-time-string mu4e-headers-date-format val))
+ (:mailing-list (mu4e~headers-mailing-list val))
+ (:human-date (propertize (mu4e~headers-human-date msg)
+ 'help-echo (format-time-string
+ mu4e-headers-long-date-format
+ (mu4e-msg-field msg :date))))
+ (:flags (propertize (mu4e~headers-flags-str val)
+ 'help-echo (format "%S" val)))
+ (:tags (propertize (mapconcat 'identity val ", ")))
+ (:size (mu4e-display-size val))
+ (t (mu4e~headers-custom-field msg field))))
+
+(defun mu4e~headers-field-truncate-to-width (_msg _field val width)
+ "Truncate VAL to WIDTH."
+ (if width
+ (truncate-string-to-width val width 0 ?\s t)
+ val))
+
+(defvar mu4e~headers-field-handler-functions
+ '(mu4e~headers-field-apply-basic-properties
+ mu4e~headers-field-truncate-to-width))
+
+(defun mu4e~headers-field-handler (f-w msg)
+ "Create a description of the field of MSG described by F-W."
+ (let* ((field (car f-w))
+ (width (cdr f-w))
+ (val (mu4e-message-field msg (car f-w))))
+ (dolist (func mu4e~headers-field-handler-functions)
+ (setq val (funcall func msg field val width)))
+ val))
+
+(defvar mu4e~headers-line-handler-functions
+ '(mu4e~headers-line-apply-flag-face))
+
+(defun mu4e~headers-line-apply-flag-face (msg line)
+ "Adjust LINE's face property based on FLAGS."
+ (let* ((flags (mu4e-message-field msg :flags))
+ (face (cond
+ ((memq 'trashed flags) 'mu4e-trashed-face)
+ ((memq 'draft flags) 'mu4e-draft-face)
+ ((or (memq 'unread flags) (memq 'new flags))
+ 'mu4e-unread-face)
+ ((memq 'flagged flags) 'mu4e-flagged-face)
+ ((memq 'replied flags) 'mu4e-replied-face)
+ ((memq 'passed flags) 'mu4e-forwarded-face)
+ (t 'mu4e-header-face))))
+ ;; hmmm, this only works with emacs 24.4+
+ (when (fboundp 'add-face-text-property)
+ (add-face-text-property 0 (length line) face t line))
+ line))
+
+(defun mu4e~headers-line-handler (msg line)
+ (dolist (func mu4e~headers-line-handler-functions)
+ (setq line (funcall func msg line)))
+ line)
+
+;; note: this function is very performance-sensitive
+(defun mu4e~headers-header-handler (msg &optional point)
+ "Create a one line description of MSG in this buffer, at POINT,
+if provided, or at the end of the buffer otherwise."
+ (let ((docid (mu4e-message-field msg :docid))
+ (line (mapconcat (lambda (f-w)
+ (mu4e~headers-field-handler f-w msg))
+ mu4e-headers-fields " ")))
+ (setq line (mu4e~headers-line-handler msg line))
+ (mu4e~headers-add-header line docid point msg)))
+
+(defconst mu4e~no-matches "No matching messages found")
+(defconst mu4e~end-of-results "End of search results")
+
+(defun mu4e~headers-found-handler (count)
+ "Create a one line description of the number of headers found
+after the end of the search results."
+ (when (buffer-live-p mu4e~headers-buffer)
+ (with-current-buffer mu4e~headers-buffer
+ (save-excursion
+ (goto-char (point-max))
+ (let ((inhibit-read-only t)
+ (str (if (zerop count) mu4e~no-matches mu4e~end-of-results)))
+ (insert (propertize str 'face 'mu4e-system-face 'intangible t))
+ (unless (zerop count)
+ (mu4e-message "Found %d matching message%s"
+ count (if (= 1 count) "" "s")))))
+ ;; if we need to jump to some specific message, do so now
+ (goto-char (point-min))
+ (when mu4e~headers-msgid-target
+ (mu4e-headers-goto-message-id mu4e~headers-msgid-target))
+ (when mu4e~headers-view-target
+ (mu4e-headers-view-message)) ;; view the message at point
+ (setq mu4e~headers-view-target nil
+ mu4e~headers-msgid-target nil))
+ (when (mu4e~headers-docid-at-point)
+ (mu4e~headers-highlight (mu4e~headers-docid-at-point)))
+ ;; run-hooks
+ (run-hooks 'mu4e-headers-found-hook)))
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+
+(defmacro mu4e~headers-defun-mark-for (mark)
+ "Define a function mu4e~headers-mark-MARK."
+ (let ((funcname (intern (format "mu4e-headers-mark-for-%s" mark)))
+ (docstring (format "Mark header at point with %s." mark)))
+ `(progn
+ (defun ,funcname () ,docstring
+ (interactive)
+ (mu4e-headers-mark-and-next ',mark))
+ (put ',funcname 'definition-name ',mark))))
+
+(mu4e~headers-defun-mark-for refile)
+(mu4e~headers-defun-mark-for something)
+(mu4e~headers-defun-mark-for delete)
+(mu4e~headers-defun-mark-for flag)
+(mu4e~headers-defun-mark-for move)
+(mu4e~headers-defun-mark-for read)
+(mu4e~headers-defun-mark-for trash)
+(mu4e~headers-defun-mark-for unflag)
+(mu4e~headers-defun-mark-for untrash)
+(mu4e~headers-defun-mark-for unmark)
+(mu4e~headers-defun-mark-for unread)
+(mu4e~headers-defun-mark-for action)
+
+
+;;; headers-mode and mode-map ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+(defvar mu4e-headers-mode-map nil
+ "Keymap for *mu4e-headers* buffers.")
+(unless mu4e-headers-mode-map
+ (setq mu4e-headers-mode-map
+ (let ((map (make-sparse-keymap)))
+
+ (define-key map (kbd "C-S-u") 'mu4e-update-mail-and-index)
+ ;; for terminal users
+ (define-key map (kbd "C-c C-u") 'mu4e-update-mail-and-index)
+
+ (define-key map "s" 'mu4e-headers-search)
+ (define-key map "S" 'mu4e-headers-search-edit)
+
+ (define-key map "/" 'mu4e-headers-search-narrow)
+
+ (define-key map "j" 'mu4e~headers-jump-to-maildir)
+
+ (define-key map (kbd "<M-left>") 'mu4e-headers-query-prev)
+ (define-key map (kbd "\\") 'mu4e-headers-query-prev)
+ (define-key map (kbd "<M-right>") 'mu4e-headers-query-next)
+
+ (define-key map "b" 'mu4e-headers-search-bookmark)
+ (define-key map "B" 'mu4e-headers-search-bookmark-edit)
+
+ (define-key map "O" 'mu4e-headers-change-sorting)
+ (define-key map "P" 'mu4e-headers-toggle-threading)
+ (define-key map "Q" 'mu4e-headers-toggle-full-search)
+ (define-key map "W" 'mu4e-headers-toggle-include-related)
+ (define-key map "V" 'mu4e-headers-toggle-skip-duplicates)
+
+ (define-key map "q" 'mu4e~headers-quit-buffer)
+ (define-key map "g" 'mu4e-headers-rerun-search) ;; for compatibility
+
+ (define-key map "%" 'mu4e-headers-mark-pattern)
+ (define-key map "t" 'mu4e-headers-mark-subthread)
+ (define-key map "T" 'mu4e-headers-mark-thread)
+
+ ;; navigation between messages
+ (define-key map "p" 'mu4e-headers-prev)
+ (define-key map "n" 'mu4e-headers-next)
+ (define-key map (kbd "<M-up>") 'mu4e-headers-prev)
+ (define-key map (kbd "<M-down>") 'mu4e-headers-next)
+
+ (define-key map (kbd "[") 'mu4e-headers-prev-unread)
+ (define-key map (kbd "]") 'mu4e-headers-next-unread)
+
+ ;; change the number of headers
+ (define-key map (kbd "C-+") 'mu4e-headers-split-view-grow)
+ (define-key map (kbd "C--") 'mu4e-headers-split-view-shrink)
+ (define-key map (kbd "<C-kp-add>") 'mu4e-headers-split-view-grow)
+ (define-key map (kbd "<C-kp-subtract>") 'mu4e-headers-split-view-shrink)
+
+ (define-key map ";" 'mu4e-context-switch)
+
+ ;; switching to view mode (if it's visible)
+ (define-key map "y" 'mu4e-select-other-view)
+
+ ;; marking/unmarking ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+ (define-key map (kbd "<backspace>") 'mu4e-headers-mark-for-trash)
+ (define-key map (kbd "d") 'mu4e-headers-mark-for-trash)
+ (define-key map (kbd "<delete>") 'mu4e-headers-mark-for-delete)
+ (define-key map (kbd "<deletechar>") 'mu4e-headers-mark-for-delete)
+ (define-key map (kbd "D") 'mu4e-headers-mark-for-delete)
+ (define-key map (kbd "m") 'mu4e-headers-mark-for-move)
+ (define-key map (kbd "r") 'mu4e-headers-mark-for-refile)
+
+ (define-key map (kbd "?") 'mu4e-headers-mark-for-unread)
+ (define-key map (kbd "!") 'mu4e-headers-mark-for-read)
+ (define-key map (kbd "A") 'mu4e-headers-mark-for-action)
+
+ (define-key map (kbd "u") 'mu4e-headers-mark-for-unmark)
+ (define-key map (kbd "+") 'mu4e-headers-mark-for-flag)
+ (define-key map (kbd "-") 'mu4e-headers-mark-for-unflag)
+ (define-key map (kbd "=") 'mu4e-headers-mark-for-untrash)
+ (define-key map (kbd "&") 'mu4e-headers-mark-custom)
+
+ (define-key map (kbd "*") 'mu4e-headers-mark-for-something)
+ (define-key map (kbd "<kp-multiply>") 'mu4e-headers-mark-for-something)
+ (define-key map (kbd "<insertchar>") 'mu4e-headers-mark-for-something)
+ (define-key map (kbd "<insert>") 'mu4e-headers-mark-for-something)
+
+
+ (define-key map (kbd "#") 'mu4e-mark-resolve-deferred-marks)
+
+ (define-key map "U" 'mu4e-mark-unmark-all)
+ (define-key map "x" 'mu4e-mark-execute-all)
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+ (define-key map "a" 'mu4e-headers-action)
+
+ ;; message composition
+ (define-key map "R" 'mu4e-compose-reply)
+ (define-key map "F" 'mu4e-compose-forward)
+ (define-key map "C" 'mu4e-compose-new)
+ (define-key map "E" 'mu4e-compose-edit)
+
+ (define-key map (kbd "RET") 'mu4e-headers-view-message)
+ (define-key map [mouse-2] 'mu4e-headers-view-message)
+
+ (define-key map "$" 'mu4e-show-log)
+ (define-key map "H" 'mu4e-display-manual)
+
+ ;; menu
+ (define-key map [menu-bar] (make-sparse-keymap))
+ (let ((menumap (make-sparse-keymap "Headers")))
+ (define-key map [menu-bar headers] (cons "Headers" menumap))
+
+ (define-key menumap [mu4e~headers-quit-buffer]
+ '("Quit view" . mu4e~headers-quit-buffer))
+ (define-key menumap [display-help] '("Help" . mu4e-display-manual))
+
+ (define-key menumap [sepa0] '("--"))
+
+ (define-key menumap [toggle-include-related]
+ '(menu-item "Toggle related messages" mu4e-headers-toggle-include-related
+ :button (:toggle . (and (boundp 'mu4e-headers-include-related)
+ mu4e-headers-include-related))))
+ (define-key menumap [toggle-threading]
+ '(menu-item "Toggle threading" mu4e-headers-toggle-threading
+ :button (:toggle . (and (boundp 'mu4e-headers-show-threads)
+ mu4e-headers-show-threads))))
+
+ (define-key menumap [sepa1] '("--"))
+
+ (define-key menumap [execute-marks] '("Execute marks"
+ . mu4e-mark-execute-all))
+ (define-key menumap [unmark-all] '("Unmark all" . mu4e-mark-unmark-all))
+ (define-key menumap [unmark] '("Unmark" . mu4e-headers-mark-for-unmark))
+
+ (define-key menumap [mark-pattern] '("Mark pattern" .
+ mu4e-headers-mark-pattern))
+ (define-key menumap [mark-as-read] '("Mark as read" .
+ mu4e-headers-mark-for-read))
+ (define-key menumap [mark-as-unread]
+ '("Mark as unread" . mu4e-headers-mark-for-unread))
+
+ (define-key menumap [mark-delete]
+ '("Mark for deletion" . mu4e-headers-mark-for-delete))
+ (define-key menumap [mark-trash]
+ '("Mark for trash" . mu4e-headers-mark-for-trash))
+ (define-key menumap [mark-move]
+ '("Mark for move" . mu4e-headers-mark-for-move))
+ (define-key menumap [sepa2] '("--"))
+
+
+ (define-key menumap [resend] '("Resend" . mu4e-compose-resend))
+ (define-key menumap [forward] '("Forward" . mu4e-compose-forward))
+ (define-key menumap [reply] '("Reply" . mu4e-compose-reply))
+ (define-key menumap [compose-new] '("Compose new" . mu4e-compose-new))
+
+
+ (define-key menumap [sepa3] '("--"))
+
+ (define-key menumap [query-next] '("Next query" . mu4e-headers-query-next))
+ (define-key menumap [query-prev] '("Previous query" .
+ mu4e-headers-query-prev))
+ (define-key menumap [narrow-search] '("Narrow search" .
+ mu4e-headers-search-narrow))
+ (define-key menumap [bookmark] '("Search bookmark" .
+ mu4e-headers-search-bookmark))
+ (define-key menumap [jump] '("Jump to maildir" .
+ mu4e~headers-jump-to-maildir))
+ (define-key menumap [refresh] '("Refresh" . mu4e-headers-rerun-search))
+ (define-key menumap [search] '("Search" . mu4e-headers-search))
+
+
+ (define-key menumap [sepa4] '("--"))
+
+ (define-key menumap [view] '("View" . mu4e-headers-view-message))
+ (define-key menumap [next] '("Next" . mu4e-headers-next))
+ (define-key menumap [previous] '("Previous" . mu4e-headers-prev))
+ (define-key menumap [sepa5] '("--")))
+ map)))
+(fset 'mu4e-headers-mode-map mu4e-headers-mode-map)
+
+
+(defun mu4e~header-line-format ()
+ "Get the format for the header line."
+ (let ((uparrow (if mu4e-use-fancy-chars " â–²" " ^"))
+ (downarrow (if mu4e-use-fancy-chars " â–¼" " V")))
+ (cons
+ (make-string
+ (+ mu4e~mark-fringe-len (floor (fringe-columns 'left t))) ?\s)
+ (mapcar
+ (lambda (item)
+ (let* ((field (car item)) (width (cdr item))
+ (info (cdr (assoc field
+ (append mu4e-header-info mu4e-header-info-custom))))
+ (sortable (plist-get info :sortable))
+ ;; if sortable, it is either t (when field is sortable itself)
+ ;; or a symbol (if another field is used for sorting)
+ (sortfield (when sortable (if (booleanp sortable) field sortable)))
+ (help (plist-get info :help))
+ ;; triangle to mark the sorted-by column
+ (arrow
+ (when (and sortable (eq sortfield mu4e-headers-sort-field))
+ (if (eq mu4e-headers-sort-direction 'descending) downarrow uparrow)))
+ (name (concat (plist-get info :shortname) arrow))
+ (map (make-sparse-keymap)))
+ (when sortable
+ (define-key map [header-line mouse-1]
+ (lambda (&optional e)
+ ;; getting the field, inspired by `tabulated-list-col-sort'
+ (interactive "e")
+ (let* ((obj (posn-object (event-start e)))
+ (field
+ (and obj (get-text-property 0 'field (car obj)))))
+ ;; "t": if we're already sorted by field, the sort-order is
+ ;; changed
+ (mu4e-headers-change-sorting field t)))))
+ (concat
+ (propertize
+ (if width
+ (truncate-string-to-width name width 0 ?\s t)
+ name)
+ 'face (when arrow 'bold)
+ 'help-echo help
+ 'mouse-face (when sortable 'highlight)
+ 'keymap (when sortable map)
+ 'field field) " ")))
+ mu4e-headers-fields))))
+
+(defvar mu4e-headers-mode-abbrev-table nil)
+
+(defun mu4e~headers-do-auto-update ()
+ "Update the current headers buffer after indexing has brought
+some changes, `mu4e-headers-auto-update' is non-nil and there is no
+user-interaction ongoing."
+ (when (and mu4e-headers-auto-update ;; must be set
+ (zerop (mu4e-mark-marks-num)) ;; non active marks
+ (not (active-minibuffer-window))) ;; no user input
+ (with-current-buffer mu4e~headers-buffer
+ (mu4e-headers-rerun-search))))
+
+(define-derived-mode mu4e-headers-mode special-mode
+ "mu4e:headers"
+ "Major mode for displaying mu4e search results.
+\\{mu4e-headers-mode-map}."
+ (use-local-map mu4e-headers-mode-map)
+ (make-local-variable 'mu4e~headers-proc)
+ (make-local-variable 'mu4e~highlighted-docid)
+ (make-local-variable 'global-mode-string)
+ (set (make-local-variable 'hl-line-face) 'mu4e-header-highlight-face)
+
+ ;; maybe update the current headers upon indexing changes
+ (add-hook 'mu4e-index-updated-hook 'mu4e~headers-do-auto-update nil t)
+ (setq
+ truncate-lines t
+ buffer-undo-list t ;; don't record undo information
+ overwrite-mode nil
+ header-line-format (mu4e~header-line-format))
+
+ (mu4e~mark-initialize) ;; initialize the marking subsystem
+ (hl-line-mode 1))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;; highlighting
+(defvar mu4e~highlighted-docid nil
+ "The highlighted docid")
+
+(defun mu4e~headers-highlight (docid)
+ "Highlight the header with DOCID, or do nothing if it's not found.
+Also, unhighlight any previously highlighted headers."
+ (with-current-buffer mu4e~headers-buffer
+ (save-excursion
+ ;; first, unhighlight the previously highlighted docid, if any
+ (when (and docid mu4e~highlighted-docid
+ (mu4e~headers-goto-docid mu4e~highlighted-docid))
+ (hl-line-unhighlight))
+ ;; now, highlight the new one
+ (when (mu4e~headers-goto-docid docid)
+ (hl-line-highlight)))
+ (setq mu4e~highlighted-docid docid)))
+
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+(defun mu4e~headers-select-window ()
+ "When there is a visible window for the headers buffer, make sure
+to select it. This is needed when adding new headers, otherwise
+adding a lot of new headers looks really choppy."
+ (let ((win (get-buffer-window mu4e~headers-buffer)))
+ (when win (select-window win))))
+
+;;;; headers in the buffer are prefixed by an invisible string with the docid
+;;;; followed by an EOT ('end-of-transmission', \004, ^D) non-printable ascii
+;;;; character. this string also has a text-property with the docid. the former
+;;;; is used for quickly finding a certain header, the latter for retrieving the
+;;;; docid at point without string matching etc.
+
+(defsubst mu4e~headers-docid-cookie (docid)
+ "Create an invisible string containing DOCID; this is to be used
+at the beginning of lines to identify headers."
+ (propertize (format "%s%d%s"
+ mu4e~headers-docid-pre docid mu4e~headers-docid-post)
+ 'docid docid 'invisible t));;
+
+(defsubst mu4e~headers-docid-at-point (&optional point)
+ "Get the docid for the header at POINT, or at current (point) if
+nil. Returns the docid, or nil if there is none."
+ (save-excursion
+ (when point
+ (goto-char point))
+ (get-text-property (line-beginning-position) 'docid)))
+
+(defun mu4e~headers-goto-docid (docid &optional to-mark)
+ "Go to the beginning of the line with the header with docid
+DOCID, or nil if it cannot be found. If the optional TO-MARK is
+non-nil, go to the point directly *after* the docid-cookie instead
+of the beginning of the line."
+ (let ((oldpoint (point)) (newpoint))
+ (goto-char (point-min))
+ (setq newpoint
+ (search-forward (mu4e~headers-docid-cookie docid) nil t))
+ (unless to-mark
+ (if (null newpoint)
+ (goto-char oldpoint) ;; not found; restore old pos
+ (progn
+ (beginning-of-line) ;; found, move to beginning of line
+ (setq newpoint (point)))))
+ newpoint)) ;; return the point, or nil if not found
+
+
+(defsubst mu4e~headers-docid-pos (docid)
+ "Return the pos of the beginning of the line with the header with
+docid DOCID, or nil if it cannot be found."
+ (let ((pos))
+ (save-excursion
+ (setq pos (mu4e~headers-goto-docid docid)))
+ pos))
+
+(defsubst mu4e~headers-field-for-docid (docid field)
+ "Get FIELD (a symbol, see `mu4e-headers-names') for the message
+with DOCID which must be present in the headers buffer."
+ (save-excursion
+ (when (mu4e~headers-goto-docid docid)
+ (mu4e-message-field (mu4e-message-at-point) field))))
+
+(defun mu4e-headers-goto-message-id (msgid)
+ "Go to the next message with message-id MSGID. Return the
+message plist, or nil if not found."
+ (mu4e-headers-find-if
+ (lambda (msg)
+ (let ((this-msgid (mu4e-message-field msg :message-id)))
+ (when (and this-msgid (string= msgid this-msgid))
+ msg)))))
+
+;;;; markers mark headers for
+(defun mu4e~headers-mark (docid mark)
+ "(Visually) mark the header for DOCID with character MARK."
+ (with-current-buffer mu4e~headers-buffer
+ (let ((inhibit-read-only t) (oldpoint (point)))
+ (unless (mu4e~headers-goto-docid docid)
+ (mu4e-error "Cannot find message with docid %S" docid))
+ ;; now, we're at the beginning of the header, looking at
+ ;; <docid>\004
+ ;; (which is invisible). jump past that…
+ (unless (re-search-forward mu4e~headers-docid-post nil t)
+ (mu4e-error "Cannot find the `mu4e~headers-docid-post' separator"))
+
+ ;; clear old marks, and add the new ones.
+ (let ((msg (get-text-property (point) 'msg)))
+ (delete-char mu4e~mark-fringe-len)
+ (insert (propertize
+ (format mu4e~mark-fringe-format mark)
+ 'face 'mu4e-header-marks-face
+ 'docid docid
+ 'msg msg)))
+ (goto-char oldpoint))))
+
+
+(defsubst mu4e~headers-add-header (str docid point &optional msg)
+ "Add header STR with DOCID to the buffer at POINT if non-nil, or
+at (point-max) otherwise. If MSG is not nil, add it as the
+text-property `msg'."
+ (when (buffer-live-p mu4e~headers-buffer)
+ (with-current-buffer mu4e~headers-buffer
+ (let ((inhibit-read-only t)
+ (is-first-header (= (point-min) (point-max))))
+ (save-excursion
+ (goto-char (if point point (point-max)))
+ (insert
+ (propertize
+ (concat
+ (mu4e~headers-docid-cookie docid)
+ mu4e~mark-fringe
+ str "\n")
+ 'docid docid 'msg msg)))))))
+
+(defun mu4e~headers-remove-header (docid &optional ignore-missing)
+ "Remove header with DOCID at point.
+When IGNORE-MISSING is non-nill, don't raise an error when the
+docid is not found."
+ (with-current-buffer mu4e~headers-buffer
+ (if (mu4e~headers-goto-docid docid)
+ (let ((inhibit-read-only t))
+ (delete-region (line-beginning-position) (line-beginning-position 2)))
+ (unless ignore-missing
+ (mu4e-error "Cannot find message with docid %S" docid)))))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+(defun mu4e~headers-search-execute (expr ignore-history)
+ "Search in the mu database for EXPR, and switch to the output
+buffer for the results. If IGNORE-HISTORY is true, do *not* update
+the query history stack."
+ ;; note: we don't want to update the history if this query comes from
+ ;; `mu4e~headers-query-next' or `mu4e~headers-query-prev'.
+ (mu4e-hide-other-mu4e-buffers)
+ (let* ((buf (get-buffer-create mu4e~headers-buffer-name))
+ (inhibit-read-only t)
+ (maxnum (unless mu4e-headers-full-search mu4e-headers-results-limit)))
+ (with-current-buffer buf
+ (mu4e-headers-mode)
+ (unless ignore-history
+ ;; save the old present query to the history list
+ (when mu4e~headers-last-query
+ (mu4e~headers-push-query mu4e~headers-last-query 'past)))
+ (setq
+ mu4e~headers-buffer buf
+ mode-name "mu4e-headers"
+ mu4e~headers-last-query expr
+ global-mode-string
+ '(:eval
+ (concat
+ (propertize
+ (mu4e~quote-for-modeline mu4e~headers-last-query)
+ 'face 'mu4e-modeline-face)
+ " "
+ (mu4e-context-label)))))
+
+ (switch-to-buffer buf)
+ (run-hook-with-args 'mu4e-headers-search-hook expr)
+ (mu4e~proc-find
+ expr
+ mu4e-headers-show-threads
+ mu4e-headers-sort-field
+ mu4e-headers-sort-direction
+ maxnum
+ mu4e-headers-skip-duplicates
+ mu4e-headers-include-related)))
+
+(defun mu4e~headers-redraw-get-view-window ()
+ "Close all windows, redraw the headers buffer based on the value
+of `mu4e-split-view', and return a window for the message view."
+ (mu4e-hide-other-mu4e-buffers)
+ (unless (buffer-live-p mu4e~headers-buffer)
+ (mu4e-error "No headers buffer available"))
+ (switch-to-buffer mu4e~headers-buffer)
+ ;; kill the existing view win
+ (when (buffer-live-p mu4e~view-buffer)
+ (kill-buffer mu4e~view-buffer))
+ ;; get a new view window
+ (setq mu4e~headers-view-win
+ (let* ((new-win-func
+ (cond
+ ((eq mu4e-split-view 'horizontal) ;; split horizontally
+ '(split-window-vertically mu4e-headers-visible-lines))
+ ((eq mu4e-split-view 'vertical) ;; split vertically
+ '(split-window-horizontally mu4e-headers-visible-columns)))))
+ (cond ((with-demoted-errors "Unable to split window: %S"
+ (eval new-win-func)))
+ (t ;; no splitting; just use the currently selected one
+ (selected-window)))))
+ mu4e~headers-view-win)
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; search-based marking
+
+(defun mu4e-headers-for-each (func)
+ "Call FUNC for each header, moving point to the header.
+FUNC receives one argument, the message s-expression for the
+corresponding header."
+ (save-excursion
+ (goto-char (point-min))
+ (while (search-forward mu4e~headers-docid-pre nil t)
+ ;; not really sure why we need to jump to bol; we do need to, otherwise we
+ ;; miss lines sometimes...
+ (let ((msg (get-text-property (line-beginning-position) 'msg)))
+ (when msg
+ (funcall func msg))))))
+
+(defun mu4e-headers-find-if (func &optional backward)
+ "Move to the next header for which FUNC returns non-`nil',
+starting from the current position. FUNC receives one argument, the
+message s-expression for the corresponding header. If BACKWARD is
+non-`nil', search backwards. Returns the new position, or `nil' if
+nothing was found. If you want to exclude matches for the current
+message, you can use `mu4e-headers-find-if-next'."
+ (let ((pos)
+ (search-func (if backward 'search-backward 'search-forward)))
+ (save-excursion
+ (while (and (null pos)
+ (funcall search-func mu4e~headers-docid-pre nil t))
+ ;; not really sure why we need to jump to bol; we do need to, otherwise we
+ ;; miss lines sometimes...
+ (let ((msg (get-text-property (line-beginning-position) 'msg)))
+ (when (and msg (funcall func msg))
+ (setq pos (point))))))
+ (when pos
+ (goto-char pos))))
+
+(defun mu4e-headers-find-if-next (func &optional backwards)
+ "Like `mu4e-headers-find-if', but do not match the current header.
+Move to the next or (if BACKWARDS is non-`nil') header for which FUNC
+returns non-`nil', starting from the current position."
+ (let ((pos))
+ (save-excursion
+ (if backwards
+ (beginning-of-line)
+ (end-of-line))
+ (setq pos (mu4e-headers-find-if func backwards)))
+ (when pos (goto-char pos))))
+
+(defvar mu4e~headers-regexp-hist nil
+ "History list of regexps used.")
+
+(defun mu4e-headers-mark-for-each-if (markpair mark-pred &optional param)
+ "Mark all headers for which predicate function MARK-PRED returns
+non-nil with MARKPAIR. MARK-PRED is function that receives two
+arguments, MSG (the message at point) and PARAM (a user-specified
+parameter). MARKPAIR is a cell (MARK . TARGET); see
+`mu4e-mark-at-point' for details about marks."
+ (mu4e-headers-for-each
+ (lambda (msg)
+ (when (funcall mark-pred msg param)
+ (mu4e-mark-at-point (car markpair) (cdr markpair))))))
+
+(defun mu4e-headers-mark-pattern ()
+ "Ask user for a kind of mark (move, delete etc.), a field to
+match and a regular expression to match with. Then, mark all
+matching messages with that mark."
+ (interactive)
+ (let ((markpair (mu4e~mark-get-markpair "Mark matched messages with: " t))
+ (field (mu4e-read-option "Field to match: "
+ '( ("subject" . :subject)
+ ("from" . :from)
+ ("to" . :to)
+ ("cc" . :cc)
+ ("bcc" . :bcc)
+ ("list" . :mailing-list))))
+ (pattern (read-string
+ (mu4e-format "Regexp:")
+ nil 'mu4e~headers-regexp-hist)))
+ (mu4e-headers-mark-for-each-if
+ markpair
+ (lambda (msg param)
+ (let* ((do-mark) (value (mu4e-msg-field msg field)))
+ (setq do-mark
+ (if (member field '(:to :from :cc :bcc :reply-to))
+ (find-if (lambda (contact)
+ (let ((name (car contact)) (email (cdr contact)))
+ (or (and name (string-match pattern name))
+ (and email (string-match pattern email))))) value)
+ (string-match pattern (or value "")))))))))
+
+(defun mu4e-headers-mark-custom ()
+ "Mark messages based on a user-provided predicate function."
+ (interactive)
+ (let* ((pred (mu4e-read-option "Match function: "
+ mu4e-headers-custom-markers))
+ (param (when (cdr pred) (eval (cdr pred))))
+ (markpair (mu4e~mark-get-markpair "Mark matched messages with: " t)))
+ (mu4e-headers-mark-for-each-if markpair (car pred) param)))
+
+(defun mu4e~headers-get-thread-info (msg what)
+ "Get WHAT (a symbol, either path or thread-id) for MSG."
+ (let* ((thread (or (mu4e-message-field msg :thread)
+ (mu4e-error "No thread info found")))
+ (path (or (plist-get thread :path)
+ (mu4e-error "No threadpath found"))))
+ (case what
+ (path path)
+ (thread-id
+ (save-match-data
+ ;; the thread id is the first segment of the thread path
+ (when (string-match "^\\([[:xdigit:]]+\\):?" path)
+ (match-string 1 path))))
+ (otherwise (mu4e-error "Not supported")))))
+
+
+(defun mu4e-headers-mark-thread-using-markpair (markpair &optional subthread)
+ "Mark the thread at point using the given markpair. If SUBTHREAD is
+non-nil, marking is limited to the message at point and its
+descendants."
+ (let* ((mark (car markpair))
+ (allowed-marks (mapcar 'car mu4e-marks)))
+ (unless (memq mark allowed-marks)
+ (mu4e-error "The mark (%s) has to be one of: %s"
+ mark allowed-marks)))
+ ;; note: the tread id is shared by all messages in a thread
+ (let* ((msg (mu4e-message-at-point))
+ (thread-id (mu4e~headers-get-thread-info msg 'thread-id))
+ (path (mu4e~headers-get-thread-info msg 'path))
+ (last-marked-point))
+ (mu4e-headers-for-each
+ (lambda (mymsg)
+ (let ((my-thread-id (mu4e~headers-get-thread-info mymsg 'thread-id)))
+ (if subthread
+ ;; subthread matching; mymsg's thread path should have path as its
+ ;; prefix
+ (when (string-match (concat "^" path)
+ (mu4e~headers-get-thread-info mymsg 'path))
+ (mu4e-mark-at-point (car markpair) (cdr markpair))
+ (setq last-marked-point (point)))
+ ;; nope; not looking for the subthread; looking for the whole thread
+ (when (string= thread-id
+ (mu4e~headers-get-thread-info mymsg 'thread-id))
+ (mu4e-mark-at-point (car markpair) (cdr markpair))
+ (setq last-marked-point (point)))))))
+ (when last-marked-point
+ (goto-char last-marked-point)
+ (mu4e-headers-next))))
+
+(defun mu4e-headers-mark-thread (&optional subthread markpair)
+ "Like `mu4e-headers-mark-thread-using-markpair' but prompt for the markpair."
+ (interactive
+ (let* ((subthread current-prefix-arg))
+ (list current-prefix-arg
+ ;; FIXME: e.g., for refiling we should evaluate this
+ ;; for each line separately
+ (mu4e~mark-get-markpair
+ (if subthread "Mark subthread with: " "Mark whole thread with: ") t))))
+ (mu4e-headers-mark-thread-using-markpair markpair subthread))
+
+(defun mu4e-headers-mark-subthread (&optional markpair)
+ "Like `mu4e-mark-thread', but only for a sub-thread."
+ (interactive)
+ (if markpair (mu4e-headers-mark-thread t markpair)
+ (let ((current-prefix-arg t))
+ (call-interactively 'mu4e-headers-mark-thread))))
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+
+;;; the query past / present / future ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+(defvar mu4e~headers-query-past nil
+ "Stack of queries before the present one.")
+(defvar mu4e~headers-query-future nil
+ "Stack of queries after the present one.")
+(defvar mu4e~headers-query-stack-size 20
+ "Maximum size for the query stacks.")
+
+(defun mu4e~headers-push-query (query where)
+ "Push QUERY to one of the query stacks.
+WHERE is a symbol telling us where to push; it's a symbol, either
+'future or 'past. Functional also removes duplicats, limits the
+stack size."
+ (let ((stack
+ (case where
+ (past mu4e~headers-query-past)
+ (future mu4e~headers-query-future))))
+ ;; only add if not the same item
+ (unless (and stack (string= (car stack) query))
+ (push query stack)
+ ;; limit the stack to `mu4e~headers-query-stack-size' elements
+ (when (> (length stack) mu4e~headers-query-stack-size)
+ (setq stack (subseq stack 0 mu4e~headers-query-stack-size)))
+ ;; remove all duplicates of the new element
+ (remove-if (lambda (elm) (string= elm (car stack))) (cdr stack))
+ ;; update the stacks
+ (case where
+ (past (setq mu4e~headers-query-past stack))
+ (future (setq mu4e~headers-query-future stack))))))
+
+(defun mu4e~headers-pop-query (whence)
+ "Pop a query from the stack.
+WHENCE is a symbol telling us where to get it from, either `future'
+or `past'."
+ (case whence
+ (past
+ (unless mu4e~headers-query-past
+ (mu4e-warn "No more previous queries"))
+ (pop mu4e~headers-query-past))
+ (future
+ (unless mu4e~headers-query-future
+ (mu4e-warn "No more next queries"))
+ (pop mu4e~headers-query-future))))
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+
+;;; interactive functions ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+(defvar mu4e~headers-search-hist nil
+ "History list of searches.")
+
+(defvar mu4e~headers-msgid-target nil
+ "Message-id to jump to after the search has finished.")
+
+(defvar mu4e~headers-view-target nil
+ "Whether to automatically view (open) the target message (as
+ per `mu4e~headers-msgid-target').")
+
+(defun mu4e-headers-search (&optional expr prompt edit ignore-history msgid show)
+ "Search in the mu database for EXPR, and switch to the output
+buffer for the results. This is an interactive function which ask
+user for EXPR. PROMPT, if non-nil, is the prompt used by this
+function (default is \"Search for:\"). If EDIT is non-nil,
+instead of executing the query for EXPR, let the user edit the
+query before executing it. If IGNORE-HISTORY is true, do *not*
+update the query history stack. If MSGID is non-nil, attempt to
+move point to the first message with that message-id after
+searching. If SHOW is non-nil, show the message with MSGID."
+ ;; note: we don't want to update the history if this query comes from
+ ;; `mu4e~headers-query-next' or `mu4e~headers-query-prev'."
+ (interactive)
+ (let* ((prompt (mu4e-format (or prompt "Search for: ")))
+ (expr
+ (if edit
+ (read-string prompt expr)
+ (or expr
+ (read-string prompt nil 'mu4e~headers-search-hist)))))
+ (mu4e-mark-handle-when-leaving)
+ (mu4e~headers-search-execute expr ignore-history)
+ (setq mu4e~headers-msgid-target msgid
+ mu4e~headers-view-target show)))
+
+(defun mu4e-headers-search-edit ()
+ "Edit the last search expression."
+ (interactive)
+ (mu4e-headers-search mu4e~headers-last-query nil t))
+
+(defun mu4e-headers-search-bookmark (&optional expr edit)
+ "Search using some bookmarked query EXPR.
+If EDIT is non-nil, let the user edit the bookmark before starting
+the search."
+ (interactive)
+ (let ((expr
+ (or expr
+ (mu4e-ask-bookmark (if edit "Select bookmark: " "Bookmark: ")))))
+ (run-hook-with-args 'mu4e-headers-search-bookmark-hook expr)
+ (mu4e-headers-search expr (when edit "Edit bookmark: ") edit)))
+
+(defun mu4e-headers-search-bookmark-edit ()
+ "Edit an existing bookmark before executing it."
+ (interactive)
+ (mu4e-headers-search-bookmark nil t))
+
+
+(defun mu4e-headers-search-narrow (filter )
+ "Narrow the last search by appending search expression FILTER to
+the last search expression. Note that you can go back to previous
+query (effectively, 'widen' it), with `mu4e-headers-query-prev'."
+ (interactive
+ (let ((filter
+ (read-string (mu4e-format "Narrow down to: ")
+ nil 'mu4e~headers-search-hist nil t)))
+ (list filter)))
+ (unless mu4e~headers-last-query
+ (mu4e-warn "There's nothing to filter"))
+ (mu4e-headers-search
+ (format "(%s) AND (%s)" mu4e~headers-last-query filter)))
+
+
+(defun mu4e-headers-change-sorting (&optional field dir)
+ "Change the sorting/threading parameters.
+FIELD is the field to sort by; DIR is a symbol: either 'ascending,
+'descending, 't (meaning: if FIELD is the same as the current
+sortfield, change the sort-order) or nil (ask the user)."
+ (interactive)
+ (let* ((field
+ (or field
+ (mu4e-read-option "Sortfield: " mu4e~headers-sort-field-choices)))
+ ;; note: 'sortable' is either a boolean (meaning: if non-nil, this is
+ ;; sortable field), _or_ another field (meaning: sort by this other field).
+ (sortable (plist-get (cdr (assoc field mu4e-header-info)) :sortable))
+ ;; error check
+ (sortable
+ (if sortable
+ sortable
+ (mu4e-error "Not a sortable field")))
+ (sortfield (if (booleanp sortable) field sortable))
+ (dir
+ (case dir
+ ((ascending descending) dir)
+ ;; change the sort order if field = curfield
+ (t
+ (if (eq sortfield mu4e-headers-sort-field)
+ (if (eq mu4e-headers-sort-direction 'ascending)
+ 'descending 'ascending)
+ 'descending))
+ (mu4e-read-option "Direction: "
+ '(("ascending" . 'ascending) ("descending" . 'descending))))))
+ (setq
+ mu4e-headers-sort-field sortfield
+ mu4e-headers-sort-direction dir)
+ (mu4e-message "Sorting by %s (%s)"
+ (symbol-name sortfield)
+ (symbol-name mu4e-headers-sort-direction))
+ (mu4e-headers-rerun-search)))
+
+(defun mu4e~headers-toggle (name togglevar dont-refresh)
+ "Toggle variable TOGGLEVAR for feature NAME. Unless DONT-REFRESH is non-nil,
+re-run the last search."
+ (set togglevar (not (symbol-value togglevar)))
+ (mu4e-message "%s turned %s%s"
+ name
+ (if (symbol-value togglevar) "on" "off")
+ (if dont-refresh
+ " (press 'g' to refresh)" ""))
+ (unless dont-refresh
+ (mu4e-headers-rerun-search)))
+
+(defun mu4e-headers-toggle-threading (&optional dont-refresh)
+ "Toggle `mu4e-headers-show-threads'. With prefix-argument, do
+_not_ refresh the last search with the new setting for threading."
+ (interactive "P")
+ (mu4e~headers-toggle "Threading" 'mu4e-headers-show-threads dont-refresh))
+
+(defun mu4e-headers-toggle-full-search (&optional dont-refresh)
+ "Toggle `mu4e-headers-full-search'. With prefix-argument, do
+_not_ refresh the last search with the new setting for threading."
+ (interactive "P")
+ (mu4e~headers-toggle "Full-search"
+ 'mu4e-headers-full-search dont-refresh))
+
+(defun mu4e-headers-toggle-include-related (&optional dont-refresh)
+ "Toggle `mu4e-headers-include-related'. With prefix-argument, do
+_not_ refresh the last search with the new setting for threading."
+ (interactive "P")
+ (mu4e~headers-toggle "Include-related"
+ 'mu4e-headers-include-related dont-refresh))
+
+(defun mu4e-headers-toggle-skip-duplicates (&optional dont-refresh)
+ "Toggle `mu4e-headers-skip-duplicates'. With prefix-argument, do
+_not_ refresh the last search with the new setting for threading."
+ (interactive "P")
+ (mu4e~headers-toggle "Skip-duplicates"
+ 'mu4e-headers-skip-duplicates dont-refresh))
+
+(defvar mu4e~headers-loading-buf nil
+ "A buffer for loading a message view.")
+
+(defun mu4e~headers-get-loading-buf ()
+ "Get a buffer to give feedback while loading a message view."
+ (unless (buffer-live-p mu4e~headers-loading-buf)
+ (setq mu4e~headers-loading-buf
+ (get-buffer-create " *mu4e-loading*")))
+ (with-current-buffer mu4e~headers-loading-buf
+ (let ((inhibit-read-only t))
+ (erase-buffer)
+ (local-set-key (kbd "q") 'kill-buffer-and-window)
+ (insert (propertize "Waiting for message..."
+ 'face 'mu4e-system-face 'intangible t))))
+ mu4e~headers-loading-buf)
+
+(defun mu4e-headers-view-message ()
+ "View message at point.
+If there's an existing window for the view, re-use that one. If
+not, create a new one, depending on the value of
+`mu4e-split-view': if it's a symbol `horizontal' or `vertical',
+split the window accordingly; if it is nil, replace the current
+window. "
+ (interactive)
+ (unless (eq major-mode 'mu4e-headers-mode)
+ (mu4e-error "Must be in mu4e-headers-mode (%S)" major-mode))
+ (let* ((msg (mu4e-message-at-point))
+ (docid (or (mu4e-message-field msg :docid)
+ (mu4e-warn "No message at point")))
+ ;; 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)))
+ (viewwin (mu4e~headers-redraw-get-view-window)))
+ (unless (window-live-p viewwin)
+ (mu4e-error "Cannot get a message view"))
+ (select-window viewwin)
+ (switch-to-buffer (mu4e~headers-get-loading-buf))
+ (mu4e~proc-view docid mu4e-view-show-images decrypt)))
+
+(defun mu4e-headers-rerun-search ()
+ "Rerun the search for the last search expression."
+ (interactive)
+ ;; if possible, try to return to the same message
+ (let* ((msg (mu4e-message-at-point))
+ (msgid (and msg (mu4e-message-field msg :message-id))))
+ (mu4e-headers-search mu4e~headers-last-query nil nil t msgid)))
+
+(defun mu4e~headers-query-navigate (whence)
+ "Execute the previous query from the query stacks.
+WHENCE determines where the query is taken from and is a symbol,
+either `future' or `past'."
+ (let ((query (mu4e~headers-pop-query whence))
+ (where (if (eq whence 'future) 'past 'future)))
+ (when query
+ (mu4e~headers-push-query mu4e~headers-last-query where)
+ (mu4e-headers-search query nil nil t))))
+
+(defun mu4e-headers-query-next ()
+ "Execute the previous query from the query stacks."
+ (interactive)
+ (mu4e~headers-query-navigate 'future))
+
+(defun mu4e-headers-query-prev ()
+ "Execute the previous query from the query stacks."
+ (interactive)
+ (mu4e~headers-query-navigate 'past))
+
+;; forget the past so we don't repeat it :/
+(defun mu4e-headers-forget-queries ()
+ "Forget all the complete query history."
+ (interactive)
+ (setq ;; note: don't forget the present one
+ mu4e~headers-query-past nil
+ mu4e~headers-query-future nil)
+ (mu4e-message "Query history cleared"))
+
+(defun mu4e~headers-move (lines)
+ "Move point LINES lines forward (if LINES is positive) or
+backward (if LINES is negative). If this succeeds, return the new
+docid. Otherwise, return nil."
+ (unless (eq major-mode 'mu4e-headers-mode)
+ (mu4e-error "Must be in mu4e-headers-mode (%S)" major-mode))
+ (let ((succeeded (zerop (forward-line lines)))
+ (docid (mu4e~headers-docid-at-point)))
+ ;; move point, even if this function is called when this window is not
+ ;; visible
+ (when docid
+ ;; update all windows showing the headers buffer
+ (walk-windows
+ (lambda (win)
+ (when (eq (window-buffer win) mu4e~headers-buffer)
+ (set-window-point win (point))))
+ nil t)
+ ;;(set-window-point (get-buffer-window mu4e~headers-buffer t) (point))
+ ;; attempt to highlight the new line, display the message
+ (mu4e~headers-highlight docid)
+ ;; update message view if it was already showing
+ (when (and mu4e-split-view (window-live-p mu4e~headers-view-win))
+ (mu4e-headers-view-message))
+ docid)))
+
+(defun mu4e-headers-next (&optional n)
+ "Move point to the next message header.
+If this succeeds, return the new docid. Otherwise, return nil.
+Optionally, takes an integer N (prefix argument), to the Nth next
+header."
+ (interactive "P")
+ (mu4e~headers-move (or n 1)))
+
+(defun mu4e-headers-prev (&optional n)
+ "Move point to the previous message header.
+If this succeeds, return the new docid. Otherwise, return nil.
+Optionally, takes an integer N (prefix argument), to the Nth
+previous header."
+ (interactive "P")
+ (mu4e~headers-move (- (or n 1))))
+
+(defun mu4e~headers-prev-or-next-unread (backwards)
+ "Move point to the next message that is unread (and
+untrashed). If BACKWARDS is non-`nil', move backwards."
+ (interactive)
+ (or (mu4e-headers-find-if-next
+ (lambda (msg)
+ (let ((flags (mu4e-message-field msg :flags)))
+ (and (member 'unread flags) (not (member 'trashed flags)))))
+ backwards)
+ (mu4e-message (format "No %s unread message found"
+ (if backwards "previous" "next")))))
+
+(defun mu4e-headers-prev-unread ()
+ "Move point to the previous message that is unread (and
+untrashed)."
+ (interactive)
+ (mu4e~headers-prev-or-next-unread t))
+
+(defun mu4e-headers-next-unread ()
+ "Move point to the next message that is unread (and
+untrashed)."
+ (interactive)
+ (mu4e~headers-prev-or-next-unread nil))
+
+(defun mu4e~headers-jump-to-maildir (maildir)
+ "Show the messages in maildir (user is prompted to ask what
+maildir)."
+ (interactive
+ (let ((maildir (mu4e-ask-maildir "Jump to maildir: ")))
+ (list maildir)))
+ (when maildir
+ (mu4e-mark-handle-when-leaving)
+ (mu4e-headers-search
+ (format "maildir:\"%s\"" maildir))))
+
+(defun mu4e-headers-split-view-grow (&optional n)
+ "In split-view, grow the headers window.
+In horizontal split-view, increase the number of lines shown by N.
+In vertical split-view, increase the number of columns shown by N.
+If N is negative shrink the headers window. When not in split-view
+do nothing."
+ (interactive "P")
+ (let ((n (or n 1))
+ (hwin (get-buffer-window mu4e~headers-buffer)))
+ (when (and (buffer-live-p mu4e~view-buffer) (window-live-p hwin))
+ (let ((n (or n 1)))
+ (case mu4e-split-view
+ ;; emacs has weird ideas about what horizontal, vertical means...
+ (horizontal
+ (window-resize hwin n nil)
+ (incf mu4e-headers-visible-lines n))
+ (vertical
+ (window-resize hwin n t)
+ (incf mu4e-headers-visible-columns n)))))))
+
+(defun mu4e-headers-split-view-shrink (&optional n)
+ "In split-view, shrink the headers window.
+In horizontal split-view, decrease the number of lines shown by N.
+In vertical split-view, decrease the number of columns shown by N.
+If N is negative grow the headers window. When not in split-view
+do nothing."
+ (interactive "P")
+ (mu4e-headers-split-view-grow (- (or n 1))))
+
+(defun mu4e-headers-action (&optional actionfunc)
+ "Ask user what to do with message-at-point, then do it.
+The actions are specified in `mu4e-headers-actions'. Optionally,
+pass ACTIONFUNC, which is a function that takes a msg-plist
+argument."
+ (interactive)
+ (let ((msg (mu4e-message-at-point))
+ (afunc (or actionfunc (mu4e-read-option "Action: " mu4e-headers-actions))))
+ (funcall afunc msg)))
+
+(defun mu4e-headers-mark-and-next (mark)
+ "Set mark MARK on the message at point or on all messages in the
+region if there is a region, then move to the next message."
+ (interactive)
+ (mu4e-mark-set mark)
+ (mu4e-headers-next))
+
+(defun mu4e~headers-quit-buffer ()
+ "Quit the mu4e-headers buffer.
+This is a rather complex function, to ensure we don't disturb
+other windows."
+ (interactive)
+ (unless (eq major-mode 'mu4e-headers-mode)
+ (mu4e-error "Must be in mu4e-headers-mode (%S)" major-mode))
+ (mu4e-mark-handle-when-leaving)
+ (let ((curbuf (current-buffer)) (curwin (selected-window))
+ (headers-visible))
+ (walk-windows
+ (lambda (win)
+ (with-selected-window win
+ ;; if we the view window connected to this one, kill it
+ (when (and (not (one-window-p win)) (eq mu4e~headers-view-win win))
+ (delete-window win)
+ (setq mu4e~headers-view-win nil)))
+ ;; and kill any _other_ (non-selected) window that shows the current
+ ;; buffer
+ (when (and
+ (eq curbuf (window-buffer win)) ;; does win show curbuf?
+ (not (eq curwin win)) ;; it's not the curwin?
+ (not (one-window-p))) ;; and not the last one?
+ (delete-window win)))) ;; delete it!
+ ;; now, all *other* windows should be gone. kill ourselves, and return
+ ;; to the main view
+ (kill-buffer)
+ (mu4e~main-view)))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+(provide 'mu4e-headers)
diff --git a/_spacemacs.d/local/mu4e/mu4e-lists.el b/_spacemacs.d/local/mu4e/mu4e-lists.el
new file mode 100644
index 0000000..00884da
--- /dev/null
+++ b/_spacemacs.d/local/mu4e/mu4e-lists.el
@@ -0,0 +1,93 @@
+;;; mu4e-lists.el -- part of mu4e, the mu mail user agent
+;;
+;; Copyright (C) 2011-2016 Dirk-Jan C. Binnema
+
+;; Author: Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
+;; Maintainer: Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
+
+;; This file is not part of GNU Emacs.
+;;
+;; 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 <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; In this file, we create a table of list-id -> shortname for mailing lists.
+;; The shortname (friendly) should a at most 8 characters, camel-case
+
+
+(defvar mu4e~mailing-lists
+ '( ("bbdb-info.lists.sourceforge.net" . "BBDB")
+ ("boost-announce.lists.boost.org" . "BoostA")
+ ("boost-interest.lists.boost.org" . "BoostI")
+ ("conkeror.mozdev.org" . "Conkeror")
+ ("curl-library.cool.haxx.se" . "LibCurl")
+ ("crypto-gram-list.schneier.com " . "CryptoGr")
+ ("dbus.lists.freedesktop.org" . "DBus")
+ ("desktop-devel-list.gnome.org" . "GnomeDT")
+ ("emacs-devel.gnu.org" . "EmacsDev")
+ ("emacs-orgmode.gnu.org" . "Orgmode")
+ ("emms-help.gnu.org" . "Emms")
+ ("enlightenment-devel.lists.sourceforge.net" . "E-Dev")
+ ("erlang-questions.erlang.org" . "Erlang")
+ ("evolution-hackers.lists.ximian.com" . "EvoDev")
+ ("farsight-devel.lists.sourceforge.net" . "Farsight")
+ ("mailman.lists.freedesktop.org" . "FDeskTop")
+ ("gcc-help.gcc.gnu.org" . "Gcc")
+ ("gmime-devel-list.gnome.org" . "GMimeDev")
+ ("gnome-shell-list.gnome.org" . "GnomeSh")
+ ("gnu-emacs-sources.gnu.org" . "EmacsSrc")
+ ("gnupg-users.gnupg.org" . "GnupgU")
+ ("gpe.handhelds.org" . "GPE")
+ ("gstreamer-devel.lists.freedesktop.org" . "GstDev")
+ ("gstreamer-devel.lists.sourceforge.net" . "GstDev")
+ ("gstreamer-openmax.lists.sourceforge.net" . "GstOmx")
+ ("gtk-devel-list.gnome.org" . "GtkDev")
+ ("gtkmm-list.gnome.org" . "GtkmmDev")
+ ("guile-devel.gnu.org" . "GuileDev")
+ ("guile-user.gnu.org" . "GuileUsr")
+ ("help-gnu-emacs.gnu.org" . "EmacsUsr")
+ ("lggdh-algemeen.vvtp.tudelft.nl" . "LGGDH")
+ ("linux-bluetooth.vger.kernel.org" . "Bluez")
+ ("maemo-developers.maemo.org" . "MaemoDev")
+ ("maemo-users.maemo.org" . "MaemoUsr")
+ ("monit-general.nongnu.org" . "Monit")
+ ("mu-discuss.googlegroups.com" . "Mu")
+ ("nautilus-list.gnome.org" . "Nautilus")
+ ("notmuch.notmuchmail.org" . "Notmuch")
+ ("orbit-list.gnome.org" . "ORBit")
+ ("pulseaudio-discuss.lists.freedesktop.org" . "PulseA")
+ ("sqlite-announce.sqlite.org" . "SQliteAnn")
+ ("sqlite-dev.sqlite.org" . "SQLiteDev")
+ ("sup-talk.rubyforge.org" . "Sup")
+ ("sylpheed-claws-users.lists.sourceforge.net" . "Sylpheed")
+ ("tinymail-devel-list.gnome.org" . "Tinymail")
+ ("unicode.sarasvati.unicode.org" . "Unicode")
+ ("xapian-discuss.lists.xapian.org" . "Xapian")
+ ("xdg.lists.freedesktop.org" . "XDG")
+ ("wl-en.lists.airs.net" . "Wdrlust")
+ ("wl-en.ml.gentei.org" . "WdrLust")
+ ("xapian-devel.lists.xapian.org" . "Xapian")
+ ("zsh-users.zsh.org" . "ZshUsr"))
+ "AList of cells (MAILING-LIST-ID . SHORTNAME)")
+
+(defvar mu4e-user-mailing-lists nil
+ "An alist with cells (MAILING-LIST-ID . SHORTNAME); these are
+used in addition to the built-in list `mu4e~mailing-lists'.")
+
+(defvar mu4e-mailing-list-patterns nil
+ "A list of regex patterns to capture a shortname out of a list
+ID. For the first regex that matches, its first matchgroup will
+be used as the shortname.")
+
+(provide 'mu4e-lists)
diff --git a/_spacemacs.d/local/mu4e/mu4e-main.el b/_spacemacs.d/local/mu4e/mu4e-main.el
new file mode 100644
index 0000000..0f4dc78
--- /dev/null
+++ b/_spacemacs.d/local/mu4e/mu4e-main.el
@@ -0,0 +1,225 @@
+;;; mu4e-main.el -- part of mu4e, the mu mail user agent
+;;
+;; Copyright (C) 2011-2016 Dirk-Jan C. Binnema
+
+;; Author: Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
+;; Maintainer: Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
+
+;; This file is not part of GNU Emacs.
+;;
+;; 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 <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;;; Code:
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+(require 'smtpmail) ;; the queing stuff (silence elint)
+(require 'mu4e-utils) ;; utility functions
+(require 'mu4e-context) ;; the context
+
+
+(defconst mu4e~main-buffer-name " *mu4e-main*"
+ "*internal* Name of the mu4e main view buffer.")
+
+(defvar mu4e-main-mode-map
+ (let ((map (make-sparse-keymap)))
+
+ (define-key map "b" 'mu4e-headers-search-bookmark)
+ (define-key map "B" 'mu4e-headers-search-bookmark-edit)
+
+ (define-key map "s" 'mu4e-headers-search)
+ (define-key map "q" 'mu4e-quit)
+ (define-key map "j" 'mu4e~headers-jump-to-maildir)
+ (define-key map "C" 'mu4e-compose-new)
+
+ (define-key map "m" 'mu4e~main-toggle-mail-sending-mode)
+ (define-key map "f" 'smtpmail-send-queued-mail)
+
+ ;;
+ (define-key map "U" 'mu4e-update-mail-and-index)
+ (define-key map (kbd "C-S-u") 'mu4e-update-mail-and-index)
+ ;; for terminal users
+ (define-key map (kbd "C-c C-u") 'mu4e-update-mail-and-index)
+
+ (define-key map "S" 'mu4e-interrupt-update-mail)
+ (define-key map (kbd "C-S-u") 'mu4e-update-mail-and-index)
+ (define-key map ";" 'mu4e-context-switch)
+
+ (define-key map "$" 'mu4e-show-log)
+ (define-key map "A" 'mu4e-about)
+ (define-key map "N" 'mu4e-news)
+ (define-key map "H" 'mu4e-display-manual)
+ map)
+
+ "Keymap for the *mu4e-main* buffer.")
+(fset 'mu4e-main-mode-map mu4e-main-mode-map)
+
+(defvar mu4e-main-mode-abbrev-table nil)
+(define-derived-mode mu4e-main-mode special-mode "mu4e:main"
+ "Major mode for the mu4e main screen.
+\\{mu4e-main-mode-map}."
+ (use-local-map mu4e-main-mode-map)
+ (setq
+ truncate-lines t
+ overwrite-mode 'overwrite-mode-binary)
+
+ ;; show context in mode-string
+ (set (make-local-variable 'global-mode-string) '(:eval (mu4e-context-label)))
+ (set (make-local-variable 'revert-buffer-function) #'mu4e~main-view-real))
+
+
+(defun mu4e~main-action-str (str &optional func-or-shortcut)
+ "Highlight the first occurence of [.] in STR.
+If FUNC-OR-SHORTCUT is non-nil and if it is a function, call it
+when STR is clicked (using RET or mouse-2); if FUNC-OR-SHORTCUT is
+a string, execute the corresponding keyboard action when it is
+clicked."
+ (let ((newstr
+ (replace-regexp-in-string
+ "\\[\\(..?\\)\\]"
+ (lambda(m)
+ (format "[%s]"
+ (propertize (match-string 1 m) 'face 'mu4e-highlight-face)))
+ str))
+ (map (make-sparse-keymap))
+ (func (if (functionp func-or-shortcut)
+ func-or-shortcut
+ (if (stringp func-or-shortcut)
+ (lexical-let ((macro func-or-shortcut))
+ (lambda()(interactive)
+ (execute-kbd-macro macro)))))))
+ (define-key map [mouse-2] func)
+ (define-key map (kbd "RET") func)
+ (put-text-property 0 (length newstr) 'keymap map newstr)
+ (put-text-property (string-match "\\[.+$" newstr)
+ (- (length newstr) 1) 'mouse-face 'highlight newstr) newstr))
+
+;; NEW
+;; This is the old `mu4e~main-view' function but without
+;; buffer switching at the end.
+(defun mu4e~main-view-real (ignore-auto noconfirm)
+ (let ((buf (get-buffer-create mu4e~main-buffer-name))
+ (inhibit-read-only t))
+ (with-current-buffer buf
+ (erase-buffer)
+ (insert
+ "* "
+ (propertize "mu4e - mu for emacs version " 'face 'mu4e-title-face)
+ (propertize mu4e-mu-version 'face 'mu4e-header-key-face)
+
+ ;; show some server properties; in this case; a big C when there's
+ ;; crypto support, a big G when there's Guile support
+ " "
+ (propertize
+ (concat
+ (when (plist-get mu4e~server-props :crypto) "C")
+ (when (plist-get mu4e~server-props :guile) "G"))
+ 'face 'mu4e-title-face)
+
+ "\n\n"
+ (propertize " Basics\n\n" 'face 'mu4e-title-face)
+ (mu4e~main-action-str "\t* [j]ump to some maildir\n" 'mu4e-jump-to-maildir)
+ (mu4e~main-action-str "\t* enter a [s]earch query\n" 'mu4e-search)
+ (mu4e~main-action-str "\t* [C]ompose a new message\n" 'mu4e-compose-new)
+ "\n"
+ (propertize " Bookmarks\n\n" 'face 'mu4e-title-face)
+ ;; TODO: it's a bit uncool to hard-code the "b" shortcut...
+ (mapconcat
+ (lambda (bm)
+ (let* ((query (nth 0 bm)) (title (nth 1 bm)) (key (nth 2 bm)))
+ (mu4e~main-action-str
+ (concat "\t* [b" (make-string 1 key) "] " title)
+ (concat "b" (make-string 1 key)))))
+ mu4e-bookmarks "\n")
+ "\n\n"
+ (propertize " Misc\n\n" 'face 'mu4e-title-face)
+
+ (mu4e~main-action-str "\t* [;]Switch focus\n" 'mu4e-context-switch)
+
+ (mu4e~main-action-str "\t* [U]pdate email & database\n"
+ 'mu4e-update-mail-and-index)
+
+ ;; show the queue functions if `smtpmail-queue-dir' is defined
+ (if (file-directory-p smtpmail-queue-dir)
+ (mu4e~main-view-queue)
+ "")
+ "\n"
+ (mu4e~main-action-str "\t* [N]ews\n" 'mu4e-news)
+ (mu4e~main-action-str "\t* [A]bout mu4e\n" 'mu4e-about)
+ (mu4e~main-action-str "\t* [H]elp\n" 'mu4e-display-manual)
+ (mu4e~main-action-str "\t* [q]uit\n" 'mu4e-quit))
+ (mu4e-main-mode)
+ )))
+
+(defun mu4e~main-view-queue ()
+ "Display queue-related actions in the main view."
+ (concat
+ (mu4e~main-action-str "\t* toggle [m]ail sending mode "
+ 'mu4e~main-toggle-mail-sending-mode)
+ "(currently "
+ (propertize (if smtpmail-queue-mail "queued" "direct")
+ 'face 'mu4e-header-key-face)
+ ")\n"
+ (let ((queue-size (mu4e~main-queue-size)))
+ (if (zerop queue-size)
+ ""
+ (mu4e~main-action-str
+ (format "\t* [f]lush %s queued %s\n"
+ (propertize (int-to-string queue-size)
+ 'face 'mu4e-header-key-face)
+ (if (> queue-size 1) "mails" "mail"))
+ 'smtpmail-send-queued-mail)))))
+
+(defun mu4e~main-queue-size ()
+ "Return, as an int, the number of emails in the queue."
+ (condition-case nil
+ (with-temp-buffer
+ (insert-file-contents (expand-file-name smtpmail-queue-index-file
+ smtpmail-queue-dir))
+ (count-lines (point-min) (point-max)))
+ (error 0)))
+
+(defun mu4e~main-view ()
+ "Create the mu4e main-view, and switch to it."
+ (mu4e~main-view-real nil nil)
+ (switch-to-buffer mu4e~main-buffer-name)
+ (goto-char (point-min))
+ (setq global-mode-string '(:eval (mu4e-context-label))))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; Interactive functions
+;; NEW
+;; Toggle mail sending mode without switching
+(defun mu4e~main-toggle-mail-sending-mode ()
+ "Toggle sending mail mode, either queued or direct."
+ (interactive)
+ (let ((curpos (point)))
+ (unless (file-directory-p smtpmail-queue-dir)
+ (mu4e-error "`smtpmail-queue-dir' does not exist"))
+ (setq smtpmail-queue-mail (not smtpmail-queue-mail))
+ (message
+ (concat "Outgoing mail will now be "
+ (if smtpmail-queue-mail "queued" "sent directly")))
+ (mu4e~main-view-real nil nil)
+ (goto-char curpos)))
+
+
+;; (progn
+;; (define-key mu4e-compose-mode-map (kbd "C-c m") 'mu4e~main-toggle-mail-sending-mode)
+;; (define-key mu4e-view-mode-map (kbd "C-c m") 'mu4e~main-toggle-mail-sending-mode)
+;; (define-key mu4e-compose-mode-map (kbd "C-c m") 'mu4e~main-toggle-mail-sending-mode)
+;; (define-key mu4e-headers-mode-map (kbd "C-c m") 'mu4e~main-toggle-mail-sending-mode)
+;; )
+
+(provide 'mu4e-main)
diff --git a/_spacemacs.d/local/mu4e/mu4e-mark.el b/_spacemacs.d/local/mu4e/mu4e-mark.el
new file mode 100644
index 0000000..4b17f34
--- /dev/null
+++ b/_spacemacs.d/local/mu4e/mu4e-mark.el
@@ -0,0 +1,466 @@
+;; mu4e-mark.el -- part of mu4e, the mu mail user agent
+;;
+;; Copyright (C) 2011-2016 Dirk-Jan C. Binnema
+
+;; Author: Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
+;; Maintainer: Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
+
+;; This file is not part of GNU Emacs.
+;;
+;; 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 <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; In this file are function related to marking messages; they assume we are
+;; currently in the headers buffer.
+
+;; Code:
+(require 'mu4e-proc)
+(require 'mu4e-utils)
+(require 'mu4e-message)
+
+(eval-when-compile (byte-compile-disable-warning 'cl-functions))
+
+;; keep byte-compiler happy
+(declare-function mu4e~headers-mark "mu4e-headers")
+(declare-function mu4e~headers-goto-docid "mu4e-headers")
+(declare-function mu4e-headers-next "mu4e-headers")
+
+
+(defcustom mu4e-headers-leave-behavior 'ask
+ "What to do when user leaves the headers view.
+That is when he e.g. quits, refreshes or does a new search.
+Value is one of the following symbols:
+- `ask' ask user whether to ignore the marks
+- `apply' automatically apply the marks before doing anything else
+- `ignore' automatically ignore the marks without asking"
+ :type '(choice (const ask :tag "ask user whether to ignore marks")
+ (const apply :tag "apply marks without asking")
+ (const ignore :tag "ignore marks without asking"))
+ :group 'mu4e-headers)
+
+(defcustom mu4e-mark-execute-pre-hook nil
+ "Hook run just *before* a mark is applied to a message. The hook function
+is called with two arguments, the mark being executed and the message itself.")
+
+(defvar mu4e-headers-show-target t
+ "Whether to show targets (such as '-> delete', '-> /archive')
+when marking message. Normally, this is useful information for the
+user, however, when you often mark large numbers (thousands) of
+message, showing the target makes this quite a bit slower (showing
+the target uses an emacs feature called 'overlays', which aren't
+particularly fast).")
+
+;;; insert stuff;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+(defvar mu4e~mark-map nil
+ "Map (hash) of docid->markinfo; when a message is marked, the
+information is added here.
+markinfo is a cons cell consisting of the following:
+\(mark . target)
+where
+ MARK is the type of mark (move, trash, delete)
+ TARGET (optional) is the target directory (for 'move')")
+
+;; the mark-map is specific for the current header buffer
+;; currently, there can't be more than one, but we never know what will
+;; happen in the future
+
+;; the fringe is the space on the left of headers, where we put marks below some
+;; handy definitions; only `mu4e-mark-fringe-len' should be change (if ever),
+;; the others follow from that.
+(defconst mu4e~mark-fringe-len 2
+ "Width of the fringe for marks on the left.")
+(defconst mu4e~mark-fringe (make-string mu4e~mark-fringe-len ?\s)
+ "The space on the left of message headers to put marks.")
+(defconst mu4e~mark-fringe-format (format "%%-%ds" mu4e~mark-fringe-len)
+ "Format string to set a mark and leave remaining space.")
+
+(defun mu4e~mark-initialize ()
+ "Initialize the marks subsystem."
+ (set (make-local-variable 'mu4e~mark-map) (make-hash-table)))
+
+(defun mu4e~mark-clear ()
+ "Clear the marks subsystem."
+ (clrhash mu4e~mark-map))
+
+(defun mu4e~mark-find-headers-buffer ()
+ "Find the headers buffer, if any."
+ (find-if
+ (lambda (b)
+ (with-current-buffer b
+ (eq major-mode 'mu4e-headers-mode)))
+ (buffer-list)))
+
+(defmacro mu4e~mark-in-context (&rest body)
+ "Evaluate BODY in the context of the headers buffer in case this
+is either a headers or view buffer."
+ `(cond
+ ((eq major-mode 'mu4e-headers-mode) ,@body)
+ ((eq major-mode 'mu4e-view-mode)
+ (when (buffer-live-p mu4e~view-headers-buffer)
+ (let* ((msg (mu4e-message-at-point))
+ (docid (mu4e-message-field msg :docid)))
+ (with-current-buffer mu4e~view-headers-buffer
+ (if (mu4e~headers-goto-docid docid)
+ ,@body
+ (mu4e-error "cannot find message in headers buffer."))))))
+ (t
+ ;; even in other modes (e.g. mu4e-main-mode we try to find
+ ;; the headers buffer
+ (let ((hbuf (mu4e~mark-find-headers-buffer)))
+ (if (buffer-live-p hbuf)
+ (with-current-buffer hbuf
+ ,@body)
+ (progn (mu4e-message "%S" major-mode) ,@body))))))
+
+(defvar mu4e-marks
+ '((refile
+ :char ("r" . "â–¶")
+ :prompt "refile"
+ :dyn-target (lambda (target msg) (mu4e-get-refile-folder msg))
+ :action (lambda (docid msg target) (mu4e~proc-move docid
+ (mu4e~mark-check-target target) "-N")))
+ (delete
+ :char ("D" . "âŒ")
+ :prompt "Delete"
+ :show-target (lambda (target) "delete")
+ :action (lambda (docid msg target) (mu4e~proc-remove docid)))
+ (flag
+ :char ("+" . "✚")
+ :prompt "+flag"
+ :show-target (lambda (target) "flag")
+ :action (lambda (docid msg target) (mu4e~proc-move docid nil "+F-u-N")))
+ (move
+ :char ("m" . "â–·")
+ :prompt "move"
+ :ask-target mu4e~mark-get-move-target
+ :action (lambda (docid msg target) (mu4e~proc-move docid
+ (mu4e~mark-check-target target) "-N")))
+ (read
+ :char ("!" . "â—¼")
+ :prompt "!read"
+ :show-target (lambda (target) "read")
+ :action (lambda (docid msg target) (mu4e~proc-move docid nil "+S-u-N")))
+ (trash
+ :char ("d" . "â–¼")
+ :prompt "dtrash"
+ :dyn-target (lambda (target msg) (mu4e-get-trash-folder msg))
+ :action (lambda (docid msg target) (mu4e~proc-move docid
+ (mu4e~mark-check-target target) "+T-N")))
+ (unflag
+ :char ("-" . "âž–")
+ :prompt "-unflag"
+ :show-target (lambda (target) "unflag")
+ :action (lambda (docid msg target) (mu4e~proc-move docid nil "-F-N")))
+ (untrash
+ :char ("=" . "â–²")
+ :prompt "=untrash"
+ :show-target (lambda (target) "untrash")
+ :action (lambda (docid msg target) (mu4e~proc-move docid nil "-T")))
+ (unread
+ :char ("?" . "â—»")
+ :prompt "?unread"
+ :show-target (lambda (target) "unread")
+ :action (lambda (docid msg target) (mu4e~proc-move docid nil "-S+u-N")))
+ (unmark
+ :char " "
+ :prompt "unmark"
+ :action (mu4e-error "No action for unmarking"))
+ (action
+ :char ( "a" . "â—¯")
+ :prompt "action"
+ :ask-target (lambda () (mu4e-read-option "Action: " mu4e-headers-actions))
+ :action (lambda (docid msg actionfunc)
+ (save-excursion
+ (when (mu4e~headers-goto-docid docid)
+ (mu4e-headers-action actionfunc)))))
+ (something
+ :char ("*" . "✱")
+ :prompt "*something"
+ :action (mu4e-error "No action for deferred mark")))
+
+ "The list of all the possible marks.
+This is an alist mapping mark symbols to their properties. The
+properties are:
+ :char (string) or (basic . fancy) The character to display in
+ the headers view. Either a single-character string, or a
+ dotted-pair cons cell where the second item will be used if
+ `mu4e-use-fancy-chars' is `t', otherwise we'll use
+ the first one. It can also be a plain string for backwards
+ compatibility since we didn't always support
+ `mu4e-use-fancy-chars' here.
+ :prompt (string) The prompt to use when asking for marks (used for
+ example when marking a whole thread)
+ :ask-target (function returning a string) Get the target. This
+ function run once per bulk-operation, and thus is suitable
+ for user-interaction. If nil, the target is nil.
+ :dyn-target (function from (TARGET MSG) to string). Compute
+ the dynamic target. This is run once per message, which is
+ passed as MSG. The default is to just return the target.
+ :show-target (function from TARGET to string) How to display
+ the target.
+ :action (function taking (DOCID MSG TARGET)). The action to
+ apply on the message.")
+
+
+(defun mu4e-mark-at-point (mark target)
+ "Mark (or unmark) message at point.
+MARK specifies the mark-type. For `move'-marks and `trash'-marks
+the TARGET argument is non-nil and specifies to which
+maildir the message is to be moved/trashed. The function works in
+both headers buffers and message buffers.
+
+The following marks are available, and the corresponding props:
+
+ MARK TARGET description
+ ----------------------------------------------------------
+ `refile' y mark this message for archiving
+ `something' n mark this message for *something* (decided later)
+ `delete' n remove the message
+ `flag' n mark this message for flagging
+ `move' y move the message to some folder
+ `read' n mark the message as read
+ `trash' y trash the message to some folder
+ `unflag' n mark this message for unflagging
+ `untrash' n remove the 'trashed' flag from a message
+ `unmark' n unmark this message
+ `unread' n mark the message as unread
+ `action' y mark the message for some action."
+ (interactive)
+ (let* ((msg (mu4e-message-at-point))
+ (docid (mu4e-message-field msg :docid))
+ ;; get a cell with the mark char and the 'target' 'move' already has a
+ ;; target (the target folder) the other ones get a pseudo "target", as
+ ;; info for the user.
+ (markdesc (cdr (or (assq mark mu4e-marks) (mu4e-error "Invalid mark %S" mark))))
+ (get-markkar
+ (lambda (char)
+ (if (listp char)
+ (if mu4e-use-fancy-chars (cdr char) (car char))
+ char)))
+ (markkar (funcall get-markkar (plist-get markdesc :char)))
+ (target (mu4e~mark-get-dyn-target mark target))
+ (show-fct (plist-get markdesc :show-target))
+ (shown-target (if show-fct
+ (funcall show-fct target)
+ (if target (format "%S" target)))))
+ (unless docid (mu4e-warn "No message on this line"))
+ (unless (eq major-mode 'mu4e-headers-mode) (mu4e-error "Not in headers-mode"))
+ (save-excursion
+ (when (mu4e~headers-mark docid markkar)
+ ;; update the hash -- remove everything current, and if add the new stuff,
+ ;; unless we're unmarking
+ (remhash docid mu4e~mark-map)
+ ;; remove possible overlays
+ (remove-overlays (line-beginning-position) (line-end-position))
+ ;; now, let's set a mark (unless we were unmarking)
+ (unless (eql mark 'unmark)
+ (puthash docid (cons mark target) mu4e~mark-map)
+ ;; when we have a target (ie., when moving), show the target folder in
+ ;; an overlay
+ (when (and shown-target mu4e-headers-show-target)
+ (let* ((targetstr (propertize (concat "-> " shown-target " ")
+ 'face 'mu4e-system-face))
+ ;; mu4e~headers-goto-docid docid t \will take us just after the
+ ;; docid cookie and then we skip the mu4e~mark-fringe
+ (start (+ (length mu4e~mark-fringe)
+ (mu4e~headers-goto-docid docid t)))
+ (overlay (make-overlay start (+ start (length targetstr)))))
+ (overlay-put overlay 'display targetstr)
+ docid)))))))
+
+
+(defun mu4e~mark-get-move-target ()
+ "Ask for a move target, and propose to create it if it does not exist."
+ (interactive)
+ ;; (mu4e-message-at-point) ;; raises error if there is none
+ (let* ((target (mu4e-ask-maildir "Move message to: "))
+ (target (if (string= (substring target 0 1) "/")
+ target
+ (concat "/" target)))
+ (fulltarget (concat mu4e-maildir target)))
+ (when (or (file-directory-p fulltarget)
+ (and (yes-or-no-p
+ (format "%s does not exist. Create now?" fulltarget))
+ (mu4e~proc-mkdir fulltarget)))
+ target)))
+
+(defun mu4e~mark-ask-target (mark)
+ "Ask the target for MARK, if the user should be asked the target."
+ (let ((getter (plist-get (cdr (assq mark mu4e-marks)) :ask-target)))
+ (and getter (funcall getter))))
+
+(defun mu4e~mark-get-dyn-target (mark target)
+ "Get the dynamic target for MARK. The result may depend on the
+message at point."
+ (let ((getter (plist-get (cdr (assq mark mu4e-marks)) :dyn-target)))
+ (if getter
+ (funcall getter target (mu4e-message-at-point))
+ target)))
+
+
+(defun mu4e-mark-set (mark &optional target)
+ "Mark the header at point, or, if region is active, mark all
+headers in the region. Optionally, provide TARGET (for moves)."
+ (unless target
+ (setq target (mu4e~mark-ask-target mark)))
+ (if (not (use-region-p))
+ ;; single message
+ (mu4e-mark-at-point mark target)
+ ;; mark all messages in the region.
+ (save-excursion
+ (let ((cant-go-further) (eor (region-end)))
+ (goto-char (region-beginning))
+ (while (and (<= (point) eor) (not cant-go-further))
+ (mu4e-mark-at-point mark target)
+ (setq cant-go-further (not (mu4e-headers-next))))))))
+
+(defun mu4e-mark-restore (docid)
+ "Restore the visual mark for the message with DOCID."
+ (let ((markcell (gethash docid mu4e~mark-map)))
+ (when markcell
+ (save-excursion
+ (when (mu4e~headers-goto-docid docid)
+ (mu4e-mark-at-point (car markcell) (cdr markcell)))))))
+
+(defun mu4e~mark-get-markpair (prompt &optional allow-something)
+ "Ask user for a mark; return (MARK . TARGET).
+If ALLOW-SOMETHING is non-nil, allow the 'something' pseudo mark
+as well."
+ (let* ((marks (mapcar (lambda (markdescr)
+ (cons (plist-get (cdr markdescr) :prompt)
+ (car markdescr)))
+ mu4e-marks))
+ (marks
+ (if allow-something
+ marks (remove-if (lambda (m) (eq 'something (cdr m))) marks)))
+ (mark (mu4e-read-option prompt marks))
+ (target (mu4e~mark-ask-target mark)))
+ (cons mark target)))
+
+
+(defun mu4e-mark-resolve-deferred-marks ()
+ "Check if there are any deferred ('something') marks.
+If there are such marks, replace them with a _real_ mark (ask the
+user which one)."
+ (interactive)
+ (mu4e~mark-in-context
+ (let ((markpair))
+ (maphash
+ (lambda (docid val)
+ (let ((mark (car val)) (target (cdr val)))
+ (when (eql mark 'something)
+ (unless markpair
+ (setq markpair
+ (mu4e~mark-get-markpair "Set deferred mark(s) to: " nil)))
+ (save-excursion
+ (when (mu4e~headers-goto-docid docid)
+ (mu4e-mark-set (car markpair) (cdr markpair)))))))
+ mu4e~mark-map))))
+
+(defun mu4e~mark-check-target (target)
+ "Check if the target exists; if not, offer to create it."
+ (let ((fulltarget (concat mu4e-maildir target)))
+ (if (not (mu4e-create-maildir-maybe fulltarget))
+ (mu4e-error "Target dir %s does not exist " fulltarget)
+ target)))
+
+(defun mu4e-mark-execute-all (&optional no-confirmation)
+ "Execute the actions for all marked messages in this buffer.
+After the actions have been executed succesfully, the affected
+messages are *hidden* from the current header list. Since the
+headers are the result of a search, we cannot be certain that the
+messages no longer match the current one - to get that
+certainty, we need to rerun the search, but we don't want to do
+that automatically, as it may be too slow and/or break the user's
+flow. Therefore, we hide the message, which in practice seems to
+work well.
+
+If NO-CONFIRMATION is non-nil, don't ask user for confirmation."
+ (interactive)
+ (mu4e~mark-in-context
+ (let ((marknum (hash-table-count mu4e~mark-map)))
+ (if (zerop marknum)
+ (message "Nothing is marked")
+ (mu4e-mark-resolve-deferred-marks)
+ (when (or no-confirmation
+ (y-or-n-p
+ (format "Are you sure you want to execute %d mark%s?"
+ marknum (if (> marknum 1) "s" ""))))
+ (maphash
+ (lambda (docid val)
+ (let* ((mark (car val)) (target (cdr val))
+ (markdescr (assq mark mu4e-marks))
+ (msg (save-excursion
+ (mu4e~headers-goto-docid docid)
+ (mu4e-message-at-point))))
+ ;; note: whenever you do something with the message,
+ ;; it looses its N (new) flag
+ (if markdescr
+ (progn
+ (run-hook-with-args
+ 'mu4e-mark-execute-pre-hook mark msg)
+ (funcall (plist-get (cdr markdescr) :action) docid msg target))
+ (mu4e-error "Unrecognized mark %S" mark))))
+ mu4e~mark-map))
+ (mu4e-mark-unmark-all)
+ (message nil)))))
+
+(defun mu4e-mark-unmark-all ()
+ "Unmark all marked messages."
+ (interactive)
+ (mu4e~mark-in-context
+ (when (or (null mu4e~mark-map) (zerop (hash-table-count mu4e~mark-map)))
+ (mu4e-warn "Nothing is marked"))
+ (maphash
+ (lambda (docid val)
+ (save-excursion
+ (when (mu4e~headers-goto-docid docid)
+ (mu4e-mark-set 'unmark))))
+ mu4e~mark-map)
+ ;; in any case, clear the marks map
+ (mu4e~mark-clear)))
+
+(defun mu4e-mark-docid-marked-p (docid)
+ "Is the given docid marked?"
+ (when (gethash docid mu4e~mark-map) t))
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+(defun mu4e-mark-marks-num ()
+ "Return the number of marks in the current buffer."
+ (if mu4e~mark-map (hash-table-count mu4e~mark-map) 0))
+
+
+(defun mu4e-mark-handle-when-leaving ()
+ "If there are any marks in the current buffer, handle those
+according to the value of `mu4e-headers-leave-behavior'. This
+function is to be called before any further action (like searching,
+quitting the buffer) is taken; returning t means 'take the following
+action', return nil means 'don't do anything'."
+ (mu4e~mark-in-context
+ (let ((marknum (mu4e-mark-marks-num))
+ (what mu4e-headers-leave-behavior))
+ (unless (zerop marknum) ;; nothing to do?
+ (when (eq what 'ask)
+ (setq what (mu4e-read-option
+ (format "There are %d existing mark(s); should we: " marknum)
+ '( ("apply marks" . apply)
+ ("ignore marks?" . ignore)))))
+ ;; we determined what to do... now do it
+ (when (eq what 'apply)
+ (mu4e-mark-execute-all t))))))
+
+
+(provide 'mu4e-mark)
+;; End of mu4e-mark.el
diff --git a/_spacemacs.d/local/mu4e/mu4e-message.el b/_spacemacs.d/local/mu4e/mu4e-message.el
new file mode 100644
index 0000000..e339077
--- /dev/null
+++ b/_spacemacs.d/local/mu4e/mu4e-message.el
@@ -0,0 +1,284 @@
+;;; mu4e-message.el -- part of mu4e, the mu mail user agent
+;;
+;; Copyright (C) 2012-2016 Dirk-Jan C. Binnema
+
+;; Author: Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
+;; Maintainer: Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
+
+;; This file is not part of GNU Emacs.
+;;
+;; 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 <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; Functions to get data from mu4e-message plist structure
+
+;;; Code:
+(eval-when-compile (byte-compile-disable-warning 'cl-functions))
+
+(require 'mu4e-vars)
+(require 'mu4e-utils)
+
+(require 'cl)
+(require 'html2text)
+
+
+(defcustom mu4e-html2text-command
+ (if (fboundp 'shr-insert-document) 'mu4e-shr2text 'html2text)
+
+ "Either a shell command or a function that converts from html to plain text.
+
+If it is a shell-command, the command reads html from standard
+input and outputs plain text on standard output. If you use the
+htmltext program, it's recommended you use \"html2text -utf8
+-width 72\". Alternatives are the python-based html2markdown, w3m
+and on MacOS you may want to use textutil.
+
+It can also be a function, which takes the current buffer in html
+as input, and transforms it into html (like the `html2text'
+function).
+
+In both cases, the output is expected to be in UTF-8 encoding.
+
+Newer emacs has the shr renderer, and when its available,
+conversion defaults `mu4e-shr2text'; otherwise, the default is
+emacs' built-in `html2text' function."
+ :type '(choice string function)
+ :group 'mu4e-view)
+
+(defcustom mu4e-view-prefer-html nil
+ "Whether to base the body display on the html-version.
+If the e-mail message has no html-version the plain-text version
+is always used."
+ :type 'boolean
+ :group 'mu4e-view)
+
+(defcustom mu4e-view-html-plaintext-ratio-heuristic 5
+ "Ratio between the length of the html and the plain text part
+below which mu4e will consider the plain text part to be 'This
+messages requires html' text bodies."
+ :type 'integer
+ :group 'mu4e-view)
+
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+(defsubst mu4e-message-field-raw (msg field)
+ "Retrieve FIELD from message plist MSG.
+FIELD is one of :from, :to, :cc, :bcc, :subject, :data,
+:message-id, :path, :maildir, :priority, :attachments,
+:references, :in-reply-to, :body-txt, :body-html
+
+Returns `nil' if the field does not exist.
+
+A message plist looks something like:
+\(:docid 32461
+ :from ((\"Nikola Tesla\" . \"niko@example.com\"))
+ :to ((\"Thomas Edison\" . \"tom@example.com\"))
+ :cc ((\"Rupert The Monkey\" . \"rupert@example.com\"))
+ :subject \"RE: what about the 50K?\"
+ :date (20369 17624 0)
+ :size 4337
+ :message-id \"6BDC23465F79238C8233AB82D81EE81AF0114E4E74@123213.mail.example.com\"
+ :path \"/home/tom/Maildir/INBOX/cur/133443243973_1.10027.atlas:2,S\"
+ :maildir \"/INBOX\"
+ :priority normal
+ :flags (seen)
+ :attachments
+ ((:index 2 :name \"photo.jpg\" :mime-type \"image/jpeg\" :size 147331)
+ (:index 3 :name \"book.pdf\" :mime-type \"application/pdf\" :size 192220))
+ :references (\"6BDC23465F79238C8384574032D81EE81AF0114E4E74@123213.mail.example.com\"
+ \"6BDC23465F79238203498230942D81EE81AF0114E4E74@123213.mail.example.com\")
+ :in-reply-to \"6BDC23465F79238203498230942D81EE81AF0114E4E74@123213.mail.example.com\"
+ :body-txt \"Hi Tom, ...\"
+\)).
+Some notes on the format:
+- The address fields are lists of pairs (NAME . EMAIL), where NAME can be nil.
+- The date is in format emacs uses in `current-time'
+- Attachments are a list of elements with fields :index (the number of
+ the MIME-part), :name (the file name, if any), :mime-type (the
+ MIME-type, if any) and :size (the size in bytes, if any).
+- Messages in the Headers view come from the database and do not have
+ :attachments, :body-txt or :body-html fields. Message in the
+ Message view use the actual message file, and do include these fields."
+ ;; after all this documentation, the spectacular implementation
+ (if msg
+ (plist-get msg field)
+ (mu4e-error "message must be non-nil")))
+
+(defsubst mu4e-message-field (msg field)
+ "Retrieve FIELD from message plist MSG.
+Like `mu4e-message-field-nil', but will sanitize `nil' values:
+- all string field except body-txt/body-html: nil -> \"\"
+- numeric fields + dates : nil -> 0
+- all others : return the value
+Thus, function will return nil for empty lists, non-existing body-txt or body-html."
+ (let ((val (mu4e-message-field-raw msg field)))
+ (cond
+ (val
+ val) ;; non-nil -> just return it
+ ((member field '(:subject :message-id :path :maildir :in-reply-to))
+ "") ;; string fields except body-txt, body-html: nil -> ""
+ ((member field '(:body-html :body-txt))
+ val)
+ ((member field '(:docid :size))
+ 0) ;; numeric type: nil -> 0
+ (t
+ val)))) ;; otherwise, just return nil
+
+(defsubst mu4e-message-has-field (msg field)
+ "Return t if MSG contains FIELD, nil otherwise."
+ (plist-member msg field))
+
+(defsubst mu4e-message-at-point (&optional noerror)
+ "Get the message s-expression for the message at point in either
+the headers buffer or the view buffer, or nil if there is no such
+message. If optional NOERROR is non-nil, do not raise an error when
+there is no message at point."
+ (let ((msg (or (get-text-property (point) 'msg) mu4e~view-msg)))
+ (if msg
+ msg
+ (unless noerror (mu4e-warn "No message at point")))))
+
+(defsubst mu4e-message-field-at-point (field)
+ "Get the field FIELD from the message at point.
+This is equivalent to:
+ (mu4e-message-field (mu4e-message-at-point) FIELD)."
+ (mu4e-message-field (mu4e-message-at-point) field))
+
+(defun mu4e-message-body-text (msg)
+ "Get the body in text form for this message.
+This is either :body-txt, or if not available, :body-html converted
+to text, using `mu4e-html2text-command' is non-nil, it will use
+that. Normally, thiss function prefers the text part, but this can
+be changed by setting `mu4e-view-prefer-html'."
+ (let* ((txt (mu4e-message-field msg :body-txt))
+ (html (mu4e-message-field msg :body-html))
+ (body
+ (cond
+ ;; does it look like some text? ie., if the text part is more than
+ ;; mu4e-view-html-plaintext-ratio-heuristic times shorter than the
+ ;; html part, it should't be used
+ ;; This is an heuristic to guard against 'This messages requires
+ ;; html' text bodies.
+ ((and (> (* mu4e-view-html-plaintext-ratio-heuristic
+ (length txt)) (length html))
+ ;; use html if it's prefered, unless there is no html
+ (or (not mu4e-view-prefer-html) (not html)))
+ txt)
+ ;; otherwise, it there some html?
+ (html
+ (with-temp-buffer
+ (insert html)
+ (cond
+ ((stringp mu4e-html2text-command)
+ (let* ((tmp-file (mu4e-make-temp-file "html")))
+ (write-region (point-min) (point-max) tmp-file)
+ (erase-buffer)
+ (call-process-shell-command mu4e-html2text-command tmp-file t t)
+ (delete-file tmp-file)))
+ ((functionp mu4e-html2text-command)
+ (funcall mu4e-html2text-command))
+ (t (mu4e-error "Invalid `mu4e-html2text-command'")))
+ (buffer-string))
+ )
+ (t ;; otherwise, an empty body
+ ""))))
+ ;; and finally, remove some crap from the remaining string; it seems
+ ;; esp. outlook lies about its encoding (ie., it says 'iso-8859-1' but
+ ;; really it's 'windows-1252'), thus giving us these funky chars. here, we
+ ;; either remove them, or replace with 'what-was-meant' (heuristically)
+ (with-temp-buffer
+ (insert body)
+ (goto-char (point-min))
+ (while (re-search-forward "[  ’]" nil t)
+ (replace-match
+ (cond
+ ((string= (match-string 0) "’") "'")
+ (t ""))))
+ (buffer-string))))
+
+(defun mu4e-message-contact-field-matches (msg cfield rx)
+ "Checks whether any of the of the contacts in field
+CFIELD (either :to, :from, :cc or :bcc, or a list of those) of
+msg MSG matches (with their name or e-mail address) regular
+expressions RX. If there is a match, return non-nil; otherwise
+return nil. RX can also be a list of regular expressions, in
+which case any of those are tried for a match."
+ (if (and cfield (listp cfield))
+ (or (mu4e-message-contact-field-matches msg (car cfield) rx)
+ (mu4e-message-contact-field-matches msg (cdr cfield) rx))
+ (when cfield
+ (if (listp rx)
+ ;; if rx is a list, try each one of them for a match
+ (find-if
+ (lambda (a-rx) (mu4e-message-contact-field-matches msg cfield a-rx))
+ rx)
+ ;; not a list, check the rx
+ (find-if
+ (lambda (ct)
+ (let ((name (car ct)) (email (cdr ct)))
+ (or
+ (and name (string-match rx name))
+ (and email (string-match rx email)))))
+ (mu4e-message-field msg cfield))))))
+
+(defun mu4e-message-contact-field-matches-me (msg cfield)
+ "Checks whether any of the of the contacts in field
+CFIELD (either :to, :from, :cc or :bcc) of msg MSG matches *me*,
+that is, any of the e-mail address in
+`mu4e-user-mail-address-list'. Returns the contact cell that
+matched, or nil."
+ (find-if
+ (lambda (cc-cell)
+ (member-if
+ (lambda (addr)
+ (string= (downcase addr) (downcase (cdr cc-cell))))
+ mu4e-user-mail-address-list))
+ (mu4e-message-field msg cfield)))
+
+(defsubst mu4e-message-part-field (msgpart field)
+ "Get some field in a message part; a part would look something like:
+ (:index 2 :name \"photo.jpg\" :mime-type \"image/jpeg\" :size 147331)."
+ (plist-get msgpart field))
+
+;; backward compatibility ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+(defalias 'mu4e-msg-field 'mu4e-message-field)
+(defalias 'mu4e-body-text 'mu4e-message-body-text) ;; backward compatibility
+
+(defun mu4e-field-at-point (field)
+ "Get FIELD (a symbol, see `mu4e-header-info') for the message at
+point in eiter the headers buffer or the view buffer."
+ (plist-get (mu4e-message-at-point) field))
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+
+(defun mu4e-shr2text ()
+ "Html to text using the shr engine; this can be used in
+`mu4e-html2text-command' in a new enough emacs. Based on code by
+Titus von der Malsburg."
+ (interactive)
+ (let ((dom (libxml-parse-html-region (point-min) (point-max)))
+ ;; When HTML emails contain references to remote images,
+ ;; retrieving these images leaks information. For example,
+ ;; the sender can see when I openend the email and from which
+ ;; computer (IP address). For this reason, it is preferrable
+ ;; to not retrieve images.
+ ;; See this discussion on mu-discuss:
+ ;; https://groups.google.com/forum/#!topic/mu-discuss/gr1cwNNZnXo
+ (shr-inhibit-images t))
+ (erase-buffer)
+ (shr-insert-document dom)
+ (goto-char (point-min))))
+
+(provide 'mu4e-message)
diff --git a/_spacemacs.d/local/mu4e/mu4e-meta.el b/_spacemacs.d/local/mu4e/mu4e-meta.el
new file mode 100644
index 0000000..50c87f9
--- /dev/null
+++ b/_spacemacs.d/local/mu4e/mu4e-meta.el
@@ -0,0 +1,11 @@
+;; auto-generated
+(defconst mu4e-mu-version "0.9.17"
+ "Required mu binary version; mu4e's version must agree with this.")
+
+(defconst mu4e-builddir "/home/aly/build/mu"
+ "Top-level build directory.")
+
+(defconst mu4e-doc-dir "/home/aly/local/mu/share/doc/mu"
+ "Mu4e's data-dir.")
+
+(provide 'mu4e-meta)
diff --git a/_spacemacs.d/local/mu4e/mu4e-proc.el b/_spacemacs.d/local/mu4e/mu4e-proc.el
new file mode 100644
index 0000000..d93af5d
--- /dev/null
+++ b/_spacemacs.d/local/mu4e/mu4e-proc.el
@@ -0,0 +1,524 @@
+;; mu4e-proc.el -- part of mu4e, the mu mail user agent
+;;
+;; Copyright (C) 2011-2016 Dirk-Jan C. Binnema
+
+;; Author: Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
+;; Maintainer: Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
+
+;; This file is not part of GNU Emacs.
+;;
+;; 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 <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;;; Code:
+(require 'mu4e-vars)
+(require 'mu4e-utils)
+(require 'mu4e-meta)
+
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; internal vars
+
+(defvar mu4e~proc-buf nil
+ "Buffer (string) for data received from the backend.")
+(defconst mu4e~proc-name " *mu4e-proc*"
+ "Name of the server process, buffer.")
+(defvar mu4e~proc-process nil
+ "The mu-server process.")
+
+;; dealing with the length cookie that precedes expressions
+(defconst mu4e~cookie-pre "\376"
+ "Each expression we get from the backend (mu server) starts with
+a length cookie:
+ <`mu4e~cookie-pre'><length-in-hex><`mu4e~cookie-post'>.")
+(defconst mu4e~cookie-post "\377"
+ "Each expression we get from the backend (mu server) starts with
+a length cookie:
+ <`mu4e~cookie-pre'><length-in-hex><`mu4e~cookie-post'>.")
+(defconst mu4e~cookie-matcher-rx
+ (concat mu4e~cookie-pre "\\([[:xdigit:]]+\\)" mu4e~cookie-post)
+ "Regular expression matching the length cookie.
+Match 1 will be the length (in hex).")
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+(defsubst mu4e~proc-send-command (frm &rest args)
+ "Send as command to the mu server process.
+Start the process if needed."
+ (unless (mu4e~proc-running-p)
+ (mu4e~proc-start))
+ (let ((cmd (apply 'format frm args)))
+ (mu4e-log 'to-server "%s" cmd)
+ (process-send-string mu4e~proc-process (concat cmd "\n"))))
+
+(defun mu4e~proc-start ()
+ "Start the mu server process."
+ (unless (file-executable-p mu4e-mu-binary)
+ (mu4e-error (format "`mu4e-mu-binary' (%S) not found" mu4e-mu-binary)))
+ (let* ((process-connection-type nil) ;; use a pipe
+ (args '("server"))
+ (args (append args (when mu4e-mu-home
+ (list (concat "--muhome=" mu4e-mu-home))))))
+ (setq mu4e~proc-buf "")
+ (setq mu4e~proc-process (apply 'start-process
+ mu4e~proc-name mu4e~proc-name
+ mu4e-mu-binary args))
+ ;; register a function for (:info ...) sexps
+ (unless mu4e~proc-process
+ (mu4e-error "Failed to start the mu4e backend"))
+ (set-process-query-on-exit-flag mu4e~proc-process nil)
+ (set-process-coding-system mu4e~proc-process 'binary 'utf-8-unix)
+ (set-process-filter mu4e~proc-process 'mu4e~proc-filter)
+ (set-process-sentinel mu4e~proc-process 'mu4e~proc-sentinel)))
+
+(defun mu4e~proc-kill ()
+ "Kill the mu server process."
+ (let* ((buf (get-buffer mu4e~proc-name))
+ (proc (and (buffer-live-p buf) (get-buffer-process buf))))
+ (when proc
+ (let ((delete-exited-processes t))
+ ;; the mu server signal handler will make it quit after 'quit'
+ (mu4e~proc-send-command "cmd:quit"))
+ ;; try sending SIGINT (C-c) to process, so it can exit gracefully
+ (ignore-errors
+ (signal-process proc 'SIGINT))))
+ (setq
+ mu4e~proc-process nil
+ mu4e~proc-buf nil))
+
+(defun mu4e~proc-running-p ()
+ "Whether the mu process is running."
+ (when (and mu4e~proc-process
+ (memq (process-status mu4e~proc-process)
+ '(run open listen connect stop)))
+ t))
+
+(defsubst mu4e~proc-eat-sexp-from-buf ()
+ "'Eat' the next s-expression from `mu4e~proc-buf'.
+Note: this is a string, not an emacs-buffer. `mu4e~proc-buf gets
+its contents from the mu-servers in the following form:
+ <`mu4e~cookie-pre'><length-in-hex><`mu4e~cookie-post'>
+Function returns this sexp, or nil if there was none.
+`mu4e~proc-buf' is updated as well, with all processed sexp data
+removed."
+ (ignore-errors ;; the server may die in the middle...
+ ;; mu4e~cookie-matcher-rx:
+ ;; (concat mu4e~cookie-pre "\\([[:xdigit:]]+\\)]" mu4e~cookie-post)
+ (let ((b (string-match mu4e~cookie-matcher-rx mu4e~proc-buf))
+ (sexp-len) (objcons))
+ (when b
+ (setq sexp-len (string-to-number (match-string 1 mu4e~proc-buf) 16))
+ ;; does mu4e~proc-buf contain the full sexp?
+ (when (>= (length mu4e~proc-buf) (+ sexp-len (match-end 0)))
+ ;; clear-up start
+ (setq mu4e~proc-buf (substring mu4e~proc-buf (match-end 0)))
+ ;; note: we read the input in binary mode -- here, we take the part
+ ;; that is the sexp, and convert that to utf-8, before we interpret
+ ;; it.
+ (setq objcons (read-from-string
+ (decode-coding-string
+ (substring mu4e~proc-buf 0 sexp-len)
+ 'utf-8 t)))
+ (when objcons
+ (setq mu4e~proc-buf (substring mu4e~proc-buf sexp-len))
+ (car objcons)))))))
+
+
+(defun mu4e~proc-filter (proc str)
+ "A process-filter for the 'mu server' output.
+It accumulates the strings into valid sexps by checking of the
+';;eox' end-of-sexp marker, and then evaluating them.
+
+The server output is as follows:
+
+ 1. an error
+ (:error 2 :message \"unknown command\")
+ ;; eox
+ => this will be passed to `mu4e-error-func'.
+
+ 2a. a message sexp looks something like:
+ \(
+ :docid 1585
+ :from ((\"Donald Duck\" . \"donald@example.com\"))
+ :to ((\"Mickey Mouse\" . \"mickey@example.com\"))
+ :subject \"Wicked stuff\"
+ :date (20023 26572 0)
+ :size 15165
+ :references (\"200208121222.g7CCMdb80690@msg.id\")
+ :in-reply-to \"200208121222.g7CCMdb80690@msg.id\"
+ :message-id \"foobar32423847ef23@pluto.net\"
+ :maildir: \"/archive\"
+ :path \"/home/mickey/Maildir/inbox/cur/1312254065_3.32282.pluto,4cd5bd4e9:2,\"
+ :priority high
+ :flags (new unread)
+ :attachments ((2 \"hello.jpg\" \"image/jpeg\") (3 \"laah.mp3\" \"audio/mp3\"))
+ :body-txt \" <message body>\"
+\)
+;; eox
+ => this will be passed to `mu4e-header-func'.
+
+ 2b. After the list of message sexps has been returned (see 2a.),
+ we'll receive a sexp that looks like
+ (:found <n>) with n the number of messages found. The <n> will be
+ passed to `mu4e-found-func'.
+
+ 3. a view looks like:
+ (:view <msg-sexp>)
+ => the <msg-sexp> (see 2.) will be passed to `mu4e-view-func'.
+
+ 4. a database update looks like:
+ (:update <msg-sexp> :move <nil-or-t>)
+
+ => the <msg-sexp> (see 2.) will be passed to
+ `mu4e-update-func', :move tells us whether this is a move to
+ another maildir, or merely a flag change.
+
+ 5. a remove looks like:
+ (:remove <docid>)
+ => the docid will be passed to `mu4e-remove-func'
+
+ 6. a compose looks like:
+ (:compose <reply|forward|edit|new> [:original<msg-sexp>] [:include <attach>])
+ `mu4e-compose-func'."
+ (mu4e-log 'misc "* Received %d byte(s)" (length str))
+ (setq mu4e~proc-buf (concat mu4e~proc-buf str)) ;; update our buffer
+ (let ((sexp (mu4e~proc-eat-sexp-from-buf)))
+ (with-local-quit
+ (while sexp
+ (mu4e-log 'from-server "%S" sexp)
+ (cond
+ ;; a header plist can be recognized by the existence of a :date field
+ ((plist-get sexp :date)
+ (funcall mu4e-header-func sexp))
+
+ ;; the found sexp, we receive after getting all the headers
+ ((plist-get sexp :found)
+ (funcall mu4e-found-func (plist-get sexp :found)))
+
+ ;; viewing a specific message
+ ((plist-get sexp :view)
+ (funcall mu4e-view-func (plist-get sexp :view)))
+
+ ;; receive an erase message
+ ((plist-get sexp :erase)
+ (funcall mu4e-erase-func))
+
+ ;; receive a :sent message
+ ((plist-get sexp :sent)
+ (funcall mu4e-sent-func
+ (plist-get sexp :docid)
+ (plist-get sexp :path)))
+
+ ;; received a pong message
+ ((plist-get sexp :pong)
+ (funcall mu4e-pong-func
+ (plist-get sexp :props)))
+
+ ;; received a contacts message
+ ;; note: we use 'member', to match (:contacts nil)
+ ((plist-member sexp :contacts)
+ (funcall mu4e-contacts-func
+ (plist-get sexp :contacts)))
+
+ ;; something got moved/flags changed
+ ((plist-get sexp :update)
+ (funcall mu4e-update-func
+ (plist-get sexp :update) (plist-get sexp :move)))
+
+ ;; a message got removed
+ ((plist-get sexp :remove)
+ (funcall mu4e-remove-func (plist-get sexp :remove)))
+
+ ;; start composing a new message
+ ((plist-get sexp :compose)
+ (funcall mu4e-compose-func
+ (plist-get sexp :compose)
+ (plist-get sexp :original)
+ (plist-get sexp :include)))
+
+ ;; do something with a temporary file
+ ((plist-get sexp :temp)
+ (funcall mu4e-temp-func
+ (plist-get sexp :temp) ;; name of the temp file
+ (plist-get sexp :what) ;; what to do with it
+ ;; (pipe|emacs|open-with...)
+ (plist-get sexp :docid) ;; docid of the message
+ (plist-get sexp :param)));; parameter for the action
+
+ ;; get some info
+ ((plist-get sexp :info)
+ (funcall mu4e-info-func sexp))
+
+ ;; receive an error
+ ((plist-get sexp :error)
+ (funcall mu4e-error-func
+ (plist-get sexp :error)
+ (plist-get sexp :message)))
+
+ (t (mu4e-message "Unexpected data from server [%S]" sexp)))
+
+ (setq sexp (mu4e~proc-eat-sexp-from-buf))))))
+
+
+;; error codes are defined in src/mu-util
+;;(defconst mu4e-xapian-empty 19 "Error code: xapian is empty/non-existent")
+
+(defun mu4e~proc-sentinel (proc msg)
+ "Function that will be called when the mu-server process terminates."
+ (let ((status (process-status proc)) (code (process-exit-status proc)))
+ (setq mu4e~proc-process nil)
+ (setq mu4e~proc-buf "") ;; clear any half-received sexps
+ (cond
+ ((eq status 'signal)
+ (cond
+ ((eq code 9) (message nil))
+ ;;(message "the mu server process has been stopped"))
+ (t (error (format "mu server process received signal %d" code)))))
+ ((eq status 'exit)
+ (cond
+ ((eq code 0)
+ (message nil)) ;; don't do anything
+ ((eq code 11)
+ (error "Database is locked by another process"))
+ ((eq code 15)
+ (error "Database needs upgrade; try `mu index --rebuild' from the command line"))
+ ((eq code 19)
+ (error "Database empty; try indexing some messages"))
+ (t (error "mu server process ended with exit code %d" code))))
+ (t
+ (error "Something bad happened to the mu server process")))))
+
+(defsubst mu4e~docid-msgid-param (docid-or-msgid)
+ "Construct a backend parameter based on DOCID-OR-MSGID."
+ (format
+ (if (stringp docid-or-msgid)
+ "msgid:\"%s\""
+ "docid:%d")
+ docid-or-msgid))
+
+(defun mu4e~proc-remove (docid)
+ "Remove message identified by docid.
+The results are reporter through either (:update ... ) or (:error)
+sexp, which are handled my `mu4e-error-func', respectively."
+ (mu4e~proc-send-command "cmd:remove docid:%d" docid))
+
+(defun mu4e~proc-escape (str)
+ "Escape STRING for transport -- put it in quotes, and escape existing quotation.
+In particular, backslashes and double-quotes."
+ (let ((esc (replace-regexp-in-string "\\\\" "\\\\\\\\" str)))
+ (format "\"%s\"" (replace-regexp-in-string "\"" "\\\\\"" esc))))
+
+(defun mu4e~proc-find (query threads sortfield sortdir maxnum skip-dups include-related)
+ "Start a database query for QUERY.
+If THREADS is non-nil, show results in threaded fasion, SORTFIELD
+is a symbol describing the field to sort by (or nil); see
+`mu4e~headers-sortfield-choices'. If SORT is `descending', sort
+Z->A, if it's `ascending', sort A->Z. MAXNUM determines the maximum
+number of results to return, or nil for 'unlimited'. If SKIP-DUPS
+is non-nil, show only one of duplicate messages (see
+`mu4e-headers-skip-duplicates'). If INCLUDE-RELATED is non-nil,
+include messages related to the messages matching the search
+query (see `mu4e-headers-include-related').
+
+For each
+result found, a function is called, depending on the kind of
+result. The variables `mu4e-error-func' contain the function that
+will be called for, resp., a message (header row) or an error."
+ (mu4e~proc-send-command
+ (concat
+ "cmd:find query:%s threads:%s sortfield:%s reverse:%s maxnum:%d "
+ "skip-dups:%s include-related:%s")
+ (mu4e~proc-escape query)
+ (if threads "true" "false")
+ ;; sortfield is e.g. ':subject'; this removes the ':'
+ (if (null sortfield) "nil" (substring (symbol-name sortfield) 1))
+ ;; TODO: use ascending/descending in backend too (it's clearer than 'reverse'
+ (if (eq sortdir 'descending) "true" "false")
+ (if maxnum maxnum -1)
+ (if skip-dups "true" "false")
+ (if include-related "true" "false")))
+
+(defun mu4e~proc-move (docid-or-msgid &optional maildir flags)
+ "Move message identified by DOCID-OR-MSGID.
+At least one of MAILDIR and FLAGS should be specified. Note, even
+if MAILDIR is nil, this is still a move, since a change in flags
+still implies a change in message filename.
+
+MAILDIR (), optionally
+setting FLAGS (keyword argument :flags). optionally setting FLAGS
+in the process. If MAILDIR is nil, message will be moved within the
+same maildir.
+
+MAILDIR must be a maildir, that is, the part _without_ cur/ or new/
+or the root-maildir-prefix. E.g. \"/archive\". This directory must
+already exist.
+
+The FLAGS parameter can have the following forms:
+ 1. a list of flags such as '(passed replied seen)
+ 2. a string containing the one-char versions of the flags, e.g. \"PRS\"
+ 3. a delta-string specifying the changes with +/- and the one-char flags,
+ e.g. \"+S-N\" to set Seen and remove New.
+
+The flags are any of `deleted', `flagged', `new', `passed', `replied' `seen' or
+`trashed', or the corresponding \"DFNPRST\" as defined in [1]. See
+`mu4e-string-to-flags' and `mu4e-flags-to-string'.
+The server reports the results for the operation through
+`mu4e-update-func'.
+
+If the variable `mu4e-change-filenames-when-moving' is
+non-nil, moving to a different maildir generates new names for
+the target files; this helps certain tools (such as mbsync).
+
+The results are reported through either (:update ... )
+or (:error ) sexp, which are handled my `mu4e-update-func' and
+`mu4e-error-func', respectively."
+ (unless (or maildir flags)
+ (mu4e-error "At least one of maildir and flags must be specified"))
+ (unless (or (not maildir) (file-exists-p (concat mu4e-maildir "/" maildir "/")))
+ (mu4e-error "Target dir does not exist"))
+ (let* ((idparam (mu4e~docid-msgid-param docid-or-msgid))
+ (flagstr
+ (when flags
+ (concat " flags:"
+ (if (stringp flags) flags (mu4e-flags-to-string flags)))))
+ (path
+ (when maildir
+ (format " maildir:%s" (mu4e~proc-escape maildir))))
+ (rename
+ (if (and maildir mu4e-change-filenames-when-moving) "true" "false")))
+ (mu4e~proc-send-command "cmd:move %s %s %s %s"
+ idparam (or flagstr "") (or path "")
+ (format "newname:%s" rename))))
+
+(defun mu4e~proc-index (path my-addresses)
+ "Update the message database for filesystem PATH, which should
+point to some maildir directory structure. MY-ADDRESSES is a list
+of 'my' email addresses (see `mu4e-user-mail-address-list')."
+ (let ((path (mu4e~proc-escape path))
+ (addrs (when my-addresses (mapconcat 'identity my-addresses ","))))
+ (if addrs
+ (mu4e~proc-send-command "cmd:index path:%s my-addresses:%s" path addrs)
+ (mu4e~proc-send-command "cmd:index path:%s" path))))
+
+(defun mu4e~proc-add (path maildir)
+ "Add the message at PATH to the database.
+With MAILDIR set to the maildir this message resides in,
+e.g. '/drafts'; if this works, we will receive (:info add :path
+<path> :docid <docid>) as well as (:update <msg-sexp>)."
+ (mu4e~proc-send-command "cmd:add path:%s %s"
+ (mu4e~proc-escape path)
+ (if maildir
+ (format "maildir:%s" (mu4e~proc-escape maildir))
+ "")))
+
+(defun mu4e~proc-sent (path maildir)
+ "Add the message at PATH to the database.
+With MAILDIR set to the maildir this message resides in,
+e.g. '/drafts'.
+
+ if this works, we will receive (:info add :path <path> :docid
+<docid> :fcc <path>)."
+ (mu4e~proc-send-command "cmd:sent path:%s maildir:%s"
+ (mu4e~proc-escape path) (mu4e~proc-escape maildir)))
+
+
+(defun mu4e~proc-compose (type decrypt &optional docid)
+ "Start composing a message of certain TYPE (a symbol, either
+`forward', `reply', `edit', `resend' or `new', based on an
+original message (ie, replying to, forwarding, editing,
+resending) with DOCID or nil for type `new'.
+
+The result will be delivered to the function registered as
+`mu4e-compose-func'."
+ (unless (member type '(forward reply edit resend new))
+ (mu4e-error "Unsupported compose-type %S" type))
+ (unless (eq (null docid) (eq type 'new))
+ (mu4e-error "`new' implies docid not-nil, and vice-versa"))
+ (mu4e~proc-send-command
+ "cmd:compose type:%s docid:%d extract-encrypted:%s use-agent:true"
+ (symbol-name type) docid (if decrypt "true" "false")))
+
+(defun mu4e~proc-mkdir (path)
+ "Create a new maildir-directory at filesystem PATH."
+ (mu4e~proc-send-command "cmd:mkdir path:%s" (mu4e~proc-escape path)))
+
+(defun mu4e~proc-extract (action docid partidx decrypt &optional path what param)
+ "Extract an attachment with index PARTIDX from message with DOCID
+and perform ACTION on it (as symbol, either `save', `open', `temp') which
+mean:
+ * save: save the part to PARAM1 (a path) (non-optional for save)$
+ * open: open the part with the default application registered for doing so
+ * temp: save to a temporary file, then respond with
+ (:temp <path> :what <what> :param <param>)."
+ (let ((cmd
+ (concat "cmd:extract "
+ (case action
+ (save
+ (format "action:save docid:%d index:%d path:%s extract-encrypted:%s use-agent:true"
+ docid partidx (mu4e~proc-escape path) (if decrypt "true" "false")))
+ (open (format "action:open docid:%d index:%d extract-encrypted:%s use-agent:true"
+ docid partidx (if decrypt "true" "false")))
+ (temp
+ (format "action:temp docid:%d index:%d what:%s%s extract-encrypted:%s use-agent:true"
+ docid partidx what
+ (if param
+ (if (stringp param)
+ (format " param:%s" (mu4e~proc-escape param))
+ (format " param:%S" param)) "") (if decrypt "true" "false")))
+ (otherwise (mu4e-error "Unsupported action %S" action))))
+ ))
+ (mu4e~proc-send-command "%s" cmd)))
+
+
+(defun mu4e~proc-ping ()
+ "Sends a ping to the mu server, expecting a (:pong ...) in response."
+ (mu4e~proc-send-command "cmd:ping"))
+
+(defun mu4e~proc-contacts (personal after)
+ "Sends the contacts command to the mu server.
+A (:contacts (<list>)) is expected in response. If PERSONAL is
+non-nil, only get personal contacts, if AFTER is non-nil, get
+only contacts seen AFTER (the time_t value)."
+ (mu4e~proc-send-command
+ "cmd:contacts personal:%s after:%d"
+ (if personal "true" "false")
+ (or after 0)))
+
+(defun mu4e~proc-view (docid-or-msgid &optional images decrypt)
+ "Get one particular message based on its DOCID-OR-MSGID.
+Optionally, if IMAGES is non-nil, backend will any images
+attached to the message, and return them as temp files.
+The result will be delivered to the function registered as
+`mu4e-view-func'."
+ (mu4e~proc-send-command
+ "cmd:view %s extract-images:%s extract-encrypted:%s use-agent:true"
+ (mu4e~docid-msgid-param docid-or-msgid)
+ (if images "true" "false")
+ (if decrypt "true" "false")))
+
+(defun mu4e~proc-view-path (path &optional images decrypt)
+ "View message at PATH (keyword argument).
+Optionally, if IMAGES is non-nil, backend will any images
+attached to the message, and return them as temp files. The
+result will be delivered to the function registered as
+`mu4e-view-func'."
+ (mu4e~proc-send-command
+ "cmd:view path:%s extract-images:%s extract-encrypted:%s use-agent:true"
+ (mu4e~proc-escape path)
+ (if images "true" "false")
+ (if decrypt "true" "false")))
+
+
+(provide 'mu4e-proc)
+;; End of mu4e-proc.el
diff --git a/_spacemacs.d/local/mu4e/mu4e-speedbar.el b/_spacemacs.d/local/mu4e/mu4e-speedbar.el
new file mode 100644
index 0000000..b359511
--- /dev/null
+++ b/_spacemacs.d/local/mu4e/mu4e-speedbar.el
@@ -0,0 +1,124 @@
+;;; mu4e-speedbar --- Speedbar support for mu4e
+
+;; Copyright (C) 2012-2016 Antono Vasiljev, Dirk-Jan C. Binnema
+;;
+;; Author: Antono Vasiljev <self@antono.info>
+;; Version: 0.1
+;; Keywords: file, tags, tools
+;;
+;; This file is not part of GNU Emacs.
+;;
+;; This program 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, or (at your option)
+;; any later version.
+;;
+;; This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+;;
+;; Speedbar provides a frame in which files, and locations in files
+;; are displayed. These functions provide mu4e specific support,
+;; showing maildir list in the side-bar.
+;;
+;; This file requires speedbar.
+
+;;; Code:
+
+(require 'speedbar)
+(require 'mu4e-vars)
+(require 'mu4e-headers)
+
+(defvar mu4e-main-speedbar-key-map nil
+ "Keymap used when in mu4e display mode.")
+(defvar mu4e-headers-speedbar-key-map nil
+ "Keymap used when in mu4e display mode.")
+(defvar mu4e-view-speedbar-key-map nil
+ "Keymap used when in mu4e display mode.")
+
+(defvar mu4e-main-speedbar-menu-items nil
+ "Additional menu-items to add to speedbar frame.")
+(defvar mu4e-headers-speedbar-menu-items nil
+ "Additional menu-items to add to speedbar frame.")
+(defvar mu4e-view-speedbar-menu-items nil
+ "Additional menu-items to add to speedbar frame.")
+
+
+(defun mu4e-speedbar-install-variables ()
+ "Install those variables used by speedbar to enhance mu4e."
+ (dolist (keymap
+ '( mu4e-main-speedbar-key-map
+ mu4e-headers-speedbar-key-map
+ mu4e-view-speedbar-key-map))
+ (unless keymap
+ (setq keymap (speedbar-make-specialized-keymap))
+ (define-key keymap "RET" 'speedbar-edit-line)
+ (define-key keymap "e" 'speedbar-edit-line))))
+
+
+;; Make sure our special speedbar major mode is loaded
+(if (featurep 'speedbar)
+ (mu4e-speedbar-install-variables)
+ (add-hook 'speedbar-load-hook 'mu4e-speedbar-install-variables))
+
+(defun mu4e~speedbar-render-maildir-list ()
+ "Insert the list of maildirs in the speedbar."
+ (interactive)
+ (mapcar (lambda (maildir-name)
+ (speedbar-insert-button
+ (concat " " maildir-name)
+ 'mu4e-highlight-face
+ 'highlight
+ 'mu4e~speedbar-maildir
+ maildir-name))
+ (mu4e-get-maildirs)))
+
+(defun mu4e~speedbar-maildir (&optional text token ident)
+ "Jump to maildir TOKEN. TEXT and INDENT are not used."
+ (speedbar-with-attached-buffer
+ (mu4e-headers-search (concat "\"maildir:" token "\"")
+ current-prefix-arg)))
+
+(defun mu4e~speedbar-render-bookmark-list ()
+ "Insert the list of bookmarks in the speedbar"
+ (interactive)
+ (mapcar (lambda (bookmark)
+ (speedbar-insert-button
+ (concat " " (nth 1 bookmark))
+ 'mu4e-highlight-face
+ 'highlight
+ 'mu4e~speedbar-bookmark
+ (nth 0 bookmark)))
+ mu4e-bookmarks))
+
+(defun mu4e~speedbar-bookmark (&optional text token ident)
+ "Run bookmarked query TOKEN. TEXT and INDENT are not used."
+ (speedbar-with-attached-buffer
+ (mu4e-headers-search token current-prefix-arg)))
+
+;;;###autoload
+(defun mu4e-speedbar-buttons (buffer)
+ "Create buttons for any mu4e BUFFER."
+ (interactive)
+ (erase-buffer)
+ (insert (propertize "* mu4e\n\n" 'face 'mu4e-title-face))
+
+ (insert (propertize " Bookmarks\n" 'face 'mu4e-title-face))
+ (mu4e~speedbar-render-bookmark-list)
+ (insert "\n")
+ (insert (propertize " Maildirs\n" 'face 'mu4e-title-face))
+ (mu4e~speedbar-render-maildir-list))
+
+(defun mu4e-main-speedbar-buttons (buffer) (mu4e-speedbar-buttons buffer))
+(defun mu4e-headers-speedbar-buttons (buffer) (mu4e-speedbar-buttons buffer))
+(defun mu4e-view-speedbar-buttons (buffer) (mu4e-speedbar-buttons buffer))
+
+
+(provide 'mu4e-speedbar)
+;;; mu4e-speedbar.el ends here
diff --git a/_spacemacs.d/local/mu4e/mu4e-utils.el b/_spacemacs.d/local/mu4e/mu4e-utils.el
new file mode 100644
index 0000000..1f8c9c0
--- /dev/null
+++ b/_spacemacs.d/local/mu4e/mu4e-utils.el
@@ -0,0 +1,1238 @@
+;;; mu4e-utils.el -- part of mu4e, the mu mail user agent
+;;
+;; Copyright (C) 2011-2016 Dirk-Jan C. Binnema
+;; Copyright (C) 2013 Tibor Simko
+
+;; Author: Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
+;; Maintainer: Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
+
+;; 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 <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; Utility functions used in the mu4e
+
+;;; Code:
+(eval-when-compile (byte-compile-disable-warning 'cl-functions))
+(require 'cl)
+
+(eval-when-compile (require 'org nil 'noerror))
+
+(require 'mu4e-vars)
+(require 'mu4e-meta)
+(require 'mu4e-lists)
+(require 'doc-view)
+
+;; keep the byte-compiler happy
+(declare-function mu4e~proc-mkdir "mu4e-proc")
+(declare-function mu4e~proc-ping "mu4e-proc")
+(declare-function mu4e~proc-contacts "mu4e-proc")
+(declare-function mu4e~proc-kill "mu4e-proc")
+(declare-function mu4e~proc-index "mu4e-proc")
+(declare-function mu4e~proc-add "mu4e-proc")
+(declare-function mu4e~proc-mkdir "mu4e-proc")
+(declare-function mu4e~proc-running-p "mu4e-proc")
+
+(declare-function mu4e~context-autoswitch "mu4e-context")
+(declare-function mu4e-context-determine "mu4e-context")
+(declare-function mu4e-context-vars "mu4e-context")
+(declare-function show-all "org")
+
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; the following is taken from org.el; we copy it here since we don't want to
+;; depend on org-mode directly (it causes byte-compilation errors) TODO: a
+;; cleaner solution....
+(defconst mu4e~ts-regexp0
+ (concat
+ "\\(\\([0-9]\\{4\\}\\)-\\([0-9]\\{2\\}\\)-\\([0-9]\\{2\\}\\)"
+ "\\( +[^]+0-9>\r\n -]+\\)?\\( +\\([0-9]\\{1,2\\}\\):"
+ "\\([0-9]\\{2\\}\\)\\)?\\)")
+ "Regular expression matching time strings for analysis.
+This one does not require the space after the date, so it can be
+used on a string that terminates immediately after the date.")
+
+(defun mu4e-parse-time-string (s &optional nodefault)
+ "Parse the standard Org-mode time string.
+This should be a lot faster than the normal `parse-time-string'.
+If time is not given, defaults to 0:00. However, with optional
+NODEFAULT, hour and minute fields will be nil if not given."
+ (if (string-match mu4e~ts-regexp0 s)
+ (list 0
+ (if (or (match-beginning 8) (not nodefault))
+ (string-to-number (or (match-string 8 s) "0")))
+ (if (or (match-beginning 7) (not nodefault))
+ (string-to-number (or (match-string 7 s) "0")))
+ (string-to-number (match-string 4 s))
+ (string-to-number (match-string 3 s))
+ (string-to-number (match-string 2 s))
+ nil nil nil)
+ (mu4e-error "Not a standard mu4e time string: %s" s)))
+
+
+(defun mu4e-user-mail-address-p (addr)
+ "If ADDR is one of user's e-mail addresses return t, nil otherwise.
+User's addresses are set in `mu4e-user-mail-address-list')."
+ (when (and addr mu4e-user-mail-address-list
+ (find addr mu4e-user-mail-address-list :test 'string=))
+ t))
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+(defmacro with~mu4e-context-vars (context &rest body)
+ "Evaluate BODY, with variables let-bound for CONTEXT (if any).
+`funcall'."
+ (declare (indent 2))
+ `(let* ((vars (and ,context (mu4e-context-vars ,context))))
+ (progv ;; XXX: perhaps use eval's lexical environment instead of progv?
+ (mapcar (lambda(cell) (car cell)) vars)
+ (mapcar (lambda(cell) (cdr cell)) vars)
+ (eval ,@body))))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; the standard folders can be functions too
+(defun mu4e~get-folder (foldervar msg)
+ "Within the mu-context of MSG, get message folder FOLDERVAR.
+If FOLDER is a string, return it, if it is a function, evaluate
+this function with MSG as parameter (which may be `nil'), and
+return the result."
+ (unless (member foldervar
+ '(mu4e-sent-folder mu4e-drafts-folder
+ mu4e-trash-folder mu4e-refile-folder))
+ (mu4e-error "Folder must be one of mu4e-(sent|drafts|trash|refile)-folder"))
+ ;; get the value with the vars for the relevants context let-bound
+ (with~mu4e-context-vars (mu4e-context-determine msg nil)
+ (let* ((folder (symbol-value foldervar))
+ (val
+ (cond
+ ((stringp folder) folder)
+ ((functionp folder) (funcall folder msg))
+ (t (mu4e-error "unsupported type for %S" folder)))))
+ (or val (mu4e-error "%S evaluates to nil" foldervar)))))
+
+(defun mu4e-get-drafts-folder (&optional msg)
+ "Get the sent folder. See `mu4e-drafts-folder'."
+ (mu4e~get-folder 'mu4e-drafts-folder msg))
+
+(defun mu4e-get-refile-folder (&optional msg)
+ "Get the folder for refiling. See `mu4e-refile-folder'."
+ (mu4e~get-folder 'mu4e-refile-folder msg))
+
+(defun mu4e-get-sent-folder (&optional msg)
+ "Get the sent folder. See `mu4e-sent-folder'."
+ (mu4e~get-folder 'mu4e-sent-folder msg))
+
+(defun mu4e-get-trash-folder (&optional msg)
+ "Get the sent folder. See `mu4e-trash-folder'."
+ (mu4e~get-folder 'mu4e-trash-folder msg))
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+(defun mu4e-remove-file-later (filename)
+ "Remove FILENAME in a few seconds."
+ (run-at-time "10 sec" nil
+ (lambda () (ignore-errors (delete-file filename)))))
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+(defun mu4e-make-temp-file (ext)
+ "Create a temporary file with extension EXT. The file will
+self-destruct in a few seconds, enough to open it in another
+program."
+ (let ((tmpfile (make-temp-file "mu4e-" nil (concat "." ext))))
+ (mu4e-remove-file-later tmpfile)
+ tmpfile))
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; mu4e-attachment-dir is either a string or a function that takes a filename
+;; and the mime-type as argument, either (or both) which can be nil
+(defun mu4e~get-attachment-dir (&optional fname mimetype)
+ "Get the directory for saving attachments from
+`mu4e-attachment-dir' (which can be either a string or a function,
+see its docstring)."
+ (let
+ ((dir
+ (cond
+ ((stringp mu4e-attachment-dir)
+ mu4e-attachment-dir)
+ ((functionp mu4e-attachment-dir)
+ (funcall mu4e-attachment-dir fname mimetype))
+ (t
+ (mu4e-error "unsupported type for mu4e-attachment-dir" )))))
+ (if dir
+ (expand-file-name dir)
+ (mu4e-error (mu4e-error "mu4e-attachment-dir evaluates to nil")))))
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+(defun mu4e~guess-maildir (path)
+ "Guess the maildir for some path, or nil if cannot find it."
+ (let ((idx (string-match mu4e-maildir path)))
+ (when (and idx (zerop idx))
+ (replace-regexp-in-string
+ mu4e-maildir
+ ""
+ (expand-file-name
+ (concat path "/../.."))))))
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+(defun mu4e-create-maildir-maybe (dir)
+ "Offer to create maildir DIR if it does not exist yet.
+Return t if the dir already existed, or an attempt has been made to
+create it -- we cannot be sure creation succeeded here, since this
+is done asynchronously. Otherwise, return nil. NOte, DIR has to be
+an absolute path."
+ (if (and (file-exists-p dir) (not (file-directory-p dir)))
+ (mu4e-error "%s exists, but is not a directory." dir))
+ (cond
+ ((file-directory-p dir) t)
+ ((yes-or-no-p (mu4e-format "%s does not exist yet. Create now?" dir))
+ (mu4e~proc-mkdir dir) t)
+ (t nil)))
+
+(defun mu4e-format (frm &rest args)
+ "Create [mu4e]-prefixed string based on format FRM and ARGS."
+ (concat
+ "[" (propertize "mu4e" 'face 'mu4e-title-face) "] "
+ (apply 'format frm args)))
+
+(defun mu4e-message (frm &rest args)
+ "Like `message', but prefixed with mu4e.
+If we're waiting for user-input or if there's some message in the
+echo area, don't show anything."
+ (unless (or (active-minibuffer-window))
+ (message "%s" (apply 'mu4e-format frm args))))
+
+(defun mu4e-index-message (frm &rest args)
+ "Like `mu4e-message', but specifically for
+index-messages. Doesn't display anything if
+`mu4e-hide-index-messages' is non-nil. "
+ (unless mu4e-hide-index-messages
+ (apply 'mu4e-message frm args)))
+
+(defun mu4e-error (frm &rest args)
+ "Create [mu4e]-prefixed error based on format FRM and ARGS.
+Does a local-exit and does not return, and raises a
+debuggable (backtrace) error."
+ (mu4e-log 'error (apply 'mu4e-format frm args))
+ (error "%s" (apply 'mu4e-format frm args)))
+
+;; the user-error function is only available in emacs-trunk
+(unless (fboundp 'user-error)
+ (defalias 'user-error 'error))
+
+(defun mu4e-warn (frm &rest args)
+ "Create [mu4e]-prefixed warning based on format FRM and ARGS.
+Does a local-exit and does not return. In emacs versions below
+24.2, the functions is the same as `mu4e-error'."
+ (mu4e-log 'error (apply 'mu4e-format frm args))
+ (user-error "%s" (apply 'mu4e-format frm args)))
+
+(defun mu4e~read-char-choice (prompt choices)
+ "Read and return one of CHOICES, prompting for PROMPT.
+Any input that is not one of CHOICES is ignored. This mu4e's
+version of `read-char-choice', that becomes case-insentive after
+trying an exact match."
+ (let ((choice) (chosen) (inhibit-quit nil))
+ (while (not chosen)
+ (message nil);; this seems needed...
+ (setq choice (read-char-exclusive prompt))
+ (setq chosen (or (member choice choices)
+ (member (downcase choice) choices)
+ (member (upcase choice) choices))))
+ (car chosen)))
+
+(defun mu4e-read-option (prompt options)
+ "Ask user for an option from a list on the input area.
+PROMPT describes a multiple-choice question to the user.
+OPTIONS describe the options, and is a list of cells describing
+particular options. Cells have the following structure:
+
+ (OPTIONSTRING . RESULT)
+
+where OPTIONSTRING is a non-empty string describing the
+option. The first character of OPTIONSTRING is used as the
+shortcut, and obviously all shortcuts must be different, so you
+can prefix the string with an uniquifying character.
+
+The options are provided as a list for the user to choose from;
+user can then choose by typing CHAR. Example:
+ (mu4e-read-option \"Choose an animal: \"
+ '((\"Monkey\" . monkey) (\"Gnu\" . gnu) (\"xMoose\" . moose)))
+
+User now will be presented with a list: \"Choose an animal:
+ [M]onkey, [G]nu, [x]Moose\".
+
+Function will return the cdr of the list element."
+ (let* ((prompt (mu4e-format "%s" prompt))
+ (chosen)
+ (optionsstr
+ (mapconcat
+ (lambda (option)
+ ;; try to detect old-style options, and warn
+ (when (characterp (car-safe (cdr-safe option)))
+ (mu4e-error
+ (concat "Please use the new format for options/actions; "
+ "see the manual")))
+ (let* ((kar (substring (car option) 0 1))
+ (val (cdr option)))
+ (concat
+ "[" (propertize kar 'face 'mu4e-highlight-face) "]"
+ (substring (car option) 1))))
+ options ", "))
+ (response
+ (mu4e~read-char-choice
+ (concat prompt optionsstr
+ " [" (propertize "C-g" 'face 'mu4e-highlight-face)
+ " to cancel]")
+ ;; the allowable chars
+ (map 'list (lambda(elm) (string-to-char (car elm))) options)))
+ (chosen
+ (find-if
+ (lambda (option) (eq response (string-to-char (car option))))
+ options)))
+ (if chosen
+ (cdr chosen)
+ (mu4e-warn "Unknown shortcut '%c'" response))))
+
+(defun mu4e~get-maildirs-1 (path mdir)
+ "Get maildirs under path, recursively, as a list of relative paths."
+ (let ((dirs)
+ (dentries
+ (ignore-errors
+ (directory-files-and-attributes
+ (concat path mdir) nil
+ "^[^.]\\|\\.[^.][^.]" t))))
+ (dolist (dentry dentries)
+ (when (and (booleanp (cadr dentry)) (cadr dentry))
+ (if (file-accessible-directory-p
+ (concat mu4e-maildir "/" mdir "/" (car dentry) "/cur"))
+ (setq dirs (cons (concat mdir (car dentry)) dirs)))
+ (unless (member (car dentry) '("cur" "new" "tmp"))
+ (setq dirs (append dirs (mu4e~get-maildirs-1 path
+ (concat mdir (car dentry) "/")))))))
+ dirs))
+
+(defvar mu4e-cache-maildir-list nil
+ "Whether to cache the list of maildirs; set it to t if you find
+that generating the list on the fly is too slow. If you do, you
+can set `mu4e-maildir-list' to nil to force regenerating the
+cache the next time `mu4e-get-maildirs' gets called.")
+
+(defvar mu4e-maildir-list nil
+ "Cached list of maildirs.")
+
+(defun mu4e-get-maildirs ()
+ "Get maildirs under `mu4e-maildir', recursively, as a list of
+relative paths (ie., /archive, /sent etc.). Most of the work is
+done in `mu4e-get-maildirs-1'. Note, these results are /cached/, so
+the list of maildirs will not change until you restart mu4e."
+ (unless mu4e-maildir (mu4e-error "`mu4e-maildir' is not defined"))
+ (unless (and mu4e-maildir-list mu4e-cache-maildir-list)
+ (setq mu4e-maildir-list
+ (sort
+ (append
+ (when (file-accessible-directory-p
+ (concat mu4e-maildir "/cur")) '("/"))
+ (mu4e~get-maildirs-1 mu4e-maildir "/"))
+ (lambda (s1 s2) (string< (downcase s1) (downcase s2))))))
+ mu4e-maildir-list)
+
+(defun mu4e-ask-maildir (prompt)
+ "Ask the user for a shortcut (using PROMPT) as defined in
+`mu4e-maildir-shortcuts', then return the corresponding folder
+name. If the special shortcut 'o' (for _o_ther) is used, or if
+`mu4e-maildir-shortcuts' is not defined, let user choose from all
+maildirs under `mu4e-maildir'."
+ (let ((prompt (mu4e-format "%s" prompt)))
+ (if (not mu4e-maildir-shortcuts)
+ (funcall mu4e-completing-read-function prompt (mu4e-get-maildirs))
+ (let* ((mlist (append mu4e-maildir-shortcuts '(("ther" . ?o))))
+ (fnames
+ (mapconcat
+ (lambda (item)
+ (concat
+ "["
+ (propertize (make-string 1 (cdr item))
+ 'face 'mu4e-highlight-face)
+ "]"
+ (car item)))
+ mlist ", "))
+ (kar (read-char (concat prompt fnames))))
+ (if (member kar '(?/ ?o)) ;; user chose 'other'?
+ (funcall mu4e-completing-read-function prompt
+ (mu4e-get-maildirs) nil nil "/")
+ (or (car-safe
+ (find-if (lambda (item) (= kar (cdr item)))
+ mu4e-maildir-shortcuts))
+ (mu4e-warn "Unknown shortcut '%c'" kar)))))))
+
+
+(defun mu4e-ask-maildir-check-exists (prompt)
+ "Like `mu4e-ask-maildir', but check for existence of the maildir,
+and offer to create it if it does not exist yet."
+ (let* ((mdir (mu4e-ask-maildir prompt))
+ (fullpath (concat mu4e-maildir mdir)))
+ (unless (file-directory-p fullpath)
+ (and (yes-or-no-p
+ (mu4e-format "%s does not exist. Create now?" fullpath))
+ (mu4e~proc-mkdir fullpath)))
+ mdir))
+
+(defun mu4e-ask-bookmark (prompt &optional kar)
+ "Ask the user for a bookmark (using PROMPT) as defined in
+`mu4e-bookmarks', then return the corresponding query."
+ (unless mu4e-bookmarks (mu4e-error "No bookmarks defined"))
+ (let* ((prompt (mu4e-format "%s" prompt))
+ (bmarks
+ (mapconcat
+ (lambda (bm)
+ (let ((query (nth 0 bm)) (title (nth 1 bm)) (key (nth 2 bm)))
+ (concat
+ "[" (propertize (make-string 1 key)
+ 'face 'mu4e-highlight-face)
+ "]"
+ title))) mu4e-bookmarks ", "))
+ (kar (read-char (concat prompt bmarks))))
+ (mu4e-get-bookmark-query kar)))
+
+
+(defun mu4e-get-bookmark-query (kar)
+ "Get the corresponding bookmarked query for shortcut character
+KAR, or raise an error if none is found."
+ (let* ((chosen-bm
+ (or (find-if
+ (lambda (bm)
+ (= kar (nth 2 bm)))
+ mu4e-bookmarks)
+ (mu4e-warn "Unknown shortcut '%c'" kar)))
+ (expr (nth 0 chosen-bm))
+ (query (eval expr)))
+ (if (stringp query)
+ query
+ (mu4e-warn "Expression must evaluate to query string ('%S')" expr))))
+
+
+(defun mu4e-bookmark-define (query descr key)
+ "Define a bookmark for QUERY with description DESCR and short
+character KEY in the list of `mu4e-bookmarks'. This replaces any
+existing bookmark with KEY."
+ (setq mu4e-bookmarks (remove-if (lambda (bm) (= (nth 2 bm) key)) mu4e-bookmarks))
+ (add-to-list 'mu4e-bookmarks (list query descr key) t))
+
+
+;;; converting flags->string and vice-versa ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+(defun mu4e~flags-to-string-raw (flags)
+ "Convert a list of flags into a string as seen in Maildir
+message files; flags are symbols draft, flagged, new, passed,
+replied, seen, trashed and the string is the concatenation of the
+uppercased first letters of these flags, as per [1]. Other flags
+than the ones listed here are ignored.
+Also see `mu4e-flags-to-string'.
+\[1\]: http://cr.yp.to/proto/maildir.html"
+ (when flags
+ (let ((kar (case (car flags)
+ ('draft ?D)
+ ('flagged ?F)
+ ('new ?N)
+ ('passed ?P)
+ ('replied ?R)
+ ('seen ?S)
+ ('trashed ?T)
+ ('attach ?a)
+ ('encrypted ?x)
+ ('signed ?s)
+ ('unread ?u))))
+ (concat (and kar (string kar))
+ (mu4e~flags-to-string-raw (cdr flags))))))
+
+(defun mu4e-flags-to-string (flags)
+ "Remove duplicates and sort the output of `mu4e~flags-to-string-raw'."
+ (concat
+ (sort (remove-duplicates
+ (append (mu4e~flags-to-string-raw flags) nil)) '>)))
+
+(defun mu4e~string-to-flags-1 (str)
+ "Convert a string with message flags as seen in Maildir
+messages into a list of flags in; flags are symbols draft,
+flagged, new, passed, replied, seen, trashed and the string is
+the concatenation of the uppercased first letters of these flags,
+as per [1]. Other letters than the ones listed here are ignored.
+Also see `mu4e-flags-to-string'.
+\[1\]: http://cr.yp.to/proto/maildir.html."
+ (when (/= 0 (length str))
+ (let ((flag
+ (case (string-to-char str)
+ (?D 'draft)
+ (?F 'flagged)
+ (?P 'passed)
+ (?R 'replied)
+ (?S 'seen)
+ (?T 'trashed))))
+ (append (when flag (list flag))
+ (mu4e~string-to-flags-1 (substring str 1))))))
+
+(defun mu4e-string-to-flags (str)
+ "Convert a string with message flags as seen in Maildir messages
+into a list of flags in; flags are symbols draft, flagged, new,
+passed, replied, seen, trashed and the string is the concatenation
+of the uppercased first letters of these flags, as per [1]. Other
+letters than the ones listed here are ignored. Also see
+`mu4e-flags-to-string'. \[1\]:
+http://cr.yp.to/proto/maildir.html "
+ ;; "Remove duplicates from the output of `mu4e~string-to-flags-1'"
+ (remove-duplicates (mu4e~string-to-flags-1 str)))
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+
+(defun mu4e-display-size (size)
+ "Get a string representation of SIZE (in bytes)."
+ (cond
+ ((>= size 1000000) (format "%2.1fM" (/ size 1000000.0)))
+ ((and (>= size 1000) (< size 1000000))
+ (format "%2.1fK" (/ size 1000.0)))
+ ((< size 1000) (format "%d" size))
+ (t (propertize "?" 'face 'mu4e-system-face))))
+
+
+(defun mu4e-display-manual ()
+ "Display the mu4e manual page for the current mode.
+Or go to the top level if there is none."
+ (interactive)
+ (info (case major-mode
+ ('mu4e-main-mode "(mu4e)Main view")
+ ('mu4e-headers-mode "(mu4e)Headers view")
+ ('mu4e-view-mode "(mu4e)Message view")
+ (t "mu4e"))))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+(defun mu4e-last-query ()
+ "Get the most recent query or nil if there is none."
+ (when (buffer-live-p mu4e~headers-buffer)
+ (with-current-buffer mu4e~headers-buffer
+ mu4e~headers-last-query)))
+
+(defun mu4e-select-other-view ()
+ "When the headers view is selected, select the message view (if
+that has a live window), and vice versa."
+ (interactive)
+ (let* ((other-buf
+ (cond
+ ((eq major-mode 'mu4e-headers-mode)
+ mu4e~view-buffer)
+ ((eq major-mode 'mu4e-view-mode)
+ mu4e~headers-buffer)))
+ (other-win (and other-buf (get-buffer-window other-buf))))
+ (if (window-live-p other-win)
+ (select-window other-win)
+ (mu4e-message "No window to switch to"))))
+
+
+(defconst mu4e-output-buffer-name "*mu4e-output*"
+ "*internal* Name of the mu4e output buffer.")
+
+(defun mu4e-process-file-through-pipe (path pipecmd)
+ "Process file at PATH through a pipe with PIPECMD."
+ (let ((buf (get-buffer-create mu4e-output-buffer-name)))
+ (with-current-buffer buf
+ (let ((inhibit-read-only t))
+ (erase-buffer)
+ (call-process-shell-command pipecmd path t t)
+ (view-mode)))
+ (switch-to-buffer buf)))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+(defvar mu4e~lists-hash nil
+ "Hashtable of mailing-list-id => shortname, based on
+ `mu4e~mailing-lists' and `mu4e-user-mailing-lists'.")
+
+(defun mu4e-get-mailing-list-shortname (list-id)
+ "Get the shortname for a mailing-list with list-id LIST-ID. based
+on `mu4e~mailing-lists', `mu4e-user-mailing-lists', and
+`mu4e-mailing-list-patterns'."
+ (unless mu4e~lists-hash
+ (setq mu4e~lists-hash (make-hash-table :test 'equal))
+ (dolist (cell mu4e~mailing-lists)
+ (puthash (car cell) (cdr cell) mu4e~lists-hash))
+ (dolist (cell mu4e-user-mailing-lists)
+ (puthash (car cell) (cdr cell) mu4e~lists-hash)))
+ (or
+ (gethash list-id mu4e~lists-hash)
+ (and (boundp 'mu4e-mailing-list-patterns)
+ (cl-member-if
+ (lambda (pattern)
+ (string-match pattern list-id))
+ mu4e-mailing-list-patterns)
+ (match-string 1 list-id))
+ ;; if it's not in the db, take the part until the first dot if there is one;
+ ;; otherwise just return the whole thing
+ (if (string-match "\\([^.]*\\)\\." list-id)
+ (match-string 1 list-id)
+ list-id)))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+(defvar mu4e-index-updated-hook nil
+ "Hook run when the indexing process had one or more updated messages.
+This can be used as a simple way to invoke some action when new
+messages appear, but note that an update in the index does not
+necessarily mean a new message.")
+
+;; some handler functions for server messages
+;;
+(defun mu4e-info-handler (info)
+ "Handler function for (:info ...) sexps received from the server
+process."
+ (let ((type (plist-get info :info)))
+ (cond
+ ((eq type 'add) t) ;; do nothing
+ ((eq type 'index)
+ (if (eq (plist-get info :status) 'running)
+ (mu4e-index-message "Indexing... processed %d, updated %d"
+ (plist-get info :processed) (plist-get info :updated))
+ (progn
+ (mu4e-index-message
+ "Indexing completed; processed %d, updated %d, cleaned-up %d"
+ (plist-get info :processed) (plist-get info :updated)
+ (plist-get info :cleaned-up))
+ (unless (zerop (plist-get info :updated))
+ (run-hooks 'mu4e-index-updated-hook)))))
+ ((plist-get info :message)
+ (mu4e-index-message "%s" (plist-get info :message))))))
+
+(defun mu4e-error-handler (errcode errmsg)
+ "Handler function for showing an error."
+ ;; don't use mu4e-error here; it's running in the process filter context
+ (case errcode
+ (4 (user-error "No matches for this search query."))
+ (t (error "Error %d: %s" errcode errmsg))))
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+;;; RFC2822 handling of phrases in mail-addresses
+;;; The optional display-name contains a phrase, it sits before the angle-addr
+;;; as specified in RFC2822 for email-addresses in header fields.
+;;; contributed by jhelberg
+
+(defun mu4e~rfc822-phrase-type (ph)
+ "Return either atom, quoted-string, a corner-case or nil. This
+ checks for empty string first. Then quotes around the phrase
+ (returning 'rfc822-quoted-string). Then whether there is a quote
+ inside the phrase (returning 'rfc822-containing-quote).
+ The reverse of the RFC atext definition is then tested.
+ If it matches, nil is returned, if not, it is an 'rfc822-atom, which
+ is returned."
+ (cond
+ ((= (length ph) 0) 'rfc822-empty)
+ ((= (aref ph 0) ?\")
+ (if (string-match "\"\\([^\"\\\n]\\|\\\\.\\|\\\\\n\\)*\"" ph)
+ 'rfc822-quoted-string
+ 'rfc822-containing-quote)) ; starts with quote, but doesn't end with one
+ ((string-match-p "[\"]" ph) 'rfc822-containing-quote)
+ ((string-match-p "[\000-\037()\*<>@,;:\\\.]+" ph) nil)
+ (t 'rfc822-atom)))
+
+(defun mu4e~rfc822-quoteit (ph)
+ "Quote RFC822 phrase only if necessary.
+ Atoms and quoted strings don't need quotes. The rest do. In
+ case a phrase contains a quote, it will be escaped."
+ (let ((type (mu4e~rfc822-phrase-type ph)))
+ (cond
+ ((eq type 'rfc822-atom) ph)
+ ((eq type 'rfc822-quoted-string) ph)
+ ((eq type 'rfc822-containing-quote)
+ (format "\"%s\""
+ (replace-regexp-in-string "\"" "\\\\\"" ph)))
+ (t (format "\"%s\"" ph)))))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+(defsubst mu4e~process-contact (contact)
+ "Process CONTACT, and either return nil when it should not be included,
+or (rfc822-string . CONTACT) otherwise."
+ (when mu4e-contact-rewrite-function
+ (setq contact (funcall mu4e-contact-rewrite-function contact)))
+ (when contact
+ (let ((name (plist-get contact :name))
+ (mail (plist-get contact :mail))
+ (ignore-rx (or mu4e-compose-complete-ignore-address-regexp "$^")))
+ (when (and mail (not (string-match ignore-rx mail)))
+ (cons
+ (if name (format "%s <%s>" (mu4e~rfc822-quoteit name) mail) mail)
+ contact)))))
+
+
+(defun mu4e~sort-contacts (contacts)
+ "Destructively sort contacts (only for cycling) in order of
+ 'mostly likely contact'.t See the code for the detail"
+ (let* ((now (+ (float-time) 3600)) ;; allow for clock diffs
+ (recent (- (float-time) (* 15 24 3600))))
+ (sort* contacts
+ (lambda (c1 c2)
+ (let* ( (c1 (cdr c1)) (c2 (cdr c2))
+ (personal1 (plist-get c1 :personal))
+ (personal2 (plist-get c2 :personal))
+ ;; note: freq, tstamp can only be missing if the rewrite
+ ;; function removed them. If the rewrite function changed the
+ ;; contact somehow, we guess it's important.
+ (freq1 (or (plist-get c1 :freq) 500))
+ (freq2 (or (plist-get c2 :freq) 500))
+ (tstamp1 (or (plist-get c1 :tstamp) now))
+ (tstamp2 (or (plist-get c2 :tstamp) now)))
+ ;; only one is personal? if so, that one comes first
+ (if (not (equal personal1 personal2))
+ (if personal1 t nil)
+ ;; only one is recent? that one comes first
+ (if (not (equal (> tstamp1 recent) (> tstamp2 recent)))
+ (> tstamp1 tstamp2)
+ ;; otherwise, use the frequency
+ (> freq1 freq2))))))))
+
+(defun mu4e~sort-contacts-for-completion (contacts)
+ "Takes CONTACTS, which is a list of RFC-822 addresses, and sort them based
+on the ranking in `mu4e~contacts.'"
+ (sort* contacts
+ (lambda (c1 c2)
+ (let ((rank1 (gethash c1 mu4e~contacts))
+ (rank2 (gethash c2 mu4e~contacts)))
+ (< rank1 rank2)))))
+
+;; start and stopping
+(defun mu4e~fill-contacts (contact-data)
+ "We receive a list of contacts, which each contact of the form
+ (:me NAME :mail EMAIL :tstamp TIMESTAMP :freq FREQUENCY)
+and fill the hash `mu4e~contacts-for-completion' with it, with
+each contact mapped to an integer for their ranking.
+
+This is used by the completion function in mu4e-compose."
+ (let ((contacts) (rank 0))
+ (dolist (contact contact-data)
+ (let ((contact-maybe (mu4e~process-contact contact)))
+ ;; note, this gives cells (rfc822-address . contact)
+ (when contact-maybe (push contact-maybe contacts))))
+ (setq contacts (mu4e~sort-contacts contacts))
+ ;; now, we have our nicely sorted list, map them to a list
+ ;; of increasing integers. We use that map in the composer
+ ;; to sort them there. It would have been so much easier if emacs
+ ;; allowed us to use the sorted-list as-is, but no such luck.
+ (setq mu4e~contacts (make-hash-table :test 'equal :weakness nil
+ :size (length contacts)))
+ (dolist (contact contacts)
+ (puthash (car contact) rank mu4e~contacts)
+ (incf rank))
+ (mu4e-index-message "Contacts received: %d"
+ (hash-table-count mu4e~contacts))))
+
+(defun mu4e~check-requirements ()
+ "Check for the settings required for running mu4e."
+ (unless (>= emacs-major-version 23)
+ (mu4e-error "Emacs >= 23.x is required for mu4e"))
+ (when mu4e~server-props
+ (let ((version (plist-get mu4e~server-props :version)))
+ (unless (string= version mu4e-mu-version)
+ (mu4e-error "mu server has version %s, but we need %s"
+ version mu4e-mu-version))))
+ (unless (and mu4e-mu-binary (file-executable-p mu4e-mu-binary))
+ (mu4e-error "Please set `mu4e-mu-binary' to the full path to the mu
+ binary."))
+ (unless mu4e-maildir
+ (mu4e-error "Please set `mu4e-maildir' to the full path to your
+ Maildir directory."))
+ ;; expand mu4e-maildir, mu4e-attachment-dir
+ (setq mu4e-maildir (expand-file-name mu4e-maildir))
+ (unless (mu4e-create-maildir-maybe mu4e-maildir)
+ (mu4e-error "%s is not a valid maildir directory" mu4e-maildir))
+ (dolist (var '(mu4e-sent-folder mu4e-drafts-folder
+ mu4e-trash-folder))
+ (unless (and (boundp var) (symbol-value var))
+ (mu4e-error "Please set %S" var))
+ (unless (functionp (symbol-value var)) ;; functions are okay, too
+ (let* ((dir (symbol-value var))
+ (path (concat mu4e-maildir dir)))
+ (unless (string= (substring dir 0 1) "/")
+ (mu4e-error "%S must start with a '/'" dir))
+ (unless (mu4e-create-maildir-maybe path)
+ (mu4e-error "%s (%S) does not exist" path var))))))
+
+
+(defun mu4e-running-p ()
+ "Whether mu4e is running.
+Checks whether the server process is live."
+ (mu4e~proc-running-p))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; starting / getting mail / updating the index
+;;
+;;
+(defvar mu4e~update-timer nil
+ "The mu4e update timer.")
+(defconst mu4e~update-name "*mu4e-update*"
+ "Name of the process and buffer to update mail.")
+(defconst mu4e~update-buffer-height 8
+ "Height of the mu4e message retrieval/update buffer.")
+
+(defvar mu4e~get-mail-ask-password "mu4e get-mail: Enter password: "
+ "Query string for `mu4e-get-mail-command' password.")
+(defvar mu4e~get-mail-password-regexp "^Remote: Enter password: $"
+ "Regexp to match a password query in the `mu4e-get-mail-command' output.")
+
+(defun mu4e~request-contacts ()
+ "If `mu4e-compose-complete-addresses' is non-nil, get/update the
+list of contacts we use for autocompletion; otherwise, do nothing."
+ (when mu4e-compose-complete-addresses
+ (setq mu4e-contacts-func 'mu4e~fill-contacts)
+ (mu4e~proc-contacts
+ mu4e-compose-complete-only-personal
+ (when mu4e-compose-complete-only-after
+ (float-time
+ (apply 'encode-time
+ (mu4e-parse-time-string mu4e-compose-complete-only-after)))))))
+
+(defun mu4e~start (&optional func)
+ "If `mu4e-contexts' have been defined, but we don't have a
+context yet, switch to the matching one, or none matches, the
+first.
+If mu4e is already running, execute function FUNC (if non-nil).
+Otherwise, check various requirements, then start mu4e. When
+successful, call FUNC (if non-nil) afterwards."
+ ;; if we're already running, simply go to the main view
+ (if (mu4e-running-p) ;; already running?
+ (when func (funcall func)) ;; yes! run func if defined
+ (progn
+ ;; no! try to set a context, do some checks, set up pong handler and ping
+ ;; the server maybe switch the context
+ (mu4e~context-autoswitch nil mu4e-context-policy)
+ (lexical-let ((func func))
+ (mu4e~check-requirements)
+ ;; set up the 'pong' handler func
+ (setq mu4e-pong-func
+ (lambda (props)
+ (setq mu4e~server-props props) ;; save props from the server
+ (let ((version (plist-get props :version))
+ (doccount (plist-get props :doccount)))
+ (mu4e~check-requirements)
+ (when func (funcall func))
+ (when (and mu4e-update-interval (null mu4e~update-timer))
+ (setq mu4e~update-timer
+ (run-at-time
+ 0 mu4e-update-interval
+ (lambda () (mu4e-update-mail-and-index mu4e-index-update-in-background)))))
+ (mu4e-message "Started mu4e with %d message%s in store"
+ doccount (if (= doccount 1) "" "s"))))))
+ ;; wake up server
+ (mu4e~proc-ping)
+ ;; maybe request the list of contacts, automatically refresh after
+ ;; reindexing
+ (mu4e~request-contacts)
+ (add-hook 'mu4e-index-updated-hook 'mu4e~request-contacts))))
+
+(defun mu4e-clear-caches ()
+ "Clear any cached resources."
+ (setq
+ mu4e-maildir-list nil
+ mu4e~contacts nil))
+
+(defun mu4e~stop ()
+ "Stop the mu4e session."
+ (when mu4e~update-timer
+ (cancel-timer mu4e~update-timer)
+ (setq mu4e~update-timer nil))
+ (mu4e-clear-caches)
+ (mu4e~proc-kill)
+ ;; kill all main/view/headers buffer
+ (mapcar
+ (lambda (buf)
+ (with-current-buffer buf
+ (when (member major-mode
+ '(mu4e-headers-mode mu4e-view-mode mu4e-main-mode))
+ (kill-buffer))))
+ (buffer-list)))
+
+
+
+(defvar mu4e~progress-reporter nil
+ "Internal, the progress reporter object.")
+
+(defun mu4e~get-mail-process-filter (proc msg)
+ "Filter the output of `mu4e-get-mail-command'.
+Currently the filter only checks if the command asks for a password
+by matching the output against `mu4e~get-mail-password-regexp'.
+The messages are inserted into the process buffer.
+
+Also scrolls to the final line, and update the progress throbber."
+ (when mu4e~progress-reporter
+ (progress-reporter-update mu4e~progress-reporter))
+
+ (when (string-match mu4e~get-mail-password-regexp msg)
+ (if (process-get proc 'x-interactive)
+ (process-send-string proc
+ (concat (read-passwd mu4e~get-mail-ask-password)
+ "\n"))
+ ;; TODO kill process?
+ (mu4e-error "Unrecognized password request")))
+ (when (process-buffer proc)
+ (let ((inhibit-read-only t)
+ (procwin (get-buffer-window (process-buffer proc))))
+ ;; Insert at end of buffer. Leave point alone.
+ (with-current-buffer (process-buffer proc)
+ (goto-char (point-max))
+ (insert msg))
+ ;; Auto-scroll unless user is interacting with the window.
+ (when (and (window-live-p procwin)
+ (not (eq (selected-window) procwin)))
+ (with-selected-window procwin
+ (goto-char (point-max)))))))
+
+(defun mu4e-update-index ()
+ "Update the mu4e index."
+ (interactive)
+ (unless mu4e-maildir
+ (mu4e-error "`mu4e-maildir' is not defined"))
+ (mu4e~proc-index mu4e-maildir mu4e-user-mail-address-list))
+
+(defvar mu4e~update-buffer nil
+ "Internal, store the buffer of the update process when
+ updating.")
+
+(define-derived-mode mu4e~update-mail-mode special-mode "mu4e:update"
+ "Major mode used for retrieving new e-mail messages in `mu4e'.")
+
+(define-key mu4e~update-mail-mode-map (kbd "q") 'mu4e-interrupt-update-mail)
+
+(defun mu4e~temp-window (buf height)
+ "Create a temporary window with HEIGHT at the bottom of the
+frame to display buffer BUF."
+ (let ((win
+ (split-window
+ (frame-root-window)
+ (- (window-height (frame-root-window)) height))))
+ (set-window-buffer win buf)
+ (set-window-dedicated-p win t)
+ win))
+
+(defun mu4e~update-sentinel-func (proc msg)
+ "Sentinel function for the update process."
+ (when mu4e~progress-reporter
+ (progress-reporter-done mu4e~progress-reporter)
+ (setq mu4e~progress-reporter nil))
+ (let* ((status (process-status proc))
+ (code (process-exit-status proc))
+ (maybe-error (or (not (eq status 'exit)) (/= code 0)))
+ (buf (and (buffer-live-p mu4e~update-buffer) mu4e~update-buffer))
+ (win (and buf (get-buffer-window buf))))
+ (message nil)
+ (if maybe-error
+ (progn
+ (when mu4e-index-update-error-warning
+ (mu4e-message "Update process returned with non-zero exit code")
+ (sit-for 5))
+ (when mu4e-index-update-error-continue
+ (mu4e-update-index)))
+ (mu4e-update-index))
+ (if (window-live-p win)
+ (with-selected-window win (kill-buffer-and-window))
+ (when (buffer-live-p buf) (kill-buffer buf)))))
+
+;; complicated function, as it:
+;; - needs to check for errors
+;; - (optionally) pop-up a window
+;; - (optionally) check password requests
+(defun mu4e~update-mail-and-index-real (run-in-background)
+ "Get a new mail by running `mu4e-get-mail-command'. If
+RUN-IN-BACKGROUND is non-nil (or called with prefix-argument),
+run in the background; otherwise, pop up a window."
+ (let* ((process-connection-type t)
+ (proc (start-process-shell-command
+ "mu4e-update" " *mu4e-update*"
+ mu4e-get-mail-command))
+ (buf (process-buffer proc))
+ (win (or run-in-background
+ (mu4e~temp-window buf mu4e~update-buffer-height))))
+ (setq mu4e~update-buffer buf)
+ (when (window-live-p win)
+ (with-selected-window win
+ ;; ;;(switch-to-buffer buf)
+ ;; (set-window-dedicated-p win t)
+ (erase-buffer)
+ (insert "\n") ;; FIXME -- needed so output start
+ (mu4e~update-mail-mode)))
+ (setq mu4e~progress-reporter
+ (unless mu4e-hide-index-messages
+ (make-progress-reporter
+ (mu4e-format "Retrieving mail..."))))
+ (set-process-sentinel proc 'mu4e~update-sentinel-func)
+ ;; if we're running in the foreground, handle password requests
+ (unless run-in-background
+ (process-put proc 'x-interactive (not run-in-background))
+ (set-process-filter proc 'mu4e~get-mail-process-filter))))
+
+(defun mu4e-update-mail-and-index (run-in-background)
+ "Get a new mail by running `mu4e-get-mail-command'. If
+run-in-background is non-nil (or called with prefix-argument), run
+in the background; otherwise, pop up a window."
+ (interactive "P")
+ (unless mu4e-get-mail-command
+ (mu4e-error "`mu4e-get-mail-command' is not defined"))
+ (if (and (buffer-live-p mu4e~update-buffer)
+ (process-live-p (get-buffer-process mu4e~update-buffer)))
+ (mu4e-message "Update process is already running")
+ (progn
+ (run-hooks 'mu4e-update-pre-hook)
+ (mu4e~update-mail-and-index-real run-in-background))))
+
+(defun mu4e-interrupt-update-mail ()
+ "Stop the update process by sending SIGINT to it."
+ (interactive)
+ (let* ((proc (and (buffer-live-p mu4e~update-buffer)
+ (get-buffer-process mu4e~update-buffer))))
+ (when (process-live-p proc)
+ (interrupt-process proc t))))
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; logging / debugging
+(defvar mu4e~log-max-lines 1200
+ "*internal* Last <n> number of lines to keep around in the buffer.")
+(defconst mu4e~log-buffer-name "*mu4e-log*"
+ "*internal* Name of the logging buffer.")
+
+(defun mu4e-log (type frm &rest args)
+ "Write a message of TYPE with format-string FRM and ARGS in
+*mu4e-log* buffer, if the variable mu4e-debug is non-nil. Type is
+either 'to-server, 'from-server or 'misc. This function is meant for debugging."
+ (when mu4e-debug
+ (with-current-buffer (get-buffer-create mu4e~log-buffer-name)
+ (view-mode)
+ (setq buffer-undo-list t)
+ (let* ((inhibit-read-only t)
+ (tstamp (propertize (format-time-string "%Y-%m-%d %T"
+ (current-time))
+ 'face 'font-lock-string-face))
+ (msg-face
+ (case type
+ (from-server 'font-lock-type-face)
+ (to-server 'font-lock-function-name-face)
+ (misc 'font-lock-variable-name-face)
+ (error 'font-lock-warning-face)
+ (otherwise (mu4e-error "Unsupported log type"))))
+ (msg (propertize (apply 'format frm args) 'face msg-face)))
+ (goto-char (point-max))
+ (insert tstamp
+ (case type
+ (from-server " <- ")
+ (to-server " -> ")
+ (error " !! ")
+ (otherwise " "))
+ msg "\n")
+
+ ;; if `mu4e-log-max-lines is specified and exceeded, clearest the oldest
+ ;; lines
+ (when (numberp mu4e~log-max-lines)
+ (let ((lines (count-lines (point-min) (point-max))))
+ (when (> lines mu4e~log-max-lines)
+ (goto-char (point-max))
+ (forward-line (- mu4e~log-max-lines lines))
+ (beginning-of-line)
+ (delete-region (point-min) (point)))))))))
+
+(defun mu4e-toggle-logging ()
+ "Toggle between enabling/disabling debug-mode (in debug-mode,
+mu4e logs some of its internal workings to a log-buffer. See
+`mu4e-visit-log'."
+ (interactive)
+ (mu4e-log 'misc "logging disabled")
+ (setq mu4e-debug (not mu4e-debug))
+ (mu4e-message "debug logging has been %s"
+ (if mu4e-debug "enabled" "disabled"))
+ (mu4e-log 'misc "logging enabled"))
+
+(defun mu4e-show-log ()
+ "Visit the mu4e debug log."
+ (interactive)
+ (let ((buf (get-buffer mu4e~log-buffer-name)))
+ (unless (buffer-live-p buf)
+ (mu4e-warn "No debug log available"))
+ (switch-to-buffer buf)))
+
+
+(defun mu4e-split-ranges-to-numbers (str n)
+ "Convert STR containing attachment numbers into a list of numbers.
+STR is a string; N is the highest possible number in the list.
+This includes expanding e.g. 3-5 into 3,4,5. If the letter
+\"a\" ('all')) is given, that is expanded to a list with numbers [1..n]."
+ (let ((str-split (split-string str))
+ beg end list)
+ (dolist (elem str-split list)
+ ;; special number "a" converts into all attachments 1-N.
+ (when (equal elem "a")
+ (setq elem (concat "1-" (int-to-string n))))
+ (if (string-match "\\([0-9]+\\)-\\([0-9]+\\)" elem)
+ ;; we have found a range A-B, which needs converting
+ ;; into the numbers A, A+1, A+2, ... B.
+ (progn
+ (setq beg (string-to-number (match-string 1 elem))
+ end (string-to-number (match-string 2 elem)))
+ (while (<= beg end)
+ (add-to-list 'list beg 'append)
+ (setq beg (1+ beg))))
+ ;; else just a number
+ (add-to-list 'list (string-to-number elem) 'append)))
+ ;; Check that all numbers are valid.
+ (mapc
+ #'(lambda (x)
+ (cond
+ ((> x n)
+ (mu4e-warn "Attachment %d bigger than maximum (%d)" x n))
+ ((< x 1)
+ (mu4e-warn "Attachment number must be greater than 0 (%d)" x))))
+ list)))
+
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+(defvar mu4e-imagemagick-identify "identify"
+ "Name/path of the Imagemagick 'identify' program.")
+
+(defun mu4e-display-image (imgpath &optional maxwidth maxheight)
+ "Display image IMG at point; optionally specify MAXWIDTH and
+MAXHEIGHT. Function tries to use imagemagick if available (ie.,
+emacs was compiled with inmagemagick support); otherwise MAXWIDTH
+and MAXHEIGHT are ignored."
+ (let* ((have-im (and (fboundp 'imagemagick-types)
+ (imagemagick-types))) ;; hmm, should check for specific type
+ (identify (and have-im maxwidth
+ (executable-find mu4e-imagemagick-identify)))
+ (props (and identify (shell-command-to-string
+ (format "%s -format '%%w' %s"
+ identify (shell-quote-argument imgpath)))))
+ (width (and props (string-to-number props)))
+ (img (if have-im
+ (if (> (or width 0) (or maxwidth 0))
+ (create-image imgpath 'imagemagick nil :width maxwidth)
+ (create-image imgpath 'imagemagick))
+ (create-image imgpath))))
+ (when img
+ (save-excursion
+ (insert "\n")
+ (let ((size (image-size img))) ;; inspired by gnus..
+ (insert-char ?\n
+ (max 0 (round (- (window-height) (or maxheight (cdr size)) 1) 2)))
+ (insert-char ?\.
+ (max 0 (round (- (window-width) (or maxwidth (car size))) 2)))
+ (insert-image img))))))
+
+
+(defun mu4e-hide-other-mu4e-buffers ()
+ "Bury mu4e-buffers (main, headers, view) (and delete all windows
+displaying it). Do _not_ bury the current buffer, though."
+ (interactive)
+ (let ((curbuf (current-buffer)))
+ ;; note: 'walk-windows' does not seem to work correctly when modifying
+ ;; windows; therefore, the doloops here
+ (dolist (frame (frame-list))
+ (dolist (win (window-list frame nil))
+ (with-current-buffer (window-buffer win)
+ (unless (eq curbuf (current-buffer))
+ (when (member major-mode '(mu4e-headers-mode mu4e-view-mode))
+ (when (eq t (window-deletable-p win))
+ (delete-window win))))))) t))
+
+
+(defun mu4e-get-time-date (prompt)
+ "Determine the emacs time value for the time/date entered by user
+ after PROMPT. Formats are all that are accepted by
+ `parse-time-string'."
+ (let ((timestr (read-string (mu4e-format "%s" prompt))))
+ (apply 'encode-time (mu4e-parse-time-string timestr))))
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+(define-derived-mode mu4e-org-mode org-mode "mu4e:org"
+ "Major mode for mu4e documents, derived from
+ `org-mode'.")
+
+(defun mu4e-info (path)
+ "Show a buffer with the information (an org-file) at PATH."
+ (interactive)
+ (unless (file-exists-p path)
+ (mu4e-error "Cannot find %s" path))
+ (lexical-let ((curbuf (current-buffer)))
+ (find-file path)
+ (mu4e-org-mode)
+ (setq buffer-read-only t)
+ (define-key mu4e-org-mode-map (kbd "q")
+ (lambda ()
+ (interactive)
+ (bury-buffer)
+ (switch-to-buffer curbuf)))))
+
+(defun mu4e-about ()
+ "Show the mu4e 'about' page."
+ (interactive)
+ (mu4e-info (concat mu4e-doc-dir "/mu4e-about.org")))
+
+(defun mu4e-news ()
+ "Show the mu4e 'about' page."
+ (interactive)
+ (mu4e-info (concat mu4e-doc-dir "/NEWS.org")))
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+(defun mu4e-refresh-message (path maildir)
+ "Re-parse message at PATH and MAILDIR; if this works, we will
+receive (:info add :path <path> :docid <docid>) as well as (:update
+<msg-sexp>)."
+ (mu4e~proc-add path maildir))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+(defun mu4e~fontify-cited ()
+ "Colorize message content based on the citation level. This is
+used in the view and compose modes."
+ (save-excursion
+ (goto-char (point-min))
+ (when (search-forward-regexp "^\n" nil t) ;; search the first empty line
+ (while (re-search-forward mu4e-cited-regexp nil t)
+ (let* ((level (string-width (replace-regexp-in-string
+ " " "" (match-string 1))))
+ (face (unless (zerop level)
+ (intern-soft (format "mu4e-cited-%d-face" level)))))
+ (when face
+ (add-text-properties (line-beginning-position 1)
+ (line-end-position 1) `(face ,face))))))))
+
+(defun mu4e~fontify-signature ()
+ "Give the message signatures a distinctive color. This is used in
+the view and compose modes."
+ (let ((inhibit-read-only t))
+ (save-excursion
+ ;; give the footer a different color...
+ (goto-char (point-min))
+ (let ((p (search-forward "^-- *$" nil t)))
+ (when p
+ (add-text-properties p (point-max) '(face mu4e-footer-face)))))))
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+(defun mu4e~quote-for-modeline (str)
+ "Quote a string to be used literally in the modeline."
+ (replace-regexp-in-string "%" "%%" str t t))
+
+
+(provide 'mu4e-utils)
+;;; End of mu4e-utils.el
diff --git a/_spacemacs.d/local/mu4e/mu4e-vars.el b/_spacemacs.d/local/mu4e/mu4e-vars.el
new file mode 100644
index 0000000..94c3a86
--- /dev/null
+++ b/_spacemacs.d/local/mu4e/mu4e-vars.el
@@ -0,0 +1,870 @@
+;;; mu4e-vars.el -- part of mu4e, the mu mail user agent
+;;
+;; Copyright (C) 2011-2016 Dirk-Jan C. Binnema
+
+;; Author: Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
+;; Maintainer: Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
+
+;; This file is not part of GNU Emacs.
+;;
+;; 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 <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;;; Code:
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; Customization
+(require 'mu4e-meta)
+(require 'message)
+
+(defgroup mu4e nil
+ "mu4e - mu for emacs"
+ :group 'mail)
+
+(defcustom mu4e-mu-home nil
+ "Location of the mu homedir, or nil for the default."
+ :group 'mu4e
+ :type '(choice (const :tag "Default location" nil)
+ (directory :tag "Specify location"))
+ :safe 'stringp)
+
+(defcustom mu4e-mu-binary (executable-find "mu")
+ "Name of the mu-binary to use.
+If it cannot be found in your PATH, you can specify the full
+path."
+ :type 'file
+ :group 'mu4e
+ :safe 'stringp)
+
+(defcustom mu4e-maildir (expand-file-name "~/Maildir")
+ "The file system path to your Maildir. Must not be a symbolic
+link."
+ :type 'directory
+ :safe 'stringp
+ :group 'mu4e)
+
+(defcustom mu4e-get-mail-command "true"
+ "Shell command to run to retrieve new mail.
+Common values are \"offlineimap\", \"fetchmail\" or \"mbsync\", but
+arbitrary shell-commands can be used.
+
+When set to the literal string \"true\" (the default), the
+command simply finishes succesfully (running the 'true' command)
+without retrieving any mail. This can be useful when mail is
+already retrieved in another way."
+ :type 'string
+ :group 'mu4e
+ :safe 'stringp)
+
+(defcustom mu4e-index-update-error-warning t
+ "Whether to display warnings when we the retrieval process (as
+ per `mu4e-get-mail-command') finished with a non-zero exit code."
+ :type 'boolean
+ :group 'mu4e
+ :safe 'booleanp)
+
+(defcustom mu4e-index-update-error-continue t
+ "Whether to continue with indexing when we the retrieval
+ process (as per `mu4e-get-mail-command') finished with a non-zero
+ exit code."
+ :type 'boolean
+ :group 'mu4e
+ :safe 'booleanp)
+
+(defcustom mu4e-index-update-in-background t
+ "Whether to run the automatic mail retrieval in the
+background."
+ :type 'boolean
+ :group 'mu4e
+ :safe 'booleanp)
+
+(defcustom mu4e-update-interval nil
+ "Number of seconds between automatic calls to retrieve mail and
+update the database. If nil, don't update automatically. Note,
+changes in `mu4e-update-interval' only take effect after restarting
+mu4e."
+ :type '(choice (const :tag "No automatic update" nil)
+ (integer :tag "Seconds"))
+ :group 'mu4e
+ :safe 'integerp)
+
+(defvar mu4e-update-pre-hook nil
+ "Hook run just *before* the mail-retrieval / database updating process starts.
+ You can use this hook for example to `mu4e-get-mail-command' with
+ some specific setting.")
+
+(defvar mu4e-hide-index-messages nil
+ "If non-nil, mu4e does not show the \"Indexing...\" messages, or
+ any messages relating to updated contacts.")
+
+(defcustom mu4e-change-filenames-when-moving nil
+ "When moving messages to different folders, normally mu/mu4e keep
+the base filename the same (the flags-part of the filename may
+change still). With this option set to non-nil, mu4e instead
+changes the filename. This latter behavior works better with some
+IMAP-synchronization programs such as mbsync; the default works
+better with e.g. offlineimap."
+ :type 'boolean
+ :group 'mu4e
+ :safe 'booleanp)
+
+(defcustom mu4e-attachment-dir (expand-file-name "~/")
+ "Default directory for saving attachments.
+This can be either a string (a file system path), or a function
+that takes a filename and the mime-type as arguments, and returns
+the attachment dir. See Info node `(mu4e) Attachments' for details."
+ :type 'directory
+ :group 'mu4e
+ :safe 'stringp)
+
+(defcustom mu4e-user-mail-address-list `(,user-mail-address)
+ "List of e-mail addresses to consider 'my email addresses'.
+I.e. addresses whose presence in an email imply that it is a
+personal message. Note that e-mail addresses are case-sensitive,
+as per RFC531."
+ :type '(repeat (string :tag "Address"))
+ :group 'mu4e)
+
+;; don't use the older vars anymore
+(make-obsolete-variable 'mu4e-user-mail-address-regexp
+ 'mu4e-user-mail-address-list "0.9.9.x")
+
+(make-obsolete-variable 'mu4e-my-email-addresses
+ 'mu4e-user-mail-address-list "0.9.9.x")
+
+(defcustom mu4e-use-fancy-chars nil
+ "Whether to use fancy (Unicode) characters for marks and
+threads. You can customize the exact fancy characters used with
+`mu4e-marks' and various `mu4e-headers-..-mark' and
+`mu4e-headers..-prefix' variables."
+ :type 'boolean
+ :group 'mu4e)
+
+(defcustom mu4e-date-format-long "%c"
+ "Date format to use in the message view, in the format of
+ `format-time-string'."
+ :type 'string
+ :group 'mu4e)
+
+(defvar mu4e-debug nil
+ "When set to non-nil, log debug information to the *mu4e-log* buffer.")
+
+(defcustom mu4e-bookmarks
+ '( ("flag:unread AND NOT flag:trashed" "Unread messages" ?u)
+ ("date:today..now" "Today's messages" ?t)
+ ("date:7d..now" "Last 7 days" ?w)
+ ("mime:image/*" "Messages with images" ?p))
+ "A list of pre-defined queries.
+These will show up in the main screen. Each of the list elements
+is a three-element list of the form (QUERY DESCRIPTION KEY),
+where QUERY is a string with a mu query, DESCRIPTION is a short
+description of the query (this will show up in the UI), and KEY
+is a shortcut key for the query."
+ :type '(repeat (list (string :tag "Query")
+ (string :tag "Description")
+ character))
+ :group 'mu4e)
+
+(defcustom mu4e-split-view 'horizontal
+ "How to show messages / headers.
+A symbol which is either:
+ * `horizontal': split horizontally (headers on top)
+ * `vertical': split vertically (headers on the left).
+ * anything else: don't split (show either headers or messages,
+ not both)
+Also see `mu4e-headers-visible-lines'
+and `mu4e-headers-visible-columns'."
+ :type '(choice (const :tag "Split horizontally" horizontal)
+ (const :tag "Split vertically" vertical)
+ (const :tag "Don't split" nil))
+ :group 'mu4e-headers)
+
+(defcustom mu4e-view-show-images nil
+ "Whether to automatically display attached images in the message
+view buffer."
+ :type 'boolean
+ :group 'mu4e-view)
+
+(make-obsolete-variable 'mu4e-show-images
+ 'mu4e-view-show-images "0.9.9.x")
+
+(defcustom mu4e-confirm-quit t
+ "Whether to confirm to quit mu4e."
+ :type 'boolean
+ :group 'mu4e)
+
+(defcustom mu4e-cited-regexp "^ *\\(\\(>+ ?\\)+\\)"
+ "Regular expression that determines whether a line is a citation."
+ :type 'string
+ :group 'mu4e)
+
+(defcustom mu4e-completing-read-function 'ido-completing-read
+ "Function to be used to receive input from the user with
+completion. This is used to receive the name of the maildir
+to switch to via `mu4e~headers-jump-to-maildir'.
+
+Suggested possible values are:
+ * `completing-read': built-in completion method
+ * `ido-completing-read': dynamic completion within the minibuffer."
+ :type 'function
+ :options '(completing-read ido-completing-read)
+ :group 'mu4e)
+
+(defcustom mu4e-context-policy 'ask-if-none
+ "The policy to determine the context when entering the mu4e main view.
+
+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-compose-context-policy'."
+ :type '(choice
+ (const :tag "Always ask what context to use, even if one matches"
+ '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))
+
+
+;; crypto
+(defgroup mu4e-crypto nil
+ "Crypto-related settings."
+ :group 'mu4e)
+
+(defcustom mu4e-auto-retrieve-keys nil
+ "Attempt to automatically retrieve public keys when needed."
+ :type 'boolean
+ :group 'mu4e-crypto)
+
+(defcustom mu4e-decryption-policy t
+ "Policy for dealing with encrypted parts.
+The setting is a symbol:
+ * t: try to decrypt automatically
+ * `ask': ask before decrypting anything
+ * nil: don't try to decrypt anything."
+ :type '(choice (const :tag "Try to decrypt automatically" t)
+ (const :tag "Ask before decrypting anything" ask)
+ (const :tag "Don't try to decrypt anything" nil))
+ :group 'mu4e-crypto)
+
+;; completion; we put them here rather than in mu4e-compose, as mu4e-utils needs
+;; the variables.
+
+(defgroup mu4e-compose nil
+ "Message-composition related settings."
+ :group 'mu4e)
+
+;; address completion
+(defcustom mu4e-compose-complete-addresses t
+ "Whether to do auto-completion of e-mail addresses."
+ :type 'boolean
+ :group 'mu4e-compose)
+
+(defcustom mu4e-compose-complete-only-personal nil
+ "Whether to consider only 'personal' e-mail addresses,
+i.e. addresses from messages where user was explicitly in one of
+the address fields (this excludes mailing list messages). See
+`mu4e-user-mail-address-list' and the mu-index manpage for details for
+details (in particular, how to define your own e-mail addresses)."
+ :type 'boolean
+ :group 'mu4e-compose)
+
+(defcustom mu4e-compose-complete-only-after "2010-01-01"
+ "Consider only contacts last seen after this date.
+Date must be a string, in a format parseable by
+`org-parse-time-string'. This excludes really old contacts.
+Set to nil to not have any time-based restriction."
+ :type 'string
+ :group 'mu4e-compose)
+
+
+;;; names and mail-addresses can be mapped onto their canonical
+;;; counterpart. use the customizeable function
+;;; mu4e-canonical-contact-function to do that. below the identity
+;;; function for mapping a contact onto the canonical one.
+(defun mu4e-contact-identity (contact)
+ "This returns the name and the mail-address of a contact.
+It is used as the identity function for converting contacts to
+their canonical counterpart; useful as an example."
+ (let ((name (plist-get contact :name))
+ (mail (plist-get contact :mail)))
+ (list :name name :mail mail)))
+
+(defcustom mu4e-contact-rewrite-function nil
+ "Either nil or a function to be used for when processing
+contacts and rewrite them or remove them altogether.
+
+If the function receives the contact as a list of the form
+ (:name NAME :mail EMAIL ... other properties ... )
+(other properties may be there as well)
+
+The function should return either:
+ - nil: remove this contact, or
+- the rewritten cell, or
+- the existing cell as-is
+
+For rewriting, it is recommended to use `plist-put' to set the
+changed parameters, so the other properties stay in place. Those
+are needed for sorting the contacts."
+ :type 'function
+ :group 'mu4e-compose)
+
+
+(defcustom mu4e-compose-complete-ignore-address-regexp "no-?reply"
+ "Ignore any e-mail addresses for completion if they match this regexp."
+ :type 'string
+ :group 'mu4e-compose)
+
+(defcustom mu4e-compose-reply-to-address nil
+ "The Reply-To address (if this, for some reason, is not equal to
+the From: address.)"
+ :type 'string
+ :group 'mu4e-compose)
+
+
+;; backward compatibility
+(make-obsolete-variable 'mu4e-reply-to-address 'mu4e-compose-reply-to-address
+ "v0.9.9")
+
+(defcustom mu4e-compose-keep-self-cc nil
+ "Non-nil means your e-mail address is kept on the CC list when
+replying to messages."
+ :type 'boolean
+ :group 'mu4e-compose)
+
+(defvar mu4e-compose-parent-message nil
+ "The parent message plist.
+This is the message being replied to, forwarded or edited; used
+in `mu4e-compose-pre-hook'. For new messages, it is nil.")
+
+;; Folders
+(defgroup mu4e-folders nil
+ "Special folders."
+ :group 'mu4e)
+
+(defcustom mu4e-drafts-folder "/drafts"
+ "Your folder for draft messages, relative to `mu4e-maildir'.
+e.g. \"/drafts\". Instead of a string, may also be a function that
+takes a message (a msg plist, see `mu4e-message-get-field'), and
+returns a folder. Note, the message parameter refers to the
+original message being replied to / being forwarded / re-edited and
+is nil otherwise. `mu4e-drafts-folder' is only evaluated once."
+ :type '(choice
+ (string :tag "Folder name")
+ (function :tag "Function return folder name"))
+ :group 'mu4e-folders)
+
+(defcustom mu4e-refile-folder "/archive"
+ "Your folder for refiling messages, relative to `mu4e-maildir',
+e.g. \"/Archive\". Instead of a string, may also be a function that
+takes a message (a msg plist, see `mu4e-message-get-field'), and
+returns a folder. Note that the message parameter refers to the
+message-at-point."
+ :type '(choice
+ (string :tag "Folder name")
+ (function :tag "Function return folder name"))
+ :group 'mu4e-folders)
+
+(defcustom mu4e-sent-folder "/sent"
+ "Your folder for sent messages, relative to `mu4e-maildir',
+e.g. \"/Sent Items\". Instead of a string, may also be a function
+that takes a message (a msg plist, see `mu4e-message-get-field'),
+and returns a folder. Note that the message parameter refers to
+the original message being replied to / being forwarded /
+re-edited, and is nil otherwise."
+ :type '(choice
+ (string :tag "Folder name")
+ (function :tag "Function return folder name"))
+ :group 'mu4e-folders)
+
+(defcustom mu4e-trash-folder "/trash"
+ "Your folder for trashed messages, relative to `mu4e-maildir',
+e.g. \"/trash\". Instead of a string, may also be a function that
+takes a message (a msg plist, see `mu4e-message-get-field'), and
+returns a folder. When using `mu4e-trash-folder' in the headers
+view (when marking messages for trash). Note that the message
+parameter refers to the message-at-point. When using it when
+composing a message (see `mu4e-sent-messages-behavior'), this
+refers to the original message being replied to / being forwarded /
+re-edited, and is nil otherwise."
+ :type '(choice
+ (string :tag "Folder name")
+ (function :tag "Function return folder name"))
+ :group 'mu4e-folders)
+
+
+(defcustom mu4e-maildir-shortcuts nil
+ "A list of maildir shortcuts. This makes it possible to quickly
+go to a particular maildir (folder), or quickly moving messages to
+them (e.g., for archiving or refiling). The list contains elements
+of the form (maildir . shortcut), where MAILDIR is a maildir (such
+as \"/archive/\"), and shortcut is a single character.
+
+You can use these shortcuts in the headers and view buffers, for
+example with `mu4e-mark-for-move-quick' (or 'm', by default) or
+`mu4e-jump-to-maildir' (or 'j', by default), followed by the
+designated shortcut character for the maildir.
+
+Unlike in search queries, folder names with spaces in them must NOT
+be quoted, since mu4e does this automatically for you."
+ :type '(repeat (cons (string :tag "Maildir") character))
+ :group 'mu4e-folders)
+
+;; Faces
+(defgroup mu4e-faces nil
+ "Type faces (fonts) used in mu4e."
+ :group 'mu4e
+ :group 'faces)
+
+(defface mu4e-unread-face
+ '((t :inherit font-lock-keyword-face :bold t))
+ "Face for an unread message header."
+ :group 'mu4e-faces)
+
+(defface mu4e-moved-face
+ '((t :inherit font-lock-comment-face :slant italic))
+ "Face for a message header that has been moved to some folder.
+\(It's still visible in the search results, since we cannot
+be sure it no longer matches)."
+ :group 'mu4e-faces)
+
+(defface mu4e-trashed-face
+ '((t :inherit font-lock-comment-face :strike-through t))
+ "Face for an message header in the trash folder."
+ :group 'mu4e-faces)
+
+(defface mu4e-draft-face
+ '((t :inherit font-lock-string-face))
+ "Face for a draft message header
+I.e. a message with the draft flag set."
+ :group 'mu4e-faces)
+
+(defface mu4e-flagged-face
+ '((t :inherit font-lock-constant-face :bold t))
+ "Face for a flagged message header."
+ :group 'mu4e-faces)
+
+(defface mu4e-replied-face
+ '((t :inherit font-lock-builtin-face :bold nil))
+ "Face for a replied message header."
+ :group 'mu4e-faces)
+
+(defface mu4e-forwarded-face
+ '((t :inherit font-lock-builtin-face :bold nil))
+ "Face for a passed (forwarded) message header."
+ :group 'mu4e-faces)
+
+(defface mu4e-header-face
+ '((t :inherit default))
+ "Face for a header without any special flags."
+ :group 'mu4e-faces)
+
+(defface mu4e-header-title-face
+ '((t :inherit font-lock-type-face))
+ "Face for a header title in the headers view."
+ :group 'mu4e-faces)
+
+(defface mu4e-header-highlight-face
+ '((t :inherit region :weight bold :underline t))
+ "Face for the header at point."
+ :group 'mu4e-faces)
+
+(defface mu4e-header-marks-face
+ '((t :inherit font-lock-preprocessor-face))
+ "Face for the mark in the headers list."
+ :group 'mu4e-faces)
+
+(defface mu4e-header-key-face
+ '((t :inherit message-header-name :bold t))
+ "Face for a header key (such as \"Foo\" in \"Subject:\ Foo\")."
+ :group 'mu4e-faces)
+
+(defface mu4e-header-value-face
+ '((t :inherit font-lock-doc-face))
+ "Face for a header value (such as \"Re: Hello!\")."
+ :group 'mu4e-faces)
+
+(defface mu4e-special-header-value-face
+ '((t :inherit font-lock-variable-name-face))
+ "Face for special header values."
+ :group 'mu4e-faces)
+
+(defface mu4e-link-face
+ '((t :inherit link))
+ "Face for showing URLs and attachments in the message view."
+ :group 'mu4e-faces)
+
+(defface mu4e-contact-face
+ '((t :inherit font-lock-variable-name-face))
+ "Face for showing URLs and attachments in the message view."
+ :group 'mu4e-faces)
+
+(defface mu4e-highlight-face
+ '((t :inherit highlight))
+ "Face for highlighting things."
+ :group 'mu4e-faces)
+
+(defface mu4e-title-face
+ '((t :inherit font-lock-type-face :bold t))
+ "Face for a header title in the headers view."
+ :group 'mu4e-faces)
+
+(defface mu4e-modeline-face
+ '((t :inherit font-lock-string-face :bold t))
+ "Face for the query in the mode-line."
+ :group 'mu4e-faces)
+
+(defface mu4e-view-body-face
+ '((t :inherit default))
+ "Face for the body in the message-view."
+ :group 'mu4e-faces)
+
+(defface mu4e-footer-face
+ '((t :inherit font-lock-comment-face))
+ "Face for message footers (signatures)."
+ :group 'mu4e-faces)
+
+(defface mu4e-url-number-face
+ '((t :inherit font-lock-constant-face :bold t))
+ "Face for the number tags for URLs."
+ :group 'mu4e-faces)
+
+(defface mu4e-attach-number-face
+ '((t :inherit font-lock-variable-name-face :bold t))
+ "Face for the number tags for attachments."
+ :group 'mu4e-faces)
+
+(defface mu4e-cited-1-face
+ '((t :inherit font-lock-builtin-face :bold nil :italic t))
+ "Face for cited message parts (level 1)."
+ :group 'mu4e-faces)
+
+(defface mu4e-cited-2-face
+ '((t :inherit font-lock-type-face :bold nil :italic t))
+ "Face for cited message parts (level 2)."
+ :group 'mu4e-faces)
+
+(defface mu4e-cited-3-face
+ '((t :inherit font-lock-variable-name-face :bold nil :italic t))
+ "Face for cited message parts (level 3)."
+ :group 'mu4e-faces)
+
+(defface mu4e-cited-4-face
+ '((t :inherit font-lock-keyword-face :bold nil :italic t))
+ "Face for cited message parts (level 4)."
+ :group 'mu4e-faces)
+
+(defface mu4e-cited-5-face
+ '((t :inherit font-lock-comment-face :bold nil :italic t))
+ "Face for cited message parts (level 5)."
+ :group 'mu4e-faces)
+
+(defface mu4e-cited-6-face
+ '((t :inherit font-lock-comment-delimiter-face :bold nil :italic t))
+ "Face for cited message parts (level 6)."
+ :group 'mu4e-faces)
+
+(defface mu4e-cited-7-face
+ '((t :inherit font-lock-preprocessor-face :bold nil :italic t))
+ "Face for cited message parts (level 7)."
+ :group 'mu4e-faces)
+
+(defface mu4e-system-face
+ '((t :inherit font-lock-comment-face :slant italic))
+ "Face for system message (such as the footers for message headers)."
+ :group 'mu4e-faces)
+
+(defface mu4e-ok-face
+ '((t :inherit font-lock-comment-face :bold t :slant normal))
+ "Face for things that are okay."
+ :group 'mu4e-faces)
+
+(defface mu4e-warning-face
+ '((t :inherit font-lock-warning-face :bold t :slant normal))
+ "Face for warnings / error."
+ :group 'mu4e-faces)
+
+(defface mu4e-compose-separator-face
+ '((t :inherit message-separator :slant italic))
+ "Face for the separator between headers / message in
+mu4e-compose-mode."
+ :group 'mu4e-faces)
+
+(defface mu4e-compose-header-face
+ '((t :inherit message-separator :slant italic))
+ "Face for the separator between headers / message in
+mu4e-compose-mode."
+ :group 'mu4e-faces)
+
+(defface mu4e-region-code
+ '((t (:background "DarkSlateGray")))
+ "Face for highlighting marked region in mu4e-view buffer."
+ :group 'mu4e-faces)
+
+;; headers info
+(defconst mu4e-header-info
+ '( (:attachments .
+ ( :name "Attachments"
+ :shortname "Atts"
+ :help "Message attachments"
+ :sortable nil))
+ (:bcc .
+ ( :name "Bcc"
+ :shortname "Bcc"
+ :help "Blind Carbon-Copy recipients for the message"
+ :sortable t))
+ (:cc .
+ ( :name "Cc"
+ :shortname "Cc"
+ :help "Carbon-Copy recipients for the message"
+ :sortable t))
+ (:date .
+ ( :name "Date"
+ :shortname "Date"
+ :help "Date/time when the message was written"
+ :sortable t))
+ (:human-date .
+ ( :name "Date"
+ :shortname "Date"
+ :help "Date/time when the message was written."
+ :sortable :date))
+ (:flags .
+ ( :name "Flags"
+ :shortname "Flgs"
+ :help "Flags for the message"
+ :sortable nil))
+ (:from .
+ ( :name "From"
+ :shortname "From"
+ :help "The sender of the message"
+ :sortable t))
+ (:from-or-to .
+ ( :name "From/To"
+ :shortname "From/To"
+ :help "Sender of the message if it's not me; otherwise the recipient"
+ :sortable nil))
+ (:maildir .
+ ( :name "Maildir"
+ :shortname "Maildir"
+ :help "Maildir for this message"
+ :sortable t))
+ (:mailing-list .
+ ( :name "List"
+ :shortname "List"
+ :help "Mailing list for this message"
+ :sortable nil))
+ (:message-id .
+ ( :name "Message-Id"
+ :shortname "MsgID"
+ :help "Message-Id for this message"
+ :sortable nil))
+ (:path .
+ ( :name "Path"
+ :shortname "Path"
+ :help "Full filesystem path to the message"
+ :sortable t))
+ (:signature .
+ ( :name "Signature"
+ :shortname "Sgn"
+ :help "Check for the cryptographic signature"
+ :sortable nil))
+ (:decryption .
+ ( :name "Decryption"
+ :shortname "Dec"
+ :help "Check the cryptographic decryption status"
+ :sortable nil))
+ (:size .
+ ( :name "Size"
+ :shortname "Size"
+ :help "Size of the message"
+ :sortable t))
+ (:subject .
+ ( :name "Subject"
+ :shortname "Subject"
+ :help "Subject of the message"
+ :sortable t))
+ (:tags .
+ ( :name "Tags"
+ :shortname "Tags"
+ :help "Tags for the message"
+ :sortable nil))
+ (:thread-subject .
+ ( :name "Subject"
+ :shortname "Subject"
+ :help "Subject of the thread"
+ :sortable :subject))
+ (:to .
+ ( :name "To"
+ :shortname "To"
+ :help "Recipient of the message"
+ :sortable t)))
+ "An alist of all possible header fields and information about them.
+This is used in the user-interface (the column headers in the header list, and
+the fields the message view).
+
+Most fields should be self-explanatory. A special one is
+`:from-or-to', which is equal to `:from' unless `:from' matches one
+of the addresses in `mu4e-user-mail-address-list', in which case it
+will be equal to `:to'.
+
+Furthermore, the property `:sortable' determines whether we can
+sort by this field. This can be either a boolean (nil or t), or a
+symbol for /another/ field. For example, the `:human-date' field
+uses `:date' for that.
+
+Note, `:sortable' does not work for custom header fields.")
+
+
+(defvar mu4e-header-info-custom
+ '( (:recipnum .
+ ( :name "Number of recipients"
+ :shortname "Recip#"
+ :help "Number of recipients for this message"
+ :function
+ (lambda (msg)
+ (format "%d"
+ (+ (length (mu4e-message-field msg :to))
+ (length (mu4e-message-field msg :cc))))))))
+"A list of custom (user-defined) headers. The format is similar
+to `mu4e-header-info', but adds a :function property, which
+should point to a function that takes a message p-list as
+argument, and returns a string. See the default value of
+`mu4e-header-info-custom for an example.")
+
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; run-time vars used in multiple places
+
+;; headers
+(defconst mu4e~headers-buffer-name "*mu4e-headers*"
+ "Name of the buffer for message headers.")
+(defvar mu4e~headers-buffer nil "Buffer for message headers.")
+; view
+(defconst mu4e~view-buffer-name "*mu4e-view*"
+ "Name for the message view buffer.")
+
+(defconst mu4e~view-embedded-buffer-name " *mu4e-embedded-view*"
+ "Name for the embedded message view buffer.")
+
+(defvar mu4e~view-buffer nil "The view buffer.")
+
+(defvar mu4e~view-msg nil "The message being viewed in view mode.")
+
+(defvar mu4e~view-headers-buffer nil
+ "The headers buffer connected to this view.")
+
+(defvar mu4e~contacts nil
+ "Hash of that maps contacts (ie. 'name <e-mail>') to an integer
+with their sort order. We need to keep this information around to
+quickly re-sort subsets of the contacts in the completions function in
+mu4e-compose.")
+
+(defvar mu4e~server-props nil
+ "Properties we receive from the mu4e server process.
+\(in the 'pong-handler').")
+
+(defvar mu4e~headers-last-query nil
+ "The present (most recent) query.")
+
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; our handlers funcs
+;; these handler funcs define what happens when we receive a certain message
+;; from the server
+(defun mu4e~default-handler (&rest args)
+ "*internal* Dummy handler function."
+ (error "Not handled: %S" args))
+
+(defvar mu4e-error-func 'mu4e~default-handler
+ "A function called for each error returned from the server
+process; the function is passed an error plist as argument. See
+`mu4e~proc-filter' for the format.")
+
+(defvar mu4e-update-func 'mu4e~default-handler
+ "A function called for each :update sexp returned from the server
+process; the function is passed a msg sexp as argument. See
+`mu4e~proc-filter' for the format.")
+
+(defvar mu4e-remove-func 'mu4e~default-handler
+ "A function called for each :remove sexp returned from the server
+process, when some message has been deleted. The function is passed
+the docid of the removed message.")
+
+(defvar mu4e-sent-func 'mu4e~default-handler
+ "A function called for each :sent sexp returned from the server
+process, when some message has been sent. The function is passed
+the docid and the draft-path of the sent message.")
+
+(defvar mu4e-view-func 'mu4e~default-handler
+ "A function called for each single message sexp returned from the
+server process. The function is passed a message sexp as
+argument. See `mu4e~proc-filter' for the format.")
+
+(defvar mu4e-header-func 'mu4e~default-handler
+ "A function called for each message returned from the server
+process; the function is passed a msg plist as argument. See
+`mu4e~proc-filter' for the format.")
+
+(defvar mu4e-found-func 'mu4e~default-handler
+ "A function called for when we received a :found sexp after the
+headers have returns, to report on the number of matches. See
+`mu4e~proc-filter' for the format.")
+
+(defvar mu4e-erase-func 'mu4e~default-handler
+ "A function called for when we received an :erase sexp after the
+headers have returns, to clear the current headers buffer. See
+`mu4e~proc-filter' for the format.")
+
+(defvar mu4e-compose-func 'mu4e~default-handler
+ "A function called for each message returned from the server
+process that is used as basis for composing a new message (ie.,
+either a reply or a forward); the function is passed msg and a
+symbol (either reply or forward). See `mu4e~proc-filter' for the
+format of <msg-plist>.")
+
+(defvar mu4e-info-func 'mu4e~default-handler
+ "A function called for each (:info type ....) sexp received from
+the server process.")
+
+(defvar mu4e-pong-func 'mu4e~default-handler
+ "A function called for each (:pong type ....) sexp received from
+the server process.")
+
+(defvar mu4e-contacts-func 'mu4e~default-handler
+ "A function called for each (:contacts (<list-of-contacts>) sexp
+received from the server process.")
+
+(defvar mu4e-temp-func 'mu4e~default-handler
+ "A function called for each (:temp <file> <cookie>) sexp received
+from the server process.")
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+(provide 'mu4e-vars)
+;;; End of mu4e-vars.el
diff --git a/_spacemacs.d/local/mu4e/mu4e-view.el b/_spacemacs.d/local/mu4e/mu4e-view.el
new file mode 100644
index 0000000..75fc79f
--- /dev/null
+++ b/_spacemacs.d/local/mu4e/mu4e-view.el
@@ -0,0 +1,1541 @@
+;;; mu4e-view.el -- part of mu4e, the mu mail user agent
+;;
+;; Copyright (C) 2011-2016 Dirk-Jan C. Binnema
+
+;; Author: Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
+;; Maintainer: Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
+
+;; This file is not part of GNU Emacs.
+;;
+;; 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 <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; In this file we define mu4e-view-mode (+ helper functions), which is used for
+;; viewing e-mail messages
+
+;;; Code:
+(require 'mu4e-utils) ;; utility functions
+(require 'mu4e-vars)
+(require 'mu4e-mark)
+(require 'mu4e-proc)
+(require 'mu4e-compose)
+(require 'mu4e-actions)
+(require 'mu4e-message)
+
+(require 'comint)
+(require 'browse-url)
+(require 'button)
+(require 'epa)
+(require 'epg)
+(require 'thingatpt)
+(require 'calendar)
+
+(eval-when-compile (byte-compile-disable-warning 'cl-functions))
+(require 'cl)
+
+
+;; the message view
+(defgroup mu4e-view nil
+ "Settings for the message view."
+ :group 'mu4e)
+
+(defcustom mu4e-view-fields
+ '(:from :to :cc :subject :flags :date :maildir :mailing-list :tags
+ :attachments :signature :decryption)
+ "Header fields to display in the message view buffer.
+For the complete list of available headers, see `mu4e-header-info'."
+ :type (list 'symbol)
+ :group 'mu4e-view)
+
+
+(defcustom mu4e-view-show-addresses nil
+ "Whether to initially show full e-mail addresses for contacts in
+address fields, rather than only their names."
+ :type 'boolean
+ :group 'mu4e-view)
+
+(make-obsolete-variable 'mu4e-view-wrap-lines nil "0.9.9-dev7")
+(make-obsolete-variable 'mu4e-view-hide-cited nil "0.9.9-dev7")
+
+(defcustom mu4e-view-date-format "%c"
+ "Date format to use in the message view.
+In the format of `format-time-string'."
+ :type 'string
+ :group 'mu4e-view)
+
+(defcustom mu4e-view-image-max-width 800
+ "The maximum width for images to display.
+This is only effective if you're using an emacs with Imagemagick
+support, and `mu4e-view-show-images' is non-nil."
+ :group 'mu4e-view)
+
+(defcustom mu4e-view-image-max-height 600
+ "The maximum height for images to display.
+This is only effective if you're using an emacs with Imagemagick
+support, and `mu4e-view-show-images' is non-nil."
+ :group 'mu4e-view)
+
+(defcustom mu4e-view-scroll-to-next t
+ "If non-nil, move to the next message when calling
+`mu4e-view-scroll-up-or-next' (typically bound to SPC) when at the
+end of a message. Otherwise, don't move to the next message.")
+
+(defcustom mu4e-save-multiple-attachments-without-asking nil
+ "If non-nil, saving multiple attachments asks once for a
+directory and saves all attachments in the chosen directory."
+ :type 'boolean
+ :group 'mu4e-view)
+
+(defvar mu4e-view-actions
+ '( ("capture message" . mu4e-action-capture-message)
+ ("view as pdf" . mu4e-action-view-as-pdf)
+ ("show this thread" . mu4e-action-show-thread))
+ "List of actions to perform on messages in view mode.
+The actions are of the form:
+ (NAME FUNC)
+where:
+* NAME is the name of the action (e.g. \"Count lines\")
+* FUNC is a function which receives a message plist as an argument.
+
+The first letter of NAME is used as a shortcut character.")
+
+(defvar mu4e-view-attachment-actions
+ '( ("wopen-with" . mu4e-view-open-attachment-with)
+ ("ein-emacs" . mu4e-view-open-attachment-emacs)
+ ("dimport-in-diary" . mu4e-view-import-attachment-diary)
+ ("|pipe" . mu4e-view-pipe-attachment))
+ "List of actions to perform on message attachments.
+The actions are cons-cells of the form:
+ (NAME . FUNC)
+where:
+* NAME is the name of the action (e.g. \"Count lines\")
+* FUNC is a function which receives two arguments: the message
+ plist and the attachment number.
+The first letter of NAME is used as a shortcut character.")
+
+(defvar mu4e-view-fill-headers t
+ "If non-nil, automatically fill the headers when viewing them.")
+
+(defvar mu4e-view-contacts-header-keymap
+ (let ((map (make-sparse-keymap)))
+ (define-key map [mouse-2] 'mu4e~view-compose-contact)
+ (define-key map "C" 'mu4e~view-compose-contact)
+ (define-key map "c" 'mu4e~view-copy-contact)
+ map)
+ "Keymap used for the contacts in the header fields.")
+
+(defvar mu4e-view-clickable-urls-keymap
+ (let ((map (make-sparse-keymap)))
+ (define-key map [mouse-1] 'mu4e~view-browse-url-from-binding)
+ (define-key map [?\M-\r] 'mu4e~view-browse-url-from-binding)
+ map)
+ "Keymap used for the urls inside the body.")
+
+(defvar mu4e-view-attachments-header-keymap
+ (let ((map (make-sparse-keymap)))
+ (define-key map [mouse-1] 'mu4e~view-open-attach-from-binding)
+ (define-key map [?\M-\r] 'mu4e~view-open-attach-from-binding)
+ (define-key map [mouse-2] 'mu4e~view-save-attach-from-binding)
+ (define-key map (kbd "<S-return>") 'mu4e~view-save-attach-from-binding)
+ map)
+ "Keymap used in the \"Attachements\" header field.")
+
+(defcustom mu4e-view-auto-mark-as-read t
+ "Automatically mark messages are 'read' when you read
+them. This is typically the expected behavior, but can be turned
+off, for example when using a read-only file-system."
+ :type 'boolean
+ :group 'mu4e-view)
+
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+
+(defvar mu4e~view-cited-hidden nil "Whether cited lines are hidden.")
+(defvar mu4e~view-link-map nil
+ "A map of some number->url so we can jump to url by number.")
+
+(defvar mu4e~path-parent-docid-map (make-hash-table :test 'equal)
+ "A map of msg paths --> parent-docids.
+This is to determine what is the parent docid for embedded
+message extracted at some path.")
+
+(defvar mu4e~view-attach-map nil
+ "A mapping of user-visible attachment number to the actual part index.")
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+(defun mu4e-view-message-with-message-id (msgid)
+ "View message with message-id MSGID. This (re)creates a
+headers-buffer with a search for MSGID, then open a view for that
+message."
+ (mu4e-headers-search (concat "msgid:" msgid) nil nil t msgid t))
+
+(define-obsolete-function-alias 'mu4e-view-message-with-msgid
+ 'mu4e-view-message-with-message-id "0.9.17")
+
+(defun mu4e~view-custom-field (msg field)
+ "Show some custom header field, or raise an error if it is not
+found."
+ (let* ((item (or (assoc field mu4e-header-info-custom)
+ (mu4e-error "field %S not found" field)))
+ (func (or (plist-get (cdr-safe item) :function)
+ (mu4e-error "no :function defined for field %S %S"
+ field (cdr item)))))
+ (funcall func msg)))
+
+
+(defun mu4e-view-message-text (msg)
+ "Return the message to display (as a string), based on the MSG plist."
+ (concat
+ (mapconcat
+ (lambda (field)
+ (let ((fieldval (mu4e-message-field msg field)))
+ (case field
+ (:subject (mu4e~view-construct-header field fieldval))
+ (:path (mu4e~view-construct-header field fieldval))
+ (:maildir (mu4e~view-construct-header field fieldval))
+ ((:flags :tags) (mu4e~view-construct-flags-tags-header
+ field fieldval))
+
+ ;; contact fields
+ (:to (mu4e~view-construct-contacts-header msg field))
+ (:from (mu4e~view-construct-contacts-header msg field))
+ (:cc (mu4e~view-construct-contacts-header msg field))
+ (:bcc (mu4e~view-construct-contacts-header msg field))
+
+ ;; if we (`user-mail-address' are the From, show To, otherwise,
+ ;; show From
+ (:from-or-to
+ (let* ((from (mu4e-message-field msg :from))
+ (from (and from (cdar from))))
+ (if (mu4e-user-mail-address-p from)
+ (mu4e~view-construct-contacts-header msg :to)
+ (mu4e~view-construct-contacts-header msg :from))))
+ ;; date
+ (:date
+ (let ((datestr
+ (when fieldval (format-time-string mu4e-view-date-format
+ fieldval))))
+ (if datestr (mu4e~view-construct-header field datestr) "")))
+ ;; size
+ (:size
+ (mu4e~view-construct-header field (mu4e-display-size fieldval)))
+ (:mailing-list
+ (mu4e~view-construct-header field fieldval))
+ (:message-id
+ (mu4e~view-construct-header field fieldval))
+ ;; attachments
+ (:attachments (mu4e~view-construct-attachments-header msg))
+ ;; pgp-signatures
+ (:signature (mu4e~view-construct-signature-header msg))
+ ;; pgp-decryption
+ (:decryption (mu4e~view-construct-decryption-header msg))
+ (t (mu4e~view-construct-header field
+ (mu4e~view-custom-field msg field))))))
+ mu4e-view-fields "")
+ "\n"
+ (let ((body (mu4e-message-body-text msg)))
+ (when (fboundp 'add-face-text-property)
+ (add-face-text-property 0 (length body) 'mu4e-view-body-face t body))
+ body)))
+
+(defun mu4e~view-embedded-winbuf ()
+ "Get a buffer (shown in a window) for the embedded message."
+ (let* ((buf (get-buffer-create mu4e~view-embedded-buffer-name))
+ (win (or (get-buffer-window buf) (split-window-vertically))))
+ (select-window win)
+ (switch-to-buffer buf)))
+
+(defun mu4e~delete-all-overlays ()
+ "`delete-all-overlays' with compatibility fallback."
+ (if (functionp 'delete-all-overlays)
+ (delete-all-overlays)
+ (remove-overlays)))
+
+
+(defun mu4e-view (msg headersbuf)
+ "Display the message MSG in a new buffer, and keep in sync with HDRSBUF.
+'In sync' here means that moving to the next/previous message in
+the the message view affects HDRSBUF, as does marking etc.
+
+As a side-effect, a message that is being viewed loses its 'unread'
+marking if it still had that."
+ (let* ((embedded ;; is it as an embedded msg (ie. message/rfc822 att)?
+ (when (gethash (mu4e-message-field msg :path)
+ mu4e~path-parent-docid-map) t))
+ (buf
+ (if embedded
+ (mu4e~view-embedded-winbuf)
+ (get-buffer-create mu4e~view-buffer-name))))
+ ;; note: mu4e~view-mark-as-read-maybe will pseudo-recursively call mu4e-view
+ ;; again by triggering mu4e~view again as it marks the message as read
+ (with-current-buffer buf
+ (switch-to-buffer buf)
+ (setq mu4e~view-msg msg)
+ (when (or embedded (not (mu4e~view-mark-as-read-maybe msg)))
+ (let ((inhibit-read-only t))
+ (erase-buffer)
+ (mu4e~delete-all-overlays)
+ (insert (mu4e-view-message-text msg))
+ (goto-char (point-min))
+ (mu4e~fontify-cited)
+ (mu4e~fontify-signature)
+ (mu4e~view-make-urls-clickable)
+ (mu4e~view-show-images-maybe msg)
+ (setq
+ mu4e~view-buffer buf
+ mu4e~view-headers-buffer headersbuf)
+ (when embedded (local-set-key "q" 'kill-buffer-and-window))
+ (mu4e-view-mode))))))
+
+(defun mu4e~view-get-property-from-event (prop)
+ "Get the property PROP at point, or the location of the mouse.
+The action is chosen based on the `last-command-event'.
+Meant to be evoked from interactive commands."
+ (if (and (eventp last-command-event)
+ (mouse-event-p last-command-event))
+ (let ((posn (event-end last-command-event)))
+ (when (numberp (posn-point posn))
+ (get-text-property
+ (posn-point posn)
+ prop
+ (window-buffer (posn-window posn)))
+ ))
+ (get-text-property (point) prop)))
+
+(defun mu4e~view-construct-header (field val &optional dont-propertize-val)
+ "Return header field FIELD (as in `mu4e-header-info') with value
+VAL if VAL is non-nil. If DONT-PROPERTIZE-VAL is non-nil, do not
+add text-properties to VAL."
+ (let* ((info (cdr (assoc field
+ (append mu4e-header-info mu4e-header-info-custom))))
+ (key (plist-get info :name))
+ (help (plist-get info :help)))
+ (if (and val (> (length val) 0))
+ (with-temp-buffer
+ (insert (propertize (concat key ":")
+ 'face 'mu4e-header-key-face
+ 'help-echo help) " "
+ (if dont-propertize-val
+ val
+ (propertize val 'face 'mu4e-header-value-face)) "\n")
+ (when mu4e-view-fill-headers
+ ;; temporarily set the fill column <margin> positions to the right, so
+ ;; we can indent the following lines correctly
+ (let* ((margin 1)
+ (fill-column (max (- fill-column margin) 0)))
+ (fill-region (point-min) (point-max))
+ (goto-char (point-min))
+ (while (and (zerop (forward-line 1)) (not (looking-at "^$")))
+ (indent-to-column margin))))
+ (buffer-string))
+ "")))
+
+(defun mu4e~view-compose-contact (&optional point)
+ "Compose a message for the address at point."
+ (interactive)
+ (unless (get-text-property (or point (point)) 'email)
+ (mu4e-error "No address at point"))
+ (mu4e~compose-mail (get-text-property (or point (point)) 'long)))
+
+(defun mu4e~view-copy-contact (&optional full)
+ "Compose a message for the address at (point)."
+ (interactive "P")
+ (let ((email (get-text-property (point) 'email))
+ (long (get-text-property (point) 'long)))
+ (unless email (mu4e-error "No address at point"))
+ (kill-new (if full long email))
+ (mu4e-message "Address copied.")))
+
+(defun mu4e~view-construct-contacts-header (msg field)
+ "Add a header for a contact field (ie., :to, :from, :cc, :bcc)."
+ (mu4e~view-construct-header field
+ (mapconcat
+ (lambda(c)
+ (let* ((name (when (car c)
+ (replace-regexp-in-string "[[:cntrl:]]" "" (car c))))
+ (email (when (cdr c)
+ (replace-regexp-in-string "[[:cntrl:]]" "" (cdr c))))
+ (short (or name email)) ;; name may be nil
+ (long (if name (format "%s <%s>" name email) email)))
+ (propertize
+ (if mu4e-view-show-addresses long short)
+ 'long long
+ 'short short
+ 'email email
+ 'keymap mu4e-view-contacts-header-keymap
+ 'face 'mu4e-contact-face
+ 'mouse-face 'highlight
+ 'help-echo (format "<%s>\n%s" email
+ "[mouse-2] or C to compose a mail for this recipient"))))
+ (mu4e-message-field msg field) ", ") t))
+
+
+(defun mu4e~view-construct-flags-tags-header (field val)
+ "Construct a Flags: header."
+ (mu4e~view-construct-header
+ field
+ (mapconcat
+ (lambda (flag)
+ (propertize
+ (if (symbolp flag)
+ (symbol-name flag)
+ flag)
+ 'face 'mu4e-special-header-value-face))
+ val
+ (propertize ", " 'face 'mu4e-header-value-face)) t))
+
+(defun mu4e~view-construct-signature-header (msg)
+ "Construct a Signature: header, if there are any signed parts."
+ (let* ((parts (mu4e-message-field msg :parts))
+ (verdicts
+ (remove-if 'null
+ (mapcar (lambda (part) (mu4e-message-part-field part :signature))
+ parts)))
+ (val (when verdicts
+ (mapconcat
+ (lambda (v)
+ (propertize (symbol-name v)
+ 'face (if (eq v 'verified)
+ 'mu4e-ok-face 'mu4e-warning-face)))
+ verdicts ", ")))
+ (btn (when val
+ (with-temp-buffer
+ (insert-text-button "Details"
+ 'action (lambda (b)
+ (mu4e-view-verify-msg-popup
+ (button-get b 'msg))))
+ (buffer-string))))
+ (val (when val (concat val " (" btn ")"))))
+ (mu4e~view-construct-header :signature val t)))
+
+(defun mu4e~view-construct-decryption-header (msg)
+ "Construct a Decryption: header, if there are any encrypted parts."
+ (let* ((parts (mu4e-message-field msg :parts))
+ (verdicts
+ (remove-if 'null
+ (mapcar (lambda (part)
+ (mu4e-message-part-field part :decryption))
+ parts)))
+ (succeeded (remove-if (lambda (v) (eq v 'failed)) verdicts))
+ (failed (remove-if (lambda (v) (eq v 'succeeded)) verdicts))
+ (succ (when succeeded
+ (propertize
+ (concat (number-to-string (length succeeded))
+ " part(s) decrypted")
+ 'face 'mu4e-ok-face)))
+ (fail (when failed
+ (propertize
+ (concat (number-to-string (length failed))
+ " part(s) failed")
+ 'face 'mu4e-warning-face)))
+ (val (concat succ fail)))
+ (mu4e~view-construct-header :decryption val t)))
+
+(defun mu4e~view-open-attach-from-binding ()
+ "Open the attachement at point, or click location."
+ (interactive)
+ (let* (( msg (mu4e~view-get-property-from-event 'mu4e-msg))
+ ( attnum (mu4e~view-get-property-from-event 'mu4e-attnum)))
+ (when (and msg attnum)
+ (mu4e-view-open-attachment msg attnum))))
+
+(defun mu4e~view-save-attach-from-binding ()
+ "Save the attachement at point, or click location."
+ (interactive)
+ (let* (( msg (mu4e~view-get-property-from-event 'mu4e-msg))
+ ( attnum (mu4e~view-get-property-from-event 'mu4e-attnum)))
+ (when (and msg attnum)
+ (mu4e-view-save-attachment-single msg attnum))))
+
+(defun mu4e~view-construct-attachments-header (msg)
+ "Display attachment information; the field looks like something like:
+ :parts ((:index 1 :name \"1.part\" :mime-type \"text/plain\"
+ :type (leaf) :attachment nil :size 228)
+ (:index 2 :name \"analysis.doc\"
+ :mime-type \"application/msword\"
+ :type (leaf attachment) :attachment nil :size 605196))"
+ (setq mu4e~view-attach-map ;; buffer local
+ (make-hash-table :size 64 :weakness nil))
+ (let* ((id 0)
+ (attachments
+ ;; we only list parts that look like attachments, ie. that have a
+ ;; non-nil :attachment property; we record a mapping between
+ ;; user-visible numbers and the part indices
+ (remove-if-not
+ (lambda (part)
+ (let* ((mtype (or (mu4e-message-part-field part :mime-type)
+ "application/octet-stream"))
+ (attachtype (mu4e-message-part-field part :type))
+ (isattach
+ (or ;; we consider parts marked either
+ ;; "attachment" or "inline" as attachment.
+ (member 'attachment attachtype)
+ ;; list inline parts as attachment (so they can be
+ ;; saved), unless they are text/plain, which are
+ ;; usually just message footers in mailing lists
+ (and (member 'inline attachtype)
+ (not (string-match "^text/plain" mtype))))))
+ (or ;; remove if it's not an attach *or* if it's an
+ ;; image/audio/application type (but not a signature)
+ isattach
+ (string-match "^\\(image\\|audio\\)" mtype)
+ (string= "message/rfc822" mtype)
+ (string= "text/calendar" mtype)
+ (and (string-match "^application" mtype)
+ (not (string-match "signature" mtype))))))
+ (mu4e-message-field msg :parts)))
+ (attstr
+ (mapconcat
+ (lambda (part)
+ (let ((index (mu4e-message-part-field part :index))
+ (name (mu4e-message-part-field part :name))
+ (size (mu4e-message-part-field part :size)))
+ (incf id)
+ (puthash id index mu4e~view-attach-map)
+
+ (concat
+ (propertize (format "[%d]" id)
+ 'face 'mu4e-attach-number-face)
+ (propertize name 'face 'mu4e-link-face
+ 'keymap mu4e-view-attachments-header-keymap
+ 'mouse-face 'highlight
+ 'help-echo (concat
+ "[mouse-1] or [M-RET] opens the attachment\n"
+ "[mouse-2] or [S-RET] offers to save it")
+ 'mu4e-msg msg
+ 'mu4e-attnum id
+ )
+ (when (and size (> size 0))
+ (propertize (format "(%s)" (mu4e-display-size size))
+ 'face 'mu4e-header-key-face)))))
+ attachments ", ")))
+ (when attachments
+ (mu4e~view-construct-header :attachments attstr t))))
+
+(defun mu4e-view-for-each-part (msg func)
+ "Apply FUNC to each part in MSG.
+FUNC should be a function taking two arguments:
+ 1. the message MSG, and
+ 2. a plist describing the attachment. The plist looks like:
+ (:index 1 :name \"test123.doc\"
+ :mime-type \"application/msword\" :attachment t :size 1234)."
+ (dolist (part (mu4e-msg-field msg :parts))
+ (funcall func msg part)))
+
+(defvar mu4e-view-mode-map nil
+ "Keymap for \"*mu4e-view*\" buffers.")
+(unless mu4e-view-mode-map
+ (setq mu4e-view-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 "q" 'mu4e~view-quit-buffer)
+
+ ;; note, 'z' is by-default bound to 'bury-buffer'
+ ;; but that's not very useful in this case
+ (define-key map "z" 'ignore)
+
+ (define-key map "s" 'mu4e-headers-search)
+ (define-key map "S" 'mu4e-view-search-edit)
+ (define-key map "/" 'mu4e-view-search-narrow)
+
+ (define-key map (kbd "<M-left>") 'mu4e-headers-query-prev)
+ (define-key map (kbd "<M-right>") 'mu4e-headers-query-next)
+
+ (define-key map "b" 'mu4e-headers-search-bookmark)
+ (define-key map "B" 'mu4e-headers-search-bookmark-edit)
+
+ (define-key map "%" 'mu4e-view-mark-pattern)
+ (define-key map "t" 'mu4e-view-mark-subthread)
+ (define-key map "T" 'mu4e-view-mark-thread)
+
+ (define-key map "v" 'mu4e-view-verify-msg-popup)
+
+ (define-key map "j" 'mu4e~headers-jump-to-maildir)
+
+ (define-key map "g" 'mu4e-view-go-to-url)
+ (define-key map "k" 'mu4e-view-save-url)
+ (define-key map "f" 'mu4e-view-fetch-url)
+
+ (define-key map "F" 'mu4e-compose-forward)
+ (define-key map "R" 'mu4e-compose-reply)
+ (define-key map "C" 'mu4e-compose-new)
+ (define-key map "E" 'mu4e-compose-edit)
+
+ (define-key map "." 'mu4e-view-raw-message)
+ (define-key map "|" 'mu4e-view-pipe)
+ (define-key map "a" 'mu4e-view-action)
+
+ (define-key map ";" 'mu4e-context-switch)
+
+ ;; toggle header settings
+ (define-key map "O" 'mu4e-headers-change-sorting)
+ (define-key map "P" 'mu4e-headers-toggle-threading)
+ (define-key map "Q" 'mu4e-headers-toggle-full-search)
+ (define-key map "W" 'mu4e-headers-toggle-include-related)
+
+ ;; change the number of headers
+ (define-key map (kbd "C-+") 'mu4e-headers-split-view-grow)
+ (define-key map (kbd "C--") 'mu4e-headers-split-view-shrink)
+ (define-key map (kbd "<C-kp-add>") 'mu4e-headers-split-view-grow)
+ (define-key map (kbd "<C-kp-subtract>") 'mu4e-headers-split-view-shrink)
+
+ ;; intra-message navigation
+ (define-key map (kbd "SPC") 'mu4e-view-scroll-up-or-next)
+ (define-key map (kbd "<home>") 'beginning-of-buffer)
+ (define-key map (kbd "<end>") 'end-of-buffer)
+ (define-key map (kbd "RET") 'mu4e-scroll-up)
+ (define-key map (kbd "<backspace>") 'mu4e-scroll-down)
+
+ ;; navigation between messages
+ (define-key map "p" 'mu4e-view-headers-prev)
+ (define-key map "n" 'mu4e-view-headers-next)
+ ;; the same
+ (define-key map (kbd "<M-down>") 'mu4e-view-headers-next)
+ (define-key map (kbd "<M-up>") 'mu4e-view-headers-prev)
+
+ (define-key map (kbd "[") 'mu4e-view-headers-prev-unread)
+ (define-key map (kbd "]") 'mu4e-view-headers-next-unread)
+
+ ;; switching to view mode (if it's visible)
+ (define-key map "y" 'mu4e-select-other-view)
+
+ ;; attachments
+ (define-key map "e" 'mu4e-view-save-attachment)
+ (define-key map "o" 'mu4e-view-open-attachment)
+ (define-key map "A" 'mu4e-view-attachment-action)
+
+ ;; marking/unmarking
+ (define-key map "d" 'mu4e-view-mark-for-trash)
+ (define-key map (kbd "<delete>") 'mu4e-view-mark-for-delete)
+ (define-key map (kbd "<deletechar>") 'mu4e-view-mark-for-delete)
+ (define-key map (kbd "D") 'mu4e-view-mark-for-delete)
+ (define-key map (kbd "m") 'mu4e-view-mark-for-move)
+ (define-key map (kbd "r") 'mu4e-view-mark-for-refile)
+
+ (define-key map (kbd "?") 'mu4e-view-mark-for-unread)
+ (define-key map (kbd "!") 'mu4e-view-mark-for-read)
+
+ (define-key map (kbd "+") 'mu4e-view-mark-for-flag)
+ (define-key map (kbd "-") 'mu4e-view-mark-for-unflag)
+ (define-key map (kbd "=") 'mu4e-view-mark-for-untrash)
+ (define-key map (kbd "&") 'mu4e-view-mark-custom)
+
+ (define-key map (kbd "*") 'mu4e-view-mark-for-something)
+ (define-key map (kbd "<kp-multiply>") 'mu4e-view-mark-for-something)
+ (define-key map (kbd "<insert>") 'mu4e-view-mark-for-something)
+ (define-key map (kbd "<insertchar>") 'mu4e-view-mark-for-something)
+
+ (define-key map (kbd "#") 'mu4e-mark-resolve-deferred-marks)
+
+ ;; misc
+ (define-key map "w" 'visual-line-mode)
+ (define-key map "#" 'mu4e-view-toggle-hide-cited)
+ (define-key map "h" 'mu4e-view-toggle-html)
+ (define-key map (kbd "M-q") 'mu4e-view-fill-long-lines)
+
+ ;; next 3 only warn user when attempt in the message view
+ (define-key map "u" 'mu4e-view-unmark)
+ (define-key map "U" 'mu4e-view-unmark-all)
+ (define-key map "x" 'mu4e-view-marked-execute)
+
+ (define-key map "$" 'mu4e-show-log)
+ (define-key map "H" 'mu4e-display-manual)
+
+ ;; menu
+ (define-key map [menu-bar] (make-sparse-keymap))
+ (let ((menumap (make-sparse-keymap "View")))
+ (define-key map [menu-bar headers] (cons "View" menumap))
+
+ (define-key menumap [quit-buffer]
+ '("Quit view" . mu4e~view-quit-buffer))
+ (define-key menumap [display-help] '("Help" . mu4e-display-manual))
+
+ (define-key menumap [sepa0] '("--"))
+ (define-key menumap [wrap-lines]
+ '("Toggle wrap lines" . visual-line-mode))
+ (define-key menumap [toggle-html]
+ '("Toggle view-html" . mu4e-view-toggle-html))
+ (define-key menumap [hide-cited]
+ '("Toggle hide cited" . mu4e-view-toggle-hide-cited))
+ (define-key menumap [raw-view]
+ '("View raw message" . mu4e-view-raw-message))
+ (define-key menumap [pipe]
+ '("Pipe through shell" . mu4e-view-pipe))
+ ;; (define-key menumap [inspect]
+ ;; '("Inspect with guile" . mu4e-inspect-message))
+
+ (define-key menumap [sepa8] '("--"))
+ (define-key menumap [open-att]
+ '("Open attachment" . mu4e-view-open-attachment))
+ (define-key menumap [extract-att]
+ '("Extract attachment" . mu4e-view-save-attachment))
+
+ (define-key menumap [save-url]
+ '("Save URL to kill-ring" . mu4e-view-save-url))
+ (define-key menumap [fetch-url]
+ '("Fetch URL" . mu4e-view-fetch-url))
+ (define-key menumap [goto-url]
+ '("Visit URL" . mu4e-view-go-to-url))
+
+ (define-key menumap [sepa1] '("--"))
+ (define-key menumap [mark-delete]
+ '("Mark for deletion" . mu4e-view-mark-for-delete))
+ (define-key menumap [mark-trash]
+ '("Mark for trash" . mu4e-view-mark-for-trash))
+ (define-key menumap [mark-move]
+ '("Mark for move" . mu4e-view-mark-for-move))
+
+ (define-key menumap [sepa2] '("--"))
+ (define-key menumap [resend] '("Resend" . mu4e-compose-resend))
+ (define-key menumap [forward] '("Forward" . mu4e-compose-forward))
+ (define-key menumap [reply] '("Reply" . mu4e-compose-reply))
+ (define-key menumap [compose-new] '("Compose new" . mu4e-compose-new))
+ (define-key menumap [sepa3] '("--"))
+
+ (define-key menumap [query-next]
+ '("Next query" . mu4e-headers-query-next))
+ (define-key menumap [query-prev]
+ '("Previous query" . mu4e-headers-query-prev))
+ (define-key menumap [narrow-search]
+ '("Narrow search" . mu4e-headers-search-narrow))
+ (define-key menumap [bookmark]
+ '("Search bookmark" . mu4e-headers-search-bookmark))
+ (define-key menumap [jump]
+ '("Jump to maildir" . mu4e~headers-jump-to-maildir))
+ (define-key menumap [search]
+ '("Search" . mu4e-headers-search))
+
+ (define-key menumap [sepa4] '("--"))
+ (define-key menumap [next] '("Next" . mu4e-view-headers-next))
+ (define-key menumap [previous] '("Previous" . mu4e-view-headers-prev)))
+ map)))
+
+(fset 'mu4e-view-mode-map mu4e-view-mode-map)
+
+(defcustom mu4e-view-mode-hook nil
+ "Hook run when entering Mu4e-View mode."
+ :options '(turn-on-visual-line-mode)
+ :type 'hook
+ :group 'mu4e-view)
+
+(defvar mu4e-view-mode-abbrev-table nil)
+(define-derived-mode mu4e-view-mode special-mode "mu4e:view"
+ "Major mode for viewing an e-mail message in mu4e.
+\\{mu4e-view-mode-map}."
+ (use-local-map mu4e-view-mode-map)
+
+ (make-local-variable 'mu4e~view-headers-buffer)
+ (make-local-variable 'mu4e~view-msg)
+ (make-local-variable 'mu4e~view-link-map)
+ (make-local-variable 'mu4e~view-attach-map)
+ (make-local-variable 'mu4e~view-cited-hidden)
+
+ ;; show context in mode-string
+ (set (make-local-variable 'global-mode-string) '(:eval (mu4e-context-label)))
+
+ (setq buffer-undo-list t);; don't record undo info
+
+ ;; autopair mode gives error when pressing RET
+ ;; turn it off
+ (when (boundp 'autopair-dont-activate)
+ (setq autopair-dont-activate t)))
+
+(defun mu4e~view-mark-as-read-maybe (msg)
+ "Clear the message MSG New/Unread status and set it to Seen.
+If the message is not New/Unread, do nothing. Evaluates to t if it
+triggers any changes, nil otherwise. If this function does any
+changes, it triggers a refresh."
+ (when (and mu4e-view-auto-mark-as-read msg)
+ (let ((flags (mu4e-message-field msg :flags))
+ (msgid (mu4e-message-field msg :message-id))
+ (docid (mu4e-message-field msg :docid)))
+ ;; attached (embedded) messages don't have docids; leave them alone if it is a new message
+ (when (and docid (or (member 'unread flags) (member 'new flags)))
+ ;; mark /all/ messages with this message-id as read, so all copies of
+ ;; this message will be marked as read.
+ (mu4e~proc-move msgid nil "+S-u-N")
+ t))))
+
+(defun mu4e~view-browse-url-func (url)
+ "Return a function that executes `browse-url' with URL.
+What browser is called is depending on
+`browse-url-browser-function' and `browse-url-mailto-function'."
+ (save-match-data
+ (if (string-match "^mailto:" url)
+ (lexical-let ((url url))
+ (lambda ()
+ (interactive)
+ (mu4e~compose-browse-url-mail url)))
+ (lexical-let ((url url))
+ (lambda ()
+ (interactive)
+ (browse-url url))))))
+
+(defun mu4e~view-browse-url-from-binding (&optional url)
+ "View in browser the url at point, or click location.
+If the optional argument URL is provided, browse that instead.
+If the url is mailto link, start writing an email to that address."
+ (interactive)
+ (let* (( url (or url (mu4e~view-get-property-from-event 'mu4e-url))))
+ (when url
+ (if (string-match-p "^mailto:" url)
+ (mu4e~compose-browse-url-mail url)
+ (browse-url url)))))
+
+(defun mu4e~view-show-images-maybe (msg)
+ "Show attached images, if `mu4e-show-images' is non-nil."
+ (when (and (display-images-p) mu4e-view-show-images)
+ (mu4e-view-for-each-part msg
+ (lambda (msg part)
+ (when (string-match "^image/"
+ (or (mu4e-message-part-field part :mime-type)
+ "application/object-stream"))
+ (let ((imgfile (mu4e-message-part-field part :temp)))
+ (when (and imgfile (file-exists-p imgfile))
+ (save-excursion
+ (goto-char (point-max))
+ (mu4e-display-image imgfile
+ mu4e-view-image-max-width
+ mu4e-view-image-max-height)))))))))
+
+
+(defvar mu4e~view-beginning-of-url-regexp
+ "https?\\://\\|mailto:"
+ "Regexp that matches the beginning of http:/https:/mailto:
+URLs; match-string 1 will contain the matched URL, if any.")
+
+;; this is fairly simplistic...
+(defun mu4e~view-make-urls-clickable ()
+ "Turn things that look like URLs into clickable things.
+Also number them so they can be opened using `mu4e-view-go-to-url'."
+ (let ((num 0))
+ (save-excursion
+ (setq mu4e~view-link-map ;; buffer local
+ (make-hash-table :size 32 :weakness nil))
+ (goto-char (point-min))
+ (while (re-search-forward mu4e~view-beginning-of-url-regexp nil t)
+ (let ((bounds (thing-at-point-bounds-of-url-at-point)))
+ (when bounds
+ (let* ((url (thing-at-point-url-at-point))
+ (ov (make-overlay (car bounds) (cdr bounds))))
+ (puthash (incf num) url mu4e~view-link-map)
+ (add-text-properties
+ (car bounds)
+ (cdr bounds)
+ `(face mu4e-link-face
+ mouse-face highlight
+ mu4e-url ,url
+ keymap ,mu4e-view-clickable-urls-keymap
+ help-echo
+ "[mouse-1] or [M-RET] to open the link"))
+ (overlay-put ov 'after-string
+ (propertize (format "[%d]" num)
+ 'face 'mu4e-url-number-face)))))))))
+
+
+(defun mu4e~view-hide-cited ()
+ "Toggle hiding of cited lines in the message body."
+ (save-excursion
+ (let ((inhibit-read-only t))
+ (goto-char (point-min))
+ (flush-lines mu4e-cited-regexp)
+ (setq mu4e~view-cited-hidden t))))
+
+(defmacro mu4e~view-in-headers-context (&rest body)
+ "Evaluate BODY in the context of the headers buffer connected to
+this view."
+ `(progn
+ (unless (buffer-live-p mu4e~view-headers-buffer)
+ (mu4e-error "no headers-buffer connected"))
+ (let* ((msg (mu4e-message-at-point))
+ (docid (mu4e-message-field msg :docid))
+ (curwin (selected-window)))
+ (unless docid
+ (mu4e-error "message without docid: action is not possible."))
+ (with-current-buffer mu4e~view-headers-buffer
+ (when (get-buffer-window)
+ (select-window (get-buffer-window)))
+ (if (mu4e~headers-goto-docid docid)
+ ,@body
+ (mu4e-error "cannot find message in headers buffer."))))))
+
+(defun mu4e-view-headers-next (&optional n)
+ "Move point to the next message header in the headers buffer
+connected with this message view. If this succeeds, return the new
+docid. Otherwise, return nil. Optionally, takes an integer
+N (prefix argument), to the Nth next header."
+ (interactive "P")
+ (mu4e~view-in-headers-context
+ (mu4e~headers-move (or n 1))))
+
+(defun mu4e-view-headers-prev (&optional n)
+ "Move point to the previous message header in the headers buffer
+connected with this message view. If this succeeds, return the new
+docid. Otherwise, return nil. Optionally, takes an integer
+N (prefix argument), to the Nth previous header."
+ (interactive "P")
+ (mu4e~view-in-headers-context
+ (mu4e~headers-move (- (or n 1)))))
+
+(defun mu4e~view-prev-or-next-unread (backwards)
+ "Move point to the next or previous (when BACKWARDS is non-`nil')
+unread message header in the headers buffer connected with this
+message view. If this succeeds, return the new docid. Otherwise,
+return nil."
+ (mu4e~view-in-headers-context
+ (mu4e~headers-prev-or-next-unread backwards))
+ (mu4e-select-other-view)
+ (mu4e-headers-view-message))
+
+(defun mu4e-view-headers-prev-unread ()
+"Move point to the previous unread message header in the headers
+buffer connected with this message view. If this succeeds, return
+the new docid. Otherwise, return nil."
+ (interactive)
+ (mu4e~view-prev-or-next-unread t))
+
+(defun mu4e-view-headers-next-unread ()
+ "Move point to the next unread message header in the headers
+buffer connected with this message view. If this succeeds, return
+the new docid. Otherwise, return nil."
+ (interactive)
+ (mu4e~view-prev-or-next-unread nil))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+;; Interactive functions
+
+(defun mu4e-view-toggle-hide-cited ()
+ "Toggle hiding of cited lines in the message body."
+ (interactive)
+ (if mu4e~view-cited-hidden
+ (mu4e-view-refresh)
+ (mu4e~view-hide-cited)))
+
+(defun mu4e-view-toggle-html ()
+ "Toggle html-display of the message body (if any)."
+ (interactive)
+ (setq mu4e-view-prefer-html (not mu4e-view-prefer-html))
+ (mu4e-view-refresh))
+
+(defun mu4e-view-refresh ()
+ "Redisplay the current message."
+ (interactive)
+ (mu4e-view mu4e~view-msg mu4e~view-headers-buffer)
+ (setq mu4e~view-cited-hidden nil))
+
+(defun mu4e-view-action (&optional msg)
+ "Ask user for some action to apply on MSG, then do it.
+If MSG is nil apply action to message returned
+bymessage-at-point. The actions are specified in
+`mu4e-view-actions'."
+ (interactive)
+ (let* ((msg (or msg (mu4e-message-at-point)))
+ (actionfunc (mu4e-read-option "Action: " mu4e-view-actions)))
+ (funcall actionfunc msg)))
+
+(defun mu4e-view-mark-pattern ()
+ "Ask user for a kind of mark (move, delete etc.), a field to
+match and a regular expression to match with. Then, mark all
+matching messages with that mark."
+ (interactive)
+ (mu4e~view-in-headers-context (mu4e-headers-mark-pattern)))
+
+(defun mu4e-view-mark-thread (&optional markpair)
+ "Ask user for a kind of mark (move, delete etc.), and apply it
+to all messages in the thread at point in the headers view. The
+optional MARKPAIR can also be used to provide the mark
+selection."
+ (interactive)
+ (mu4e~view-in-headers-context
+ (if markpair (mu4e-headers-mark-thread nil markpair)
+ (call-interactively 'mu4e-headers-mark-thread))))
+
+(defun mu4e-view-mark-subthread (&optional markpair)
+ "Ask user for a kind of mark (move, delete etc.), and apply it
+to all messages in the subthread at point in the headers view.
+The optional MARKPAIR can also be used to provide the mark
+selection."
+ (interactive)
+ (mu4e~view-in-headers-context
+ (if markpair (mu4e-headers-mark-subthread markpair)
+ (mu4e-headers-mark-subthread))))
+
+(defun mu4e-view-search-narrow ()
+ "Run `mu4e-headers-search-narrow' in the headers buffer."
+ (interactive)
+ (mu4e~view-in-headers-context
+ (call-interactively 'mu4e-headers-search-narrow)))
+
+(defun mu4e-view-search-edit ()
+ "Run `mu4e-headers-search-edit' in the headers buffer."
+ (interactive)
+ (mu4e~view-in-headers-context (mu4e-headers-search-edit)))
+
+(defun mu4e-mark-region-code ()
+ "Highlight region marked with `message-mark-inserted-region'.
+Add this function to `mu4e-view-mode-hook' to enable this feature."
+ (require 'message)
+ (let (beg end ov-beg ov-end ov-inv)
+ (save-excursion
+ (goto-char (point-min))
+ (while (re-search-forward
+ (concat "^" message-mark-insert-begin) nil t)
+ (setq ov-beg (match-beginning 0)
+ ov-end (match-end 0)
+ ov-inv (make-overlay ov-beg ov-end)
+ beg ov-end)
+ (overlay-put ov-inv 'invisible t)
+ (when (re-search-forward
+ (concat "^" message-mark-insert-end) nil t)
+ (setq ov-beg (match-beginning 0)
+ ov-end (match-end 0)
+ ov-inv (make-overlay ov-beg ov-end)
+ end ov-beg)
+ (overlay-put ov-inv 'invisible t))
+ (when (and beg end)
+ (let ((ov (make-overlay beg end)))
+ (overlay-put ov 'face 'mu4e-region-code))
+ (setq beg nil end nil))))))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; Wash functions
+(defun mu4e-view-fill-long-lines ()
+ "Fill lines that are wider than the window width or `fill-column'."
+ (interactive)
+ (with-current-buffer mu4e~view-buffer
+ (save-excursion
+ (let ((inhibit-read-only t)
+ (width (window-width (get-buffer-window (current-buffer)))))
+ (save-restriction
+ (message-goto-body)
+ (while (not (eobp))
+ (end-of-line)
+ (when (>= (current-column) (min fill-column width))
+ (narrow-to-region (min (1+ (point)) (point-max))
+ (point-at-bol))
+ (let ((goback (point-marker)))
+ (fill-paragraph nil)
+ (goto-char (marker-position goback)))
+ (widen))
+ (forward-line 1)))))))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; attachment handling
+(defun mu4e~view-get-attach-num (prompt msg &optional multi)
+ "Ask the user with PROMPT for an attachment number for MSG, and
+ensure it is valid. The number is [1..n] for attachments
+\[0..(n-1)] in the message. If MULTI is nil, return the number for
+the attachment; otherwise (MULTI is non-nil), accept ranges of
+attachment numbers, as per `mu4e-split-ranges-to-numbers', and
+return the corresponding string."
+ (let* ((count (hash-table-count mu4e~view-attach-map)) (def))
+ (when (zerop count) (mu4e-error "No attachments for this message"))
+ (if (not multi)
+ (if (= count 1)
+ (read-number (mu4e-format "%s: " prompt) 1)
+ (read-number (mu4e-format "%s (1-%d): " prompt count)))
+ (progn
+ (setq def (if (= count 1) "1" (format "1-%d" count)))
+ (read-string (mu4e-format "%s (default %s): " prompt def)
+ nil nil def)))))
+
+(defun mu4e~view-get-attach (msg attnum)
+ "Return the attachment plist in MSG corresponding to attachment
+number ATTNUM."
+ (let* ((partid (gethash attnum mu4e~view-attach-map))
+ (attach
+ (find-if
+ (lambda (part)
+ (eq (mu4e-message-part-field part :index) partid))
+ (mu4e-message-field msg :parts))))
+ (or attach (mu4e-error "Not a valid attachment"))))
+
+
+(defun mu4e~view-request-attachment-path (fname path)
+ "Ask the user where to save FNAME (default is PATH/FNAME)."
+ (let ((fpath (expand-file-name
+ (read-file-name
+ (mu4e-format "Save as ")
+ path nil nil fname) path)))
+ (if (file-directory-p fpath)
+ (expand-file-name fname fpath)
+ fpath)))
+
+(defun mu4e~view-request-attachments-dir (path)
+ "Ask the user where to save multiple attachments (default is PATH)."
+ (let ((fpath (expand-file-name
+ (read-directory-name
+ (mu4e-format "Save in directory ")
+ path nil nil nil) path)))
+ (if (file-directory-p fpath)
+ fpath)))
+
+(defun mu4e-view-save-attachment-single (&optional msg attnum)
+ "Save attachment number ATTNUM from MSG.
+If MSG is nil use the message returned by `message-at-point'.
+If ATTNUM is nil ask for the attachment number."
+ (interactive)
+ (let* ((msg (or msg (mu4e-message-at-point)))
+ (attnum (or attnum
+ (mu4e~view-get-attach-num "Attachment to save" msg)))
+ (att (mu4e~view-get-attach msg attnum))
+ (fname (plist-get att :name))
+ (mtype (plist-get att :mime-type))
+ (path (concat
+ (mu4e~get-attachment-dir fname mtype) "/"))
+ (index (plist-get att :index))
+ (retry t) (fpath))
+ (while retry
+ (setq fpath (mu4e~view-request-attachment-path fname path))
+ (setq retry
+ (and (file-exists-p fpath)
+ (not (y-or-n-p (mu4e-format "Overwrite '%s'?" fpath))))))
+ (mu4e~proc-extract
+ 'save (mu4e-message-field msg :docid)
+ index mu4e-decryption-policy fpath)))
+
+
+(defun mu4e-view-save-attachment-multi (&optional msg)
+ "Offer to save multiple email attachments from the current message.
+Default is to save all messages, [1..n], where n is the number of
+attachments. You can type multiple values separated by space, e.g.
+ 1 3-6 8
+will save attachments 1,3,4,5,6 and 8.
+
+Furthermore, there is a shortcut \"a\" which so means all
+attachments, but as this is the default, you may not need it."
+ (interactive)
+ (let* ((msg (or msg (mu4e-message-at-point)))
+ (attachstr (mu4e~view-get-attach-num
+ "Attachment number range (or 'a' for 'all')" msg t))
+ (count (hash-table-count mu4e~view-attach-map))
+ (attachnums (mu4e-split-ranges-to-numbers attachstr count)))
+ (if mu4e-save-multiple-attachments-without-asking
+ (let* ((path (concat (mu4e~get-attachment-dir) "/"))
+ (attachdir (mu4e~view-request-attachments-dir path)))
+ (dolist (num attachnums)
+ (let* ((att (mu4e~view-get-attach msg num))
+ (fname (plist-get att :name))
+ (index (plist-get att :index))
+ (retry t)
+ fpath)
+ (while retry
+ (setq fpath (expand-file-name (concat attachdir fname) path))
+ (setq retry
+ (and (file-exists-p fpath)
+ (not (y-or-n-p
+ (mu4e-format "Overwrite '%s'?" fpath))))))
+ (mu4e~proc-extract
+ 'save (mu4e-message-field msg :docid)
+ index mu4e-decryption-policy fpath))))
+ (dolist (num attachnums)
+ (mu4e-view-save-attachment-single msg num)))))
+
+(defun mu4e-view-save-attachment (&optional multi)
+ "Offer to save attachment(s).
+If MULTI (prefix-argument) is nil, save a single one, otherwise,
+offer to save a range of attachments."
+ (interactive "P")
+ (if multi
+ (mu4e-view-save-attachment-multi)
+ (mu4e-view-save-attachment-single)))
+
+(defun mu4e-view-open-attachment (&optional msg attnum)
+ "Open attachment number ATTNUM from MSG.
+If MSG is nil use the message returned by `message-at-point'.
+If ATTNUM is nil ask for the attachment number."
+ (interactive)
+ (let* ((msg (or msg (mu4e-message-at-point)))
+ (attnum (or attnum
+ (mu4e~view-get-attach-num "Attachment to open" msg)))
+ (att (or (mu4e~view-get-attach msg attnum)))
+ (index (plist-get att :index))
+ (docid (mu4e-message-field msg :docid))
+ (mimetype (plist-get att :mime-type)))
+ (if (and mimetype (string= mimetype "message/rfc822"))
+ ;; special handling for message-attachments; we open them in mu4e. we also
+ ;; send the docid as parameter (4th arg); we'll get this back from the
+ ;; server, and use it to determine the parent message (ie., the current
+ ;; message) when showing the embedded message/rfc822, and return to the
+ ;; current message when quitting that one.
+ (mu4e~view-temp-action docid index "mu4e" docid)
+ ;; otherwise, open with the default program (handled in mu-server
+ (mu4e~proc-extract 'open docid index mu4e-decryption-policy))))
+
+
+(defun mu4e~view-temp-action (docid index what &optional param)
+ "Open attachment INDEX for message with DOCID, and invoke ACTION."
+ (interactive)
+ (mu4e~proc-extract 'temp docid index mu4e-decryption-policy nil what param ))
+
+(defvar mu4e~view-open-with-hist nil "History list for the open-with argument.")
+
+(defun mu4e-view-open-attachment-with (msg attachnum &optional cmd)
+ "Open MSG's attachment ATTACHNUM with CMD.
+If CMD is nil, ask user for it."
+ (interactive)
+ (let* ((att (mu4e~view-get-attach msg attachnum))
+ (cmd (or cmd
+ (read-string
+ (mu4e-format "Shell command to open it with: ")
+ nil 'mu4e~view-open-with-hist)))
+ (index (plist-get att :index)))
+ (mu4e~view-temp-action
+ (mu4e-message-field msg :docid) index "open-with" cmd)))
+
+(defvar mu4e~view-pipe-hist nil
+ "History list for the pipe argument.")
+
+(defun mu4e-view-pipe-attachment (msg attachnum &optional pipecmd)
+ "Feed MSG's attachment ATTACHNUM through pipe PIPECMD.
+If PIPECMD is nil, ask user for it."
+ (interactive)
+ (let* ((att (mu4e~view-get-attach msg attachnum))
+ (pipecmd (or pipecmd
+ (read-string
+ (mu4e-format "Pipe: ")
+ nil
+ 'mu4e~view-pipe-hist)))
+ (index (plist-get att :index)))
+ (mu4e~view-temp-action
+ (mu4e-message-field msg :docid) index "pipe" pipecmd)))
+
+
+(defun mu4e-view-open-attachment-emacs (msg attachnum)
+ "Open MSG's attachment ATTACHNUM in the current emacs instance."
+ (interactive)
+ (let* ((att (mu4e~view-get-attach msg attachnum))
+ (index (plist-get att :index)))
+ (mu4e~view-temp-action (mu4e-message-field msg :docid) index "emacs")))
+
+(defun mu4e-view-import-attachment-diary (msg attachnum)
+ "Open MSG's attachment ATTACHNUM in the current emacs instance."
+ (interactive)
+ (let* ((att (mu4e~view-get-attach msg attachnum))
+ (index (plist-get att :index)))
+ (mu4e~view-temp-action (mu4e-message-field msg :docid) index "diary")))
+
+(defun mu4e-view-attachment-action (&optional msg)
+ "Ask user what to do with attachments in MSG
+If MSG is nil use the message returned by `message-at-point'.
+The actions are specified in `mu4e-view-attachment-actions'."
+ (interactive)
+ (let* ((msg (or msg (mu4e-message-at-point)))
+ (actionfunc (mu4e-read-option
+ "Action on attachment: "
+ mu4e-view-attachment-actions))
+ (attnum (mu4e~view-get-attach-num "Which attachment" msg)))
+ (when (and actionfunc attnum)
+ (funcall actionfunc msg attnum))))
+
+;; handler-function to handle the response we get from the server when we
+;; want to do something with one of the attachments.
+(defun mu4e~view-temp-handler (path what docid param)
+ "Handler function for doing things with temp files (ie.,
+attachments) in response to a (mu4e~proc-extract 'temp ... )."
+ (cond
+ ((string= what "open-with")
+ ;; 'param' will be the program to open-with
+ (start-process "*mu4e-open-with-proc*" "*mu4e-open-with*" param path))
+ ((string= what "pipe")
+ ;; 'param' will be the pipe command, path the infile for this
+ (mu4e-process-file-through-pipe path param))
+ ;; if it's mu4e, it's some embedded message; 'param' may contain the docid
+ ;; of the parent message.
+ ((string= what "mu4e")
+ ;; remember the mapping path->docid, which maps the path of the embedded
+ ;; message to the docid of its parent
+ (puthash path docid mu4e~path-parent-docid-map)
+ (mu4e~proc-view-path path mu4e-view-show-images mu4e-decryption-policy))
+ ((string= what "emacs")
+ (find-file path)
+ ;; make the buffer read-only since it usually does not make
+ ;; sense to edit the temp buffer; use C-x C-q if you insist...
+ (setq buffer-read-only t))
+ ((string= what "diary")
+ (icalendar-import-file path diary-file))
+ (t (mu4e-error "Unsupported action %S" what))))
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+(defun mu4e-view-mark-custom ()
+ "Run some custom mark function."
+ (mu4e~view-in-headers-context
+ (mu4e-headers-mark-custom)))
+
+(defun mu4e~view-split-view-p ()
+ "Return t if we're in split-view, nil otherwise."
+ (member mu4e-split-view '(horizontal vertical)))
+
+(defun mu4e-view-scroll-up-or-next ()
+ "Scroll-up the current message.
+If `mu4e-view-scroll-to-next' is non-nil, and we can't scroll-up
+anymore, go the next message."
+ (interactive)
+ (condition-case nil
+ (scroll-up)
+ (error
+ (when mu4e-view-scroll-to-next
+ (mu4e-view-headers-next)))))
+
+(defun mu4e-scroll-up ()
+ "Scroll text of selected window up one line."
+ (interactive)
+ (scroll-up 1))
+
+(defun mu4e-scroll-down ()
+ "Scroll text of selected window down one line."
+ (interactive)
+ (scroll-down 1))
+
+(defun mu4e-view-unmark-all ()
+ "If we're in split-view, unmark all messages.
+Otherwise, warn user that unmarking only works in the header
+list."
+ (interactive)
+ (if (mu4e~view-split-view-p)
+ (mu4e~view-in-headers-context (mu4e-mark-unmark-all))
+ (mu4e-message "Unmarking needs to be done in the header list view")))
+
+(defun mu4e-view-unmark ()
+ "If we're in split-view, unmark message at point.
+Otherwise, warn user that unmarking only works in the header
+list."
+ (interactive)
+ (if (mu4e~view-split-view-p)
+ (mu4e-view-mark-for-unmark)
+ (mu4e-message "Unmarking needs to be done in the header list view")))
+
+
+(defmacro mu4e~view-defun-mark-for (mark)
+ "Define a function mu4e-view-mark-for-MARK."
+ (let ((funcname (intern (format "mu4e-view-mark-for-%s" mark)))
+ (docstring (format "Mark the current message for %s." mark)))
+ `(progn
+ (defun ,funcname () ,docstring
+ (interactive)
+ (mu4e~view-in-headers-context
+ (mu4e-headers-mark-and-next ',mark)))
+ (put ',funcname 'definition-name ',mark))))
+
+(mu4e~view-defun-mark-for move)
+(mu4e~view-defun-mark-for trash)
+(mu4e~view-defun-mark-for refile)
+(mu4e~view-defun-mark-for delete)
+(mu4e~view-defun-mark-for flag)
+(mu4e~view-defun-mark-for unflag)
+(mu4e~view-defun-mark-for unmark)
+(mu4e~view-defun-mark-for something)
+(mu4e~view-defun-mark-for read)
+(mu4e~view-defun-mark-for unread)
+
+(defun mu4e-view-marked-execute ()
+ "Execute the marks."
+ (interactive)
+ (mu4e~view-in-headers-context
+ (mu4e-mark-execute-all)))
+
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; URL handling
+(defun mu4e~view-get-urls-num (prompt &optional multi)
+ "Ask the user with PROMPT for an URL number for MSG, and ensure
+it is valid. The number is [1..n] for URLs \[0..(n-1)] in the
+message. If MULTI is nil, return the number for the URL;
+otherwise (MULTI is non-nil), accept ranges of URL numbers, as
+per `mu4e-split-ranges-to-numbers', and return the corresponding
+string."
+ (let* ((count (hash-table-count mu4e~view-link-map)) (def))
+ (when (zerop count) (mu4e-error "No links for this message"))
+ (if (not multi)
+ (if (= count 1)
+ (read-number (mu4e-format "%s: " prompt) 1)
+ (read-number (mu4e-format "%s (1-%d): " prompt count)))
+ (progn
+ (setq def (if (= count 1) "1" (format "1-%d" count)))
+ (read-string (mu4e-format "%s (default %s): " prompt def)
+ nil nil def)))))
+
+(defun mu4e-view-go-to-url (&optional multi)
+ "Offer to go to url(s). If MULTI (prefix-argument) is nil, go to
+a single one, otherwise, offer to go to a range of urls."
+ (interactive "P")
+ (mu4e~view-handle-urls "URL to visit"
+ multi (lambda (url) (mu4e~view-browse-url-from-binding url))))
+
+(defun mu4e-view-save-url (&optional multi)
+ "Offer to save urls(s) to the kill-ring. If
+MULTI (prefix-argument) is nil, save a single one, otherwise, offer
+to save a range of URLs."
+ (interactive "P")
+ (mu4e~view-handle-urls "URL to save" multi
+ (lambda (url)
+ (kill-new url)
+ (mu4e-message "Saved %s to the kill-ring" url))))
+
+(defun mu4e-view-fetch-url (&optional multi)
+ "Offer to fetch (download) urls(s). If MULTI (prefix-argument) is nil,
+download a single one, otherwise, offer to fetch a range of
+URLs. The urls are fetched to `mu4e-attachment-dir'."
+ (interactive "P")
+ (mu4e~view-handle-urls "URL to fetch" multi
+ (lambda (url)
+ (let ((target (concat (mu4e~get-attachment-dir url) "/"
+ (file-name-nondirectory url))))
+ (url-copy-file url target)
+ (mu4e-message "Fetched %s -> %s" url target)))))
+
+(defun mu4e~view-handle-urls (prompt multi urlfunc)
+ "If MULTI is nil, apply URLFUNC to a single uri, otherwise, apply
+it to a range of uris. PROMPT is the query to present to the user."
+ (interactive "P")
+ (if multi
+ (mu4e~view-handle-multi-urls prompt urlfunc)
+ (mu4e~view-handle-single-url prompt urlfunc)))
+
+(defun mu4e~view-handle-single-url (prompt urlfunc &optional num)
+ "Apply URLFUNC to url NUM in the current message, prompting the
+user with PROMPT."
+ (interactive)
+ (let* ((num (or num (mu4e~view-get-urls-num prompt)))
+ (url (gethash num mu4e~view-link-map)))
+ (unless url (mu4e-warn "Invalid number for URL"))
+ (funcall urlfunc url)))
+
+(defun mu4e~view-handle-multi-urls (prompt urlfunc)
+ "Apply URLFUNC to a a range of urls in the current message,
+prompting the user with PROMPT.
+
+Default is to aplly it to all URLs, [1..n], where n is the number
+of urls. You can type multiple values separated by space, e.g. 1
+3-6 8 will visit urls 1,3,4,5,6 and 8.
+
+Furthermore, there is a shortcut \"a\" which means all urls, but as
+this is the default, you may not need it."
+ (interactive)
+ (let* ((linkstr (mu4e~view-get-urls-num
+ "URL number range (or 'a' for 'all')" t))
+ (count (hash-table-count mu4e~view-link-map))
+ (linknums (mu4e-split-ranges-to-numbers linkstr count)))
+ (dolist (num linknums)
+ (mu4e~view-handle-single-url prompt urlfunc num))))
+
+(defun mu4e-view-for-each-uri (func)
+ "Execute FUNC (which receives a uri) for each uri in the current
+ message."
+ (maphash (lambda (num uri) (funcall func uri)) mu4e~view-link-map))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+(defconst mu4e~view-raw-buffer-name " *mu4e-raw-view*"
+ "*internal* Name for the raw message view buffer")
+
+(defun mu4e-view-raw-message ()
+ "Display the raw contents of message at point in a new buffer."
+ (interactive)
+ (let ((path (mu4e-message-field-at-point :path))
+ (buf (get-buffer-create mu4e~view-raw-buffer-name)))
+ (unless (and path (file-readable-p path))
+ (mu4e-error "Not a readable file: %S" path))
+ (with-current-buffer buf
+ (let ((inhibit-read-only t))
+ (erase-buffer)
+ (insert-file-contents path)
+ (view-mode)
+ (goto-char (point-min))))
+ (switch-to-buffer buf)))
+
+(defun mu4e-view-pipe (cmd)
+ "Pipe the message at point through shell command CMD, and display
+the results."
+ (interactive "sShell command: ")
+ (let ((path (mu4e-message-field (mu4e-message-at-point) :path)))
+ (mu4e-process-file-through-pipe path cmd)))
+
+(defconst mu4e~verify-buffer-name " *mu4e-verify*")
+
+(defun mu4e-view-verify-msg-popup (&optional msg)
+ "Pop-up a little signature verification window for (optional) MSG
+or message-at-point."
+ (interactive)
+ (let* ((msg (or msg (mu4e-message-at-point)))
+ (path (mu4e-message-field msg :path))
+ (cmd (format "%s verify --verbose %s %s"
+ mu4e-mu-binary
+ (shell-quote-argument path)
+ (if mu4e-decryption-policy
+ "--decrypt --use-agent"
+ "")))
+ (output (shell-command-to-string cmd))
+ ;; create a new one
+ (buf (get-buffer-create mu4e~verify-buffer-name))
+ (win (or (get-buffer-window buf)
+ (split-window-vertically (- (window-height) 6)))))
+ (with-selected-window win
+ (let ((inhibit-read-only t))
+ ;; (set-window-dedicated-p win t)
+ (switch-to-buffer buf)
+ (erase-buffer)
+ (insert output)
+ (goto-char (point-min))
+ (local-set-key "q" 'kill-buffer-and-window))
+ (setq buffer-read-only t))
+ (select-window win)))
+
+
+(defun mu4e~view-quit-buffer ()
+ "Quit the mu4e-view buffer.
+This is a rather complex function, to ensure we don't disturb
+other windows."
+ (interactive)
+ (unless (eq major-mode 'mu4e-view-mode)
+ (mu4e-error "Must be in mu4e-view-mode (%S)" major-mode))
+ (let ((curbuf (current-buffer)) (curwin (selected-window))
+ (headers-win))
+ (walk-windows
+ (lambda (win)
+ ;; check whether the headers buffer window is visible
+ (when (eq mu4e~view-headers-buffer (window-buffer win))
+ (setq headers-win win))
+ ;; and kill any _other_ (non-selected) window that shows the current
+ ;; buffer
+ (when
+ (and
+ (eq curbuf (window-buffer win)) ;; does win show curbuf?
+ (not (eq curwin win)) ;; but it's not the curwin?
+ (not (one-window-p))) ;; and not the last one on the frame?
+ (delete-window win)))) ;; delete it!
+ ;; now, all *other* windows should be gone.
+ ;; if the headers view is also visible, kill ourselves + window; otherwise
+ ;; switch to the headers view
+ (if (window-live-p headers-win)
+ ;; headers are visible
+ (progn
+ (kill-buffer-and-window) ;; kill the view win
+ (setq mu4e~headers-view-win nil)
+ (select-window headers-win)) ;; and switch to the headers win...
+ ;; headers are not visible...
+ (progn
+ (kill-buffer)
+ (setq mu4e~headers-view-win nil)
+ (when (buffer-live-p mu4e~view-headers-buffer)
+ (switch-to-buffer mu4e~view-headers-buffer))))))
+
+(provide 'mu4e-view)
+;; end of mu4e-view
diff --git a/_spacemacs.d/local/mu4e/mu4e.el b/_spacemacs.d/local/mu4e/mu4e.el
new file mode 100644
index 0000000..fd01875
--- /dev/null
+++ b/_spacemacs.d/local/mu4e/mu4e.el
@@ -0,0 +1,93 @@
+;;; mu4e.el --- part of mu4e, the mu mail user agent
+;;
+;; Copyright (C) 2011-2016 Dirk-Jan C. Binnema
+
+;; Author: Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
+;; Maintainer: Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
+;; Keywords: email
+;; Version: 0.0
+
+;; 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 <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;;; Code:
+
+(eval-when-compile (require 'cl))
+
+(require 'mu4e-meta) ;; autogenerated file with metadata (version etc.)
+(require 'mu4e-headers) ;; headers view
+(require 'mu4e-view) ;; message view
+(require 'mu4e-main) ;; main screen
+(require 'mu4e-compose) ;; message composition / sending
+(require 'mu4e-proc) ;; communication with backend
+(require 'mu4e-utils) ;; utility functions
+(require 'mu4e-context) ;; support for contexts
+(require 'mu4e-speedbar) ;; support for speedbar
+
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; register our handler functions; these connect server messages to functions
+;; to handle them.
+;;
+;;
+;; these are all defined in mu4e-headers
+(setq mu4e-update-func 'mu4e~headers-update-handler)
+(setq mu4e-header-func 'mu4e~headers-header-handler)
+(setq mu4e-found-func 'mu4e~headers-found-handler)
+(setq mu4e-view-func 'mu4e~headers-view-handler)
+(setq mu4e-remove-func 'mu4e~headers-remove-handler)
+(setq mu4e-erase-func 'mu4e~headers-clear)
+
+;; these ones are defined in mu4e-utils
+(setq mu4e-info-func 'mu4e-info-handler)
+(setq mu4e-error-func 'mu4e-error-handler)
+;; note: mu4e-utils also dynamically (temporarily)
+;; registers mu4e-pong func
+
+;; this one is defined in mu4e-compose
+(setq mu4e-compose-func 'mu4e~compose-handler)
+;; note: mu4e-compose.el dynamically registers mu4e-sent-func
+;; we don't do that here, because it's only a local (temporary)
+;; handler
+
+;; this one is defined in mu4e-view
+(setq mu4e-temp-func 'mu4e~view-temp-handler)
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;;###autoload
+(defun mu4e (&optional background)
+ "If mu4e is not running yet, start it. Then, show the main
+window, unless BACKGROUND (prefix-argument) is non-nil."
+ (interactive "P")
+ ;; start mu4e, then show the main view
+ (mu4e~start (unless background 'mu4e~main-view)))
+
+(defun mu4e-quit()
+ "Quit the mu4e session."
+ (interactive)
+ (if mu4e-confirm-quit
+ (when (y-or-n-p (mu4e-format "Are you sure you want to quit?"))
+ (mu4e~stop))
+ (mu4e~stop)))
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+(provide 'mu4e)
+
+;;; mu4e.el ends here
diff --git a/_spacemacs.d/local/mu4e/org-mu4e.el b/_spacemacs.d/local/mu4e/org-mu4e.el
new file mode 100644
index 0000000..b9e9688
--- /dev/null
+++ b/_spacemacs.d/local/mu4e/org-mu4e.el
@@ -0,0 +1,323 @@
+;;; org-mu4e -- Support for links to mu4e messages/queries from within
+;;; org-mode, and for writing message in org-mode, sending them as
+;;; rich-text
+;;
+;; Copyright (C) 2012-2016 Dirk-Jan C. Binnema
+
+;; Author: Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
+;; Maintainer: Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
+;; Keywords: outlines, hypermedia, calendar, mail
+;; Version: 0.0
+
+;; 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 1the 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 <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;;; Code:
+
+
+;; The expect version here is org 8.x
+
+
+;; the 'noerror is just to make sure bytecompilations does not break...
+;; FIXME: find a better solution
+(require 'org nil 'noerror)
+(require 'org-exp nil 'noerror)
+
+(eval-when-compile (require 'cl))
+(require 'mu4e)
+
+(defgroup org-mu4e nil
+ "Settings for the org-mode related functionality in mu4e."
+ :group 'mu4e
+ :group 'org)
+
+(defvar org-mu4e-link-query-in-headers-mode nil
+ "If non-nil, `org-store-link' in `mu4e-headers-mode' links to the
+the current query; otherwise, it links to the message at point.")
+
+(defcustom org-mu4e-link-desc-func
+ (lambda (msg) (or (plist-get msg :subject) "No subject"))
+ "Function that takes a msg and returns a string for the
+description part of an org-mode link.
+
+Example usage:
+
+ (defun my-link-descr (msg)
+ (let ((subject (or (plist-get msg :subject)
+ \"No subject\"))
+ (date (or (format-time-string mu4e-headers-date-format
+ (mu4e-msg-field msg :date))
+ \"No date\")))
+ (concat subject \" \" date)))
+
+ (setq org-mu4e-link-desc-func 'my-link-descr)"
+ :type 'function
+ :group 'org-mu4e)
+
+(defun org-mu4e-store-link ()
+ "Store a link to a mu4e query or message."
+ (when (member major-mode '(mu4e-headers-mode mu4e-view-mode))
+ (if (and (eq major-mode 'mu4e-headers-mode)
+ org-mu4e-link-query-in-headers-mode)
+ ;; storing links to queries
+ (let* ((query (mu4e-last-query))
+ desc link)
+ (org-store-link-props :type "mu4e" :query query)
+ (setq
+ desc (concat "mu4e:query:" query)
+ link desc)
+ (org-add-link-props :link link :description desc)
+ link)
+ ;; storing links to messages
+ (let* ((msg (mu4e-message-at-point))
+ (msgid (or (plist-get msg :message-id) "<none>"))
+ (from (or (plist-get msg :from) '(("none" . "none"))))
+ (fromname (car (car from)))
+ (fromaddress (cdr (car from)))
+ (to (or (plist-get msg :to) '(("none" . "none"))))
+ (toname (car (car to)))
+ (toaddress (cdr (car to)))
+ (fromto (if (mu4e-user-mail-address-p fromaddress)
+ (format "to %s <%s>" toname toaddress)
+ (format "from %s <%s>" fromname fromaddress)))
+ (date (plist-get msg :date))
+ (date-ts (format-time-string (org-time-stamp-format t) date))
+ (date-ts-ia (format-time-string (org-time-stamp-format t t) date))
+ (subject (or (plist-get msg :subject) "<none>"))
+ link)
+ (org-store-link-props :type "mu4e" :link link
+ :message-id msgid)
+ (setq link (concat "mu4e:msgid:" msgid))
+ (org-add-link-props :link link
+ :to (format "%s <%s>" toname toaddress)
+ :toname toname
+ :toaddress toaddress
+ :from (format "%s <%s>" fromname fromaddress)
+ :fromname fromname
+ :fromaddress fromaddress
+ :fromto fromto
+ :date date-ts-ia
+ :date-timestamp date-ts
+ :date-timestamp-inactive date-ts-ia
+ :subject subject
+ :description (funcall org-mu4e-link-desc-func msg))
+ link))))
+
+(org-add-link-type "mu4e" 'org-mu4e-open)
+(add-hook 'org-store-link-functions 'org-mu4e-store-link)
+
+(defun org-mu4e-open (path)
+ "Open the mu4e message (for paths starting with 'msgid:') or run
+the query (for paths starting with 'query:')."
+ (cond
+ ((string-match "^msgid:\\(.+\\)" path)
+ (mu4e-view-message-with-message-id (match-string 1 path)))
+ ((string-match "^query:\\(.+\\)" path)
+ (mu4e-headers-search (match-string 1 path) current-prefix-arg))
+ (t (mu4e-error "mu4e: unrecognized link type '%s'" path))))
+
+(defun org-mu4e-store-and-capture ()
+ "Store a link to the current message or query (depending on
+`org-mu4e-link-query-in-headers-mode', and capture it with
+org-mode)."
+ (interactive)
+ (org-mu4e-store-link)
+ (org-capture))
+
+
+
+;;; editing with org-mode ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+;; below, some functions for the org->html conversion
+;; based on / inspired by Eric Schulte's org-mime.el
+;; Homepage: http://orgmode.org/worg/org-contrib/org-mime.php
+;;
+;; EXPERIMENTAL
+(defun org~mu4e-mime-file (ext path id)
+ "Create a file for an attachment."
+ (format (concat "<#part type=\"%s\" filename=\"%s\" "
+ "disposition=inline id=\"<%s>\">\n<#/part>\n")
+ ext path id))
+
+(defun org~mu4e-mime-multipart (plain html &optional images)
+ "Create a multipart/alternative with text/plain and text/html alternatives.
+If the html portion of the message includes images, wrap the html
+and images in a multipart/related part."
+ (concat "<#multipart type=alternative><#part type=text/plain>"
+ plain
+ (when images "<#multipart type=related>")
+ "<#part type=text/html>"
+ html
+ images
+ (when images "<#/multipart>\n")
+ "<#/multipart>\n"))
+
+(defun org~mu4e-mime-replace-images (str current-file)
+ "Replace images in html files with cid links."
+ (let (html-images)
+ (cons
+ (replace-regexp-in-string ;; replace images in html
+ "src=\"\\([^\"]+\\)\""
+ (lambda (text)
+ (format
+ "src=\"cid:%s\""
+ (let* ((url (and (string-match "src=\"\\([^\"]+\\)\"" text)
+ (match-string 1 text)))
+ (path (expand-file-name
+ url (file-name-directory current-file)))
+ (ext (file-name-extension path))
+ (id (replace-regexp-in-string "[\/\\\\]" "_" path)))
+ (add-to-list 'html-images
+ (org~mu4e-mime-file
+ (concat "image/" ext) path id))
+ id)))
+ str)
+ html-images)))
+
+(defun org~mu4e-mime-convert-to-html ()
+ "Convert the current body to html."
+ (unless (fboundp 'org-export-string-as)
+ (mu4e-error "require function 'org-export-string-as not found."))
+ (unless (executable-find "dvipng")
+ (mu4e-error "Required program dvipng not found"))
+ (let* ((begin
+ (save-excursion
+ (goto-char (point-min))
+ (search-forward mail-header-separator)))
+ (end (point-max))
+ (raw-body (buffer-substring begin end))
+ (tmp-file (make-temp-name (expand-file-name "mail"
+ temporary-file-directory)))
+ ;; because we probably don't want to skip part of our mail
+ (org-export-skip-text-before-1st-heading nil)
+ ;; because we probably don't want to export a huge style file
+ (org-export-htmlize-output-type 'inline-css)
+ ;; makes the replies with ">"s look nicer
+ (org-export-preserve-breaks t)
+ ;; dvipng for inline latex because MathJax doesn't work in mail
+ (org-export-with-LaTeX-fragments 'dvipng)
+ ;; to hold attachments for inline html images
+ (html-and-images
+ (org~mu4e-mime-replace-images
+ (org-export-string-as raw-body 'html t)
+ tmp-file))
+ (html-images (cdr html-and-images))
+ (html (car html-and-images)))
+ (delete-region begin end)
+ (save-excursion
+ (goto-char begin)
+ (newline)
+ (insert (org~mu4e-mime-multipart
+ raw-body html (mapconcat 'identity html-images "\n"))))))
+
+;; next some functions to make the org/mu4e-compose-mode switch as smooth as
+;; possible.
+(defun org~mu4e-mime-decorate-headers ()
+ "Make the headers visually distinctive (org-mode)."
+ (save-excursion
+ (goto-char (point-min))
+ (let* ((eoh (when (search-forward mail-header-separator)
+ (match-end 0)))
+ (olay (make-overlay (point-min) eoh)))
+ (when olay
+ (overlay-put olay 'face 'font-lock-comment-face)))))
+
+(defun org~mu4e-mime-undecorate-headers ()
+ "Don't make the headers visually distinctive.
+\(well, mu4e-compose-mode will take care of that)."
+ (save-excursion
+ (goto-char (point-min))
+ (let* ((eoh (when (search-forward mail-header-separator)
+ (match-end 0))))
+ (remove-overlays (point-min) eoh))))
+
+(defvar org-mu4e-convert-to-html nil
+ "Whether to do automatic org-mode => html conversion when sending messages.")
+
+(defun org~mu4e-mime-convert-to-html-maybe ()
+ "Convert to html if `org-mu4e-convert-to-html' is non-nil.
+This function is called when sending a message (from
+`message-send-hook') and, if non-nil, will send the message as
+the rich-text version of the what is assumed to be an org-mode
+body."
+ (when org-mu4e-convert-to-html
+ (mu4e-message "Converting to html")
+ (org~mu4e-mime-convert-to-html)))
+
+(defun org~mu4e-mime-switch-headers-or-body ()
+ "Switch the buffer to either mu4e-compose-mode (when in headers)
+or org-mode (when in the body)."
+ (interactive)
+ (let* ((sepapoint
+ (save-excursion
+ (goto-char (point-min))
+ (search-forward-regexp mail-header-separator nil t))))
+ ;; only do stuff when the sepapoint exist; note that after sending the
+ ;; message, this function maybe called on a message with the sepapoint
+ ;; stripped. This is why we don't use `message-point-in-header'.
+ (when sepapoint
+ (cond
+ ;; we're in the body, but in mu4e-compose-mode?
+ ;; if so, switch to org-mode
+ ((and (> (point) sepapoint) (eq major-mode 'mu4e-compose-mode))
+ (org-mode)
+ (add-hook 'before-save-hook
+ (lambda ()
+ (mu4e-error "Switch to mu4e-compose-mode (M-m) before saving."))
+ nil t)
+ (org~mu4e-mime-decorate-headers)
+ (local-set-key (kbd "M-m")
+ (lambda (keyseq)
+ (interactive "kEnter mu4e-compose-mode key sequence: ")
+ (let ((func (lookup-key mu4e-compose-mode-map keyseq)))
+ (if func (funcall func) (insert keyseq))))))
+ ;; we're in the headers, but in org-mode?
+ ;; if so, switch to mu4e-compose-mode
+ ((and (<= (point) sepapoint) (eq major-mode 'org-mode))
+ (org~mu4e-mime-undecorate-headers)
+ (mu4e-compose-mode)
+ (add-hook 'message-send-hook 'org~mu4e-mime-convert-to-html-maybe nil t)))
+ ;; and add the hook
+ (add-hook 'post-command-hook 'org~mu4e-mime-switch-headers-or-body t t))))
+
+
+(defun org-mu4e-compose-org-mode ()
+ "Pseudo-Minor mode for mu4e-compose-mode, to edit the message
+body using org-mode."
+ (interactive)
+ (unless (member major-mode '(org-mode mu4e-compose-mode))
+ (mu4e-error "Need org-mode or mu4e-compose-mode"))
+ ;; we can check if we're already in org-mu4e-compose-mode by checking if the
+ ;; post-command-hook is set; hackish...but a buffer-local variable does not
+ ;; seem to survive buffer switching
+ (if (not (member 'org~mu4e-mime-switch-headers-or-body post-command-hook))
+ (progn
+ (org~mu4e-mime-switch-headers-or-body)
+ (mu4e-message
+ (concat
+ "org-mu4e-compose-org-mode enabled; "
+ "press M-m before issuing message-mode commands")))
+ (progn ;; otherwise, remove crap
+ (remove-hook 'post-command-hook 'org~mu4e-mime-switch-headers-or-body t)
+ (org~mu4e-mime-undecorate-headers) ;; shut off org-mode stuff
+ (mu4e-compose-mode)
+ (message "org-mu4e-compose-org-mode disabled"))))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+(provide 'org-mu4e)
+
+;;; org-mu4e.el ends here
diff --git a/_spacemacs.d/local/mu4e/org-old-mu4e.el b/_spacemacs.d/local/mu4e/org-old-mu4e.el
new file mode 100644
index 0000000..d73a780
--- /dev/null
+++ b/_spacemacs.d/local/mu4e/org-old-mu4e.el
@@ -0,0 +1,289 @@
+;;; org-mu4e -- Support for links to mu4e messages/queries from within org-mode,
+;;; and for writing message in org-mode, sending them as rich-text
+;;
+;; Copyright (C) 2012-2016 Dirk-Jan C. Binnema
+
+;; Author: Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
+;; Maintainer: Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
+;; Keywords: outlines, hypermedia, calendar, mail
+;; Version: 0.0
+
+;; 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 <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;;; Code:
+
+;; This should support org-mode versions 6.x. 7.x
+
+;; the 'noerror is just to make sure bytecompilations does not break...
+;; FIXME: find a better solution
+(require 'org nil 'noerror)
+(require 'org-exp nil 'noerror)
+
+(eval-when-compile (require 'cl))
+(require 'mu4e)
+
+(defgroup org-mu4e nil
+ "Settings for the org-mode related functionality in mu4e."
+ :group 'mu4e
+ :group 'org)
+
+(defcustom org-mu4e-link-desc-func
+ (lambda (msg) (or (plist-get msg :subject) "No subject"))
+ "Function that takes a msg and returns a string for the
+description part of an org-mode link.
+
+Example usage:
+
+ (defun my-link-descr (msg)
+ (let ((subject (or (plist-get msg :subject)
+ \"No subject\"))
+ (date (or (format-time-string mu4e-headers-date-format
+ (mu4e-msg-field msg :date))
+ \"No date\")))
+ (concat subject \" \" date)))
+
+ (setq org-mu4e-link-desc-func 'my-link-descr)"
+ :type 'function
+ :group 'org-mu4e)
+
+(defun org-mu4e-store-link ()
+ "Store a link to a mu4e query or message."
+ (cond
+ ;; storing links to queries
+ ((eq major-mode 'mu4e-headers-mode)
+ (let* ((query (mu4e-last-query))
+ desc link)
+ (org-store-link-props :type "mu4e" :query query)
+ (setq
+ desc (concat "mu4e:query:" query)
+ link desc)
+ (org-add-link-props :link link :description desc)
+ link))
+ ;; storing links to messages
+ ((eq major-mode 'mu4e-view-mode)
+ (let* ((msg (mu4e-message-at-point))
+ (msgid (or (plist-get msg :message-id) "<none>"))
+ link)
+ (org-store-link-props :type "mu4e" :link link
+ :message-id msgid)
+ (setq link (concat "mu4e:msgid:" msgid))
+ (org-add-link-props :link link
+ :description (funcall org-mu4e-link-desc-func msg))
+ link))))
+
+(org-add-link-type "mu4e" 'org-mu4e-open)
+(add-hook 'org-store-link-functions 'org-mu4e-store-link)
+
+(defun org-mu4e-open (path)
+ "Open the mu4e message (for paths starting with 'msgid:') or run
+the query (for paths starting with 'query:')."
+ (require 'mu4e)
+ (cond
+ ((string-match "^msgid:\\(.+\\)" path)
+ (mu4e-view-message-with-message-id (match-string 1 path)))
+ ((string-match "^query:\\(.+\\)" path)
+ (mu4e-headers-search (match-string 1 path) current-prefix-arg))
+ (t (mu4e-error "mu4e: unrecognized link type '%s'" path))))
+
+
+
+
+;;; editing with org-mode ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+;; below, some functions for the org->html conversion
+;; based on / inspired by Eric Schulte's org-mime.el
+;; Homepage: http://orgmode.org/worg/org-contrib/org-mime.php
+;;
+;; EXPERIMENTAL
+(defun org~mu4e-mime-file (ext path id)
+ "Create a file for an attachment."
+ (format (concat "<#part type=\"%s\" filename=\"%s\" "
+ "disposition=inline id=\"<%s>\">\n<#/part>\n")
+ ext path id))
+
+(defun org~mu4e-mime-multipart (plain html &optional images)
+ "Create a multipart/alternative with text/plain and text/html alternatives.
+If the html portion of the message includes images, wrap the html
+and images in a multipart/related part."
+ (concat "<#multipart type=alternative><#part type=text/plain>"
+ plain
+ (when images "<#multipart type=related>")
+ "<#part type=text/html>"
+ html
+ images
+ (when images "<#/multipart>\n")
+ "<#/multipart>\n"))
+
+(defun org~mu4e-mime-replace-images (str current-file)
+ "Replace images in html files with cid links."
+ (let (html-images)
+ (cons
+ (replace-regexp-in-string ;; replace images in html
+ "src=\"\\([^\"]+\\)\""
+ (lambda (text)
+ (format
+ "src=\"cid:%s\""
+ (let* ((url (and (string-match "src=\"\\([^\"]+\\)\"" text)
+ (match-string 1 text)))
+ (path (expand-file-name
+ url (file-name-directory current-file)))
+ (ext (file-name-extension path))
+ (id (replace-regexp-in-string "[\/\\\\]" "_" path)))
+ (add-to-list 'html-images
+ (org~mu4e-mime-file
+ (concat "image/" ext) path id))
+ id)))
+ str)
+ html-images)))
+
+(defun org~mu4e-mime-convert-to-html ()
+ "Convert the current body to html."
+ (unless (fboundp 'org-export-string)
+ (mu4e-error "require function 'org-export-string not found."))
+ (unless (executable-find "dvipng")
+ (mu4e-error "Required program dvipng not found"))
+ (let* ((begin
+ (save-excursion
+ (goto-char (point-min))
+ (search-forward mail-header-separator)))
+ (end (point-max))
+ (raw-body (buffer-substring begin end))
+ (tmp-file (make-temp-name (expand-file-name "mail"
+ temporary-file-directory)))
+ (body (org-export-string raw-body 'org
+ (file-name-directory tmp-file)))
+ ;; because we probably don't want to skip part of our mail
+ (org-export-skip-text-before-1st-heading nil)
+ ;; because we probably don't want to export a huge style file
+ (org-export-htmlize-output-type 'inline-css)
+ ;; makes the replies with ">"s look nicer
+ (org-export-preserve-breaks t)
+ ;; dvipng for inline latex because MathJax doesn't work in mail
+ (org-export-with-LaTeX-fragments 'dvipng)
+ ;; to hold attachments for inline html images
+ (html-and-images
+ (org~mu4e-mime-replace-images
+ (org-export-string raw-body 'html
+ (file-name-directory tmp-file))
+ tmp-file))
+ (html-images (cdr html-and-images))
+ (html (car html-and-images)))
+ (delete-region begin end)
+ (save-excursion
+ (goto-char begin)
+ (newline)
+ (insert (org~mu4e-mime-multipart
+ body html (mapconcat 'identity html-images "\n"))))))
+
+;; next some functions to make the org/mu4e-compose-mode switch as smooth as
+;; possible.
+(defun org~mu4e-mime-decorate-headers ()
+ "Make the headers visually distinctive (org-mode)."
+ (save-excursion
+ (goto-char (point-min))
+ (let* ((eoh (when (search-forward mail-header-separator)
+ (match-end 0)))
+ (olay (make-overlay (point-min) eoh)))
+ (when olay
+ (overlay-put olay 'face 'font-lock-comment-face)))))
+
+(defun org~mu4e-mime-undecorate-headers ()
+ "Don't make the headers visually distinctive.
+\(well, mu4e-compose-mode will take care of that)."
+ (save-excursion
+ (goto-char (point-min))
+ (let* ((eoh (when (search-forward mail-header-separator)
+ (match-end 0))))
+ (remove-overlays (point-min) eoh))))
+
+(defvar org-mu4e-convert-to-html nil
+ "Whether to do automatic org-mode => html conversion when sending messages.")
+
+(defun org~mu4e-mime-convert-to-html-maybe ()
+ "Convert to html if `org-mu4e-convert-to-html' is non-nil.
+This function is called when sending a message (from
+`message-send-hook') and, if non-nil, will send the message as
+the rich-text version of the what is assumed to be an org-mode
+body."
+ (when org-mu4e-convert-to-html
+ (mu4e-message "Converting to html")
+ (org~mu4e-mime-convert-to-html)))
+
+(defun org~mu4e-mime-switch-headers-or-body ()
+ "Switch the buffer to either mu4e-compose-mode (when in headers)
+or org-mode (when in the body)."
+ (interactive)
+ (let* ((sepapoint
+ (save-excursion
+ (goto-char (point-min))
+ (search-forward-regexp mail-header-separator nil t))))
+ ;; only do stuff when the sepapoint exist; note that after sending the
+ ;; message, this function maybe called on a message with the sepapoint
+ ;; stripped. This is why we don't use `message-point-in-header'.
+ (when sepapoint
+ (cond
+ ;; we're in the body, but in mu4e-compose-mode?
+ ;; if so, switch to org-mode
+ ((and (> (point) sepapoint) (eq major-mode 'mu4e-compose-mode))
+ (org-mode)
+ (add-hook 'before-save-hook
+ (lambda ()
+ (mu4e-error "Switch to mu4e-compose-mode (M-m) before saving."))
+ nil t)
+ (org~mu4e-mime-decorate-headers)
+ (local-set-key (kbd "M-m")
+ (lambda (keyseq)
+ (interactive "kEnter mu4e-compose-mode key sequence: ")
+ (let ((func (lookup-key mu4e-compose-mode-map keyseq)))
+ (if func (funcall func) (insert keyseq))))))
+ ;; we're in the headers, but in org-mode?
+ ;; if so, switch to mu4e-compose-mode
+ ((and (<= (point) sepapoint) (eq major-mode 'org-mode))
+ (org~mu4e-mime-undecorate-headers)
+ (mu4e-compose-mode)
+ (add-hook 'message-send-hook 'org~mu4e-mime-convert-to-html-maybe nil t)))
+ ;; and add the hook
+ (add-hook 'post-command-hook 'org~mu4e-mime-switch-headers-or-body t t))))
+
+
+(defun org-mu4e-compose-org-mode ()
+ "Pseudo-Minor mode for mu4e-compose-mode, to edit the message
+body using org-mode."
+ (interactive)
+ (unless (member major-mode '(org-mode mu4e-compose-mode))
+ (mu4e-error "Need org-mode or mu4e-compose-mode"))
+ ;; we can check if we're already in org-mu4e-compose-mode by checking if the
+ ;; post-command-hook is set; hackish...but a buffer-local variable does not
+ ;; seem to survive buffer switching
+ (if (not (member 'org~mu4e-mime-switch-headers-or-body post-command-hook))
+ (progn
+ (org~mu4e-mime-switch-headers-or-body)
+ (mu4e-message
+ (concat
+ "org-mu4e-compose-org-mode enabled; "
+ "press M-m before issuing message-mode commands")))
+ (progn ;; otherwise, remove crap
+ (remove-hook 'post-command-hook 'org~mu4e-mime-switch-headers-or-body t)
+ (org~mu4e-mime-undecorate-headers) ;; shut off org-mode stuff
+ (mu4e-compose-mode)
+ (message "org-mu4e-compose-org-mode disabled"))))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+(provide 'org-mu4e)
+
+;;; org-mu4e.el ends here