mu4e
This commit is contained in:
parent
a60d268ff4
commit
4bf880cf49
|
@ -10,3 +10,4 @@ eproject.lst
|
|||
/.emacs.desktop.lock
|
||||
/eshell/alias
|
||||
/eshell/lastdir
|
||||
/url/cookies
|
||||
|
|
|
@ -14,6 +14,23 @@
|
|||
(setq-default save-place t)
|
||||
(setq save-place-file (expand-file-name ".places" user-emacs-directory))
|
||||
|
||||
;; save a bunch of variables to the desktop file
|
||||
;; for lists specify the len of the maximal saved data also
|
||||
(setq desktop-globals-to-save
|
||||
(append '((extended-command-history . 30)
|
||||
(file-name-history . 100)
|
||||
(grep-history . 30)
|
||||
(compile-history . 30)
|
||||
(minibuffer-history . 50)
|
||||
(query-replace-history . 60)
|
||||
(read-expression-history . 60)
|
||||
(regexp-history . 60)
|
||||
(regexp-search-ring . 20)
|
||||
(search-ring . 20)
|
||||
(shell-command-history . 50)
|
||||
tags-file-name
|
||||
register-alist)))
|
||||
|
||||
;; Make emacs open all files in last emacs session (taken from ergoemacs).
|
||||
|
||||
;; This functionality is provided by desktop-save-mode
|
|
@ -3,6 +3,7 @@
|
|||
emacs-eclim
|
||||
evil-plugins
|
||||
flymake
|
||||
mu4e
|
||||
))
|
||||
|
||||
;; load extensions
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
;; auto-generated
|
||||
(defconst mu4e-about "* About mu4e
|
||||
|
||||
*mu4e* is an emacs e-mail client based on the [[http://djcbsoftware.nl/code/mu][mu]] email search engine. It was
|
||||
written & designed by /Dirk-Jan C. Binnema/, with contributions from others.
|
||||
|
||||
*mu4e* and *mu* are free software, licensed under the terms of the [[http://www.gnu.org/licenses/gpl-3.0.html][GNU GPLv3]].
|
||||
|
||||
You can get the code from [[https://github.com/djcb/mu][the git repository]]; there, you can also [[https://github.com/djcb/mu/issues][file bugs
|
||||
and feature requests]].
|
||||
|
||||
*mu4e* has its own [[info:mu4e][manual]], which includes an [[info:mu4e#FAQ%20-%20Frequently%20Anticipated%20Questions][FAQ]]. If that is not enough,
|
||||
there's also the [[http://groups.google.com/group/mu-discuss][mu mailing list]].
|
||||
|
||||
[Press *q* to quit this buffer]
|
||||
|
||||
# Local Variables:
|
||||
# mode: org; org-startup-folded: nil
|
||||
# End:
|
||||
" "About mu4e.")
|
||||
(provide 'mu4e-about)
|
|
@ -0,0 +1,167 @@
|
|||
;;; mu4e-actions.el -- part of mu4e, the mu mail user agent
|
||||
;;
|
||||
;; Copyright (C) 2011-2012 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 'mu4e-utils)
|
||||
(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 (plist-get 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)
|
||||
(error "msg2pdf not found; please set `mu4e-msg2pdf'"))
|
||||
(let* ((pdf
|
||||
(shell-command-to-string
|
||||
(concat mu4e-msg2pdf " "
|
||||
(shell-quote-argument (mu4e-msg-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))
|
||||
(message "==> %S %S" pdf (mu4e-msg-field msg :path))
|
||||
(error "Failed to create PDF file"))
|
||||
(find-file pdf)))
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
|
||||
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
(defun mu4e-action-view-in-browser (msg)
|
||||
"View the body of the message in a web browser. You can influence
|
||||
the browser to use with the variable `browse-url-generic-program'."
|
||||
(let ((html (mu4e-msg-field msg :body-html))
|
||||
(tmpfile (format "%s/%d.html" temporary-file-directory (random))))
|
||||
(unless html (error "No html part for this message"))
|
||||
(with-temp-file tmpfile
|
||||
(insert html)
|
||||
(save-buffer)
|
||||
(url-view-url (concat "file:///" tmpfile)))))
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
|
||||
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
(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-msg-field msg :body-txt)
|
||||
(error "No text body for this message"))
|
||||
(with-temp-buffer
|
||||
(insert (mu4e-msg-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'."
|
||||
(interactive)
|
||||
(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)
|
||||
(error "org-capture is not available."))
|
||||
(unless mu4e-org-contacts-file
|
||||
(error "`mu4e-org-contacts-file' is not defined."))
|
||||
(let* ((sender (car-safe (mu4e-msg-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))))
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
|
||||
|
||||
(provide 'mu4e-actions)
|
|
@ -0,0 +1,796 @@
|
|||
;; mu4e-compose.el -- part of mu4e, the mu mail user agent for emacs
|
||||
;;
|
||||
;; Copyright (C) 2011-2012 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
|
||||
|
||||
;;; Code:
|
||||
;; we use some stuff from gnus..
|
||||
|
||||
(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)
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; Composing / Sending messages
|
||||
(defgroup mu4e-compose nil
|
||||
"Customizations for composing/sending messages."
|
||||
:group 'mu4e)
|
||||
|
||||
(defcustom mu4e-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)
|
||||
|
||||
(defcustom mu4e-sent-messages-behavior 'sent
|
||||
"Determines what mu4e does with sent messages - this is a symbol
|
||||
which can be either:
|
||||
'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."
|
||||
:type 'symbol
|
||||
:safe 'symbolp
|
||||
:group 'mu4e-compose)
|
||||
|
||||
|
||||
(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)
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
|
||||
(defun mu4e-compose-attach-captured-message()
|
||||
"Insert the last captured message file as an attachment."
|
||||
(interactive)
|
||||
(unless mu4e-captured-message
|
||||
(error "No message has been captured"))
|
||||
(let ((path (plist-get mu4e-captured-message :path)))
|
||||
(unless (file-exists-p path)
|
||||
(error "Captured message file not found"))
|
||||
(mml-attach-file
|
||||
path
|
||||
"application/octet-stream"
|
||||
(or (plist-get mu4e-captured-message :subject) "No subject")
|
||||
"attachment")))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(defun mu4e~compose-user-agent-construct ()
|
||||
"Return the User-Agent string for mu4e. This is either the value
|
||||
of `mu4e-user-agent', or, if not set, a string based on the versions
|
||||
of mu4e and emacs."
|
||||
(format "mu4e %s; emacs %s" mu4e-mu-version emacs-version))
|
||||
|
||||
|
||||
(defun mu4e~compose-cite-original (msg)
|
||||
"Return a cited version of the original message MSG (ie., the
|
||||
plist). This function use gnus' `message-cite-function', and as
|
||||
such all its settings apply."
|
||||
(with-temp-buffer
|
||||
(when (fboundp 'mu4e-view-message-text) ;; keep bytecompiler happy
|
||||
(insert (mu4e-view-message-text msg))
|
||||
;; this seems to be needed, otherwise existing signatures
|
||||
;; won't be stripped
|
||||
(message-yank-original)
|
||||
(goto-char (point-min))
|
||||
(push-mark (point-max))
|
||||
(funcall message-cite-function)
|
||||
(pop-mark)
|
||||
(buffer-string))))
|
||||
|
||||
(defun mu4e~compose-header (hdr val)
|
||||
"Return a header line of the form HDR: VAL\n. If VAL is nil,
|
||||
return nil."
|
||||
(when val (format "%s: %s\n" hdr val)))
|
||||
|
||||
|
||||
(defun mu4e~compose-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 (which may be empty) and the message-id. If the
|
||||
message-id is empty, returns the old References. If both are empty,
|
||||
return nil."
|
||||
(let ((refs (plist-get msg :references))
|
||||
(old-msgid (plist-get msg :message-id)))
|
||||
(when old-msgid
|
||||
(setq refs (append refs (list old-msgid)))
|
||||
(mapconcat
|
||||
(lambda (msgid) (format "<%s>" msgid))
|
||||
refs ","))))
|
||||
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; determine the recipient fields for new messages
|
||||
|
||||
(defun mu4e~compose-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>" name email)
|
||||
(format "%s" email))))
|
||||
lst ", ")))
|
||||
|
||||
(defun mu4e~compose-address-cell-equal (cell1 cell2)
|
||||
"Return t if cell1 and cell2 have the same e-mail
|
||||
address (case-insensitively), nil otherwise. cell1 and cell2 are
|
||||
cons cells (NAME . EMAIL)."
|
||||
(string=
|
||||
(downcase (or (cdr cell1) ""))
|
||||
(downcase (or (cdr cell2) ""))))
|
||||
|
||||
|
||||
(defun mu4e~compose-create-to-lst (origmsg)
|
||||
"Create a list of address for the To: in a new message, based on
|
||||
the original message ORIGMSG. If the Reply-To address is set, use
|
||||
that, otherwise use the From address. Note, whatever was in the To:
|
||||
field before, goes to the Cc:-list (if we're doing a reply-to-all)."
|
||||
(let ((reply-to
|
||||
(or (plist-get origmsg :reply-to) (plist-get origmsg :from))))
|
||||
(delete-duplicates reply-to :test #'mu4e~compose-address-cell-equal)))
|
||||
|
||||
|
||||
(defun mu4e~compose-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~compose-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~compose-address-cell-equal cc-cell to-cell))
|
||||
(mu4e~compose-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)
|
||||
(mu4e~compose-address-cell-equal cc-cell
|
||||
(cons nil user-mail-address)))
|
||||
cc-lst))))
|
||||
cc-lst)))
|
||||
|
||||
(defun mu4e~compose-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~compose-recipients-list-to-string
|
||||
(case field
|
||||
(:to
|
||||
(mu4e~compose-create-to-lst origmsg))
|
||||
(:cc
|
||||
(mu4e~compose-create-cc-lst origmsg reply-all))
|
||||
(otherwise
|
||||
(error "Unsupported field")))))
|
||||
|
||||
|
||||
(defun mu4e~compose-from-construct ()
|
||||
"Construct a value for the From:-field of the reply to MSG,
|
||||
based on `user-full-name' and `user-mail-address'; if the latter is
|
||||
nil, function returns nil."
|
||||
(when user-mail-address
|
||||
(if user-full-name
|
||||
(format "%s <%s>" user-full-name user-mail-address)
|
||||
(format "%s" user-mail-address))))
|
||||
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
(defun mu4e~compose-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 file. Also see
|
||||
`mu4e-remove-mail-header-separator'."
|
||||
(save-excursion
|
||||
(let ((sepa (propertize mail-header-separator
|
||||
'intangible t 'read-only t
|
||||
'font-lock-face 'mu4e-system-face)))
|
||||
(goto-char (point-min))
|
||||
;; search for the first empty line
|
||||
(if (search-forward-regexp "^$" nil t)
|
||||
(replace-match sepa)
|
||||
(progn ;; no empty line? then prepend one
|
||||
(goto-char (point-max))
|
||||
(insert (concat "\n" sepa)))))))
|
||||
|
||||
(defun mu4e~compose-remove-mail-header-separator ()
|
||||
"Remove `mail-header-separator; we do this before saving a
|
||||
file (and restore it afterwardds), to ensure that the separator
|
||||
never hits the disk. Also see `mu4e~compose-insert-mail-header-separator."
|
||||
(save-excursion
|
||||
(goto-char (point-min))
|
||||
;; remove the --text follows this line-- separator
|
||||
(when (search-forward-regexp (concat "^" mail-header-separator))
|
||||
(let ((inhibit-read-only t))
|
||||
(replace-match "")))))
|
||||
|
||||
(defun mu4e~compose-user-wants-reply-all (origmsg)
|
||||
"Ask user whether she wants to reply to *all* recipients if there
|
||||
are more than 1 (based on ORIGMSG)."
|
||||
(let* ((recipnum
|
||||
(+ (length (mu4e~compose-create-to-lst origmsg))
|
||||
(length (mu4e~compose-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~compose-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* ((hostname
|
||||
(downcase
|
||||
(save-match-data
|
||||
(substring system-name
|
||||
(string-match "^[^.]+" system-name) (match-end 0))))))
|
||||
(format "%s-%x%x.%s:2,%s"
|
||||
(format-time-string "%Y%m%d" (current-time))
|
||||
(emacs-pid) (random t) hostname (or flagstr ""))))
|
||||
|
||||
(defun mu4e~compose-common-construct ()
|
||||
"Construct the common headers for each message."
|
||||
(mu4e~compose-header "User-agent" (mu4e~compose-user-agent-construct)))
|
||||
|
||||
(defconst mu4e~compose-reply-prefix "Re: "
|
||||
"String to prefix replies with.")
|
||||
|
||||
(defun mu4e~compose-reply-construct (origmsg)
|
||||
"Create a draft message as a reply to original message ORIGMSG."
|
||||
(let* ((recipnum
|
||||
(+ (length (mu4e~compose-create-to-lst origmsg))
|
||||
(length (mu4e~compose-create-cc-lst origmsg t))))
|
||||
(reply-all (mu4e~compose-user-wants-reply-all origmsg))
|
||||
(old-msgid (plist-get origmsg :message-id))
|
||||
(subject
|
||||
(concat mu4e~compose-reply-prefix
|
||||
(message-strip-subject-re (or (plist-get origmsg :subject) "")))))
|
||||
(message "S:%s" subject)
|
||||
(concat
|
||||
(mu4e~compose-header "From" (or (mu4e~compose-from-construct) ""))
|
||||
(mu4e~compose-header "Reply-To" mu4e-reply-to-address)
|
||||
(mu4e~compose-header "To" (mu4e~compose-recipients-construct :to origmsg))
|
||||
(mu4e~compose-header "Cc" (mu4e~compose-recipients-construct :cc origmsg
|
||||
reply-all))
|
||||
(mu4e~compose-header "Subject" subject)
|
||||
(mu4e~compose-header "References"
|
||||
(mu4e~compose-references-construct origmsg))
|
||||
(mu4e~compose-common-construct)
|
||||
(when old-msgid
|
||||
(mu4e~compose-header "In-reply-to" (format "<%s>" old-msgid)))
|
||||
"\n\n"
|
||||
(mu4e~compose-cite-original origmsg))))
|
||||
|
||||
(defconst mu4e~compose-forward-prefix "Fwd: "
|
||||
"String to prefix replies with.")
|
||||
|
||||
(defun mu4e~compose-forward-construct (origmsg)
|
||||
"Create a draft forward message for original message ORIGMSG."
|
||||
(let ((subject
|
||||
(or (plist-get origmsg :subject) "")))
|
||||
(concat
|
||||
(mu4e~compose-header "From" (or (mu4e~compose-from-construct) ""))
|
||||
(mu4e~compose-header "Reply-To" mu4e-reply-to-address)
|
||||
(mu4e~compose-header "To" "")
|
||||
(mu4e~compose-common-construct)
|
||||
(mu4e~compose-header "References"
|
||||
(mu4e~compose-references-construct origmsg))
|
||||
(mu4e~compose-header "Subject"
|
||||
(concat
|
||||
;; if there's no Fwd: yet, prepend it
|
||||
(if (string-match "^Fwd:" subject)
|
||||
""
|
||||
mu4e~compose-forward-prefix)
|
||||
subject))
|
||||
"\n\n"
|
||||
(mu4e~compose-cite-original origmsg))))
|
||||
|
||||
|
||||
(defun mu4e~compose-newmsg-construct ()
|
||||
"Create a new message."
|
||||
(concat
|
||||
(mu4e~compose-header "From" (or (mu4e~compose-from-construct) ""))
|
||||
(mu4e~compose-header "Reply-To" mu4e-reply-to-address)
|
||||
(mu4e~compose-header "To" "")
|
||||
(mu4e~compose-header "Subject" "")
|
||||
(mu4e~compose-common-construct)))
|
||||
|
||||
|
||||
(defun mu4e~compose-open-new-draft-file (compose-type &optional msg)
|
||||
"Open a draft file for a new message, creating it if it does not
|
||||
already exist, and optionally fill it with STR. Function also adds
|
||||
the new message to the database. When the draft message is added to
|
||||
the database, `mu4e-path-docid-map' will be updated, so that we can
|
||||
use the new docid. Returns the full path to the new message."
|
||||
(let* ((draft
|
||||
(concat mu4e-maildir mu4e-drafts-folder "/cur/"
|
||||
(mu4e~compose-message-filename-construct "DS")))
|
||||
(str (case compose-type
|
||||
(reply (mu4e~compose-reply-construct msg))
|
||||
(forward (mu4e~compose-forward-construct msg))
|
||||
(new (mu4e~compose-newmsg-construct))
|
||||
(t (error "unsupported compose-type %S" compose-type)))))
|
||||
(when str
|
||||
(with-current-buffer (find-file-noselect draft)
|
||||
(insert str)))
|
||||
draft)) ;; return the draft buffer file
|
||||
|
||||
|
||||
;; '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~setup-fcc-maybe ()
|
||||
"Maybe setup Fcc, based on `mu4e-sent-messages-behavior'. If
|
||||
needed, set the Fcc header, and register the handler function."
|
||||
(let* ((mdir
|
||||
(case mu4e-sent-messages-behavior
|
||||
(delete nil)
|
||||
(trash mu4e-trash-folder)
|
||||
(sent mu4e-sent-folder)
|
||||
(otherwise
|
||||
(error "unsupported value '%S' `mu4e-sent-messages-behavior'."
|
||||
mu4e-sent-messages-behavior))))
|
||||
(fccfile (and mdir
|
||||
(concat mu4e-maildir mdir "/cur/"
|
||||
(mu4e~compose-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
|
||||
;; hander 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 maildir))))))) ;; update the database
|
||||
|
||||
|
||||
(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~compose-remove-mail-header-separator nil t)
|
||||
(add-hook 'after-save-hook
|
||||
(lambda ()
|
||||
(mu4e~compose-set-friendly-buffer-name)
|
||||
(mu4e~compose-insert-mail-header-separator)
|
||||
(set-buffer-modified-p nil)
|
||||
;; update the file on disk -- ie., without the separator
|
||||
(mu4e~proc-add (buffer-file-name) mu4e-drafts-folder)) nil t))
|
||||
|
||||
(defconst mu4e~compose-hidden-headers
|
||||
`("^References:" "^Face:" "^X-Face:"
|
||||
"^X-Draft-From:" "^User-agent:")
|
||||
"Hidden headers when composing.")
|
||||
|
||||
(defconst mu4e~compose-address-fields-regexp
|
||||
"^\\(To\\|B?Cc\\|Reply-To\\|From\\):")
|
||||
|
||||
(defun mu4e~compose-find-completion-style (some-style)
|
||||
"Find completion style SOME-STYLE in completion-styles-alist, or return nil."
|
||||
(find-if (lambda (style) (eq some-style (car style))) completion-styles-alist))
|
||||
|
||||
(defconst mu4e~completion-cycle-treshold 5
|
||||
"mu4e value for `completion-cycle-treshold'.")
|
||||
|
||||
(defun mu4e~compose-setup-completion ()
|
||||
"Set up autocompletion of addresses."
|
||||
(let ((compstyle
|
||||
(or (mu4e~compose-find-completion-style 'substring)
|
||||
(mu4e~compose-find-completion-style 'partial-completion))))
|
||||
;; emacs-24+ has 'substring, otherwise we try partial-completion, otherwise
|
||||
;; we leave it at the default
|
||||
(when compstyle
|
||||
(make-local-variable 'completion-styles)
|
||||
(add-to-list 'completion-styles (car compstyle)))
|
||||
(when (boundp 'completion-cycle-threshold)
|
||||
(make-local-variable 'completion-cycle-threshold)
|
||||
(setq completion-cycle-threshold mu4e~completion-cycle-treshold))
|
||||
(add-to-list 'completion-at-point-functions 'mu4e~compose-complete-contact)
|
||||
;; needed for emacs 23...
|
||||
(when (= emacs-major-version 23)
|
||||
(make-local-variable 'message-completion-alist)
|
||||
(setq message-completion-alist
|
||||
(cons
|
||||
(cons mu4e~compose-address-fields-regexp 'completion-at-point)
|
||||
message-completion-alist)))))
|
||||
|
||||
(define-derived-mode mu4e-compose-mode message-mode "mu4e:compose"
|
||||
"Major mode for the mu4e message composition, derived from `message-mode'.
|
||||
\\{message-mode-map}."
|
||||
(let ((message-hidden-headers mu4e~compose-hidden-headers))
|
||||
(use-local-map mu4e-compose-mode-map)
|
||||
(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 (and mu4e-compose-complete-addresses
|
||||
(boundp 'completion-at-point-functions))
|
||||
(mu4e~compose-setup-completion))
|
||||
|
||||
;; setup the fcc-stuff, if needed
|
||||
(add-hook 'message-send-hook
|
||||
(lambda ()
|
||||
;; for safety, always save the draft before sending
|
||||
(set-buffer-modified-p t)
|
||||
(save-buffer)
|
||||
(mu4e~setup-fcc-maybe)) nil t)
|
||||
;; when the message has been sent.
|
||||
(add-hook 'message-sent-hook
|
||||
(lambda ()
|
||||
(setq mu4e-sent-func 'mu4e-sent-handler)
|
||||
(mu4e~proc-sent (buffer-file-name) mu4e-drafts-folder)) nil)))
|
||||
|
||||
(defconst mu4e~compose-buffer-max-name-length 30
|
||||
"Maximum length of the mu4e-send-buffer-name.")
|
||||
|
||||
(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)))))
|
||||
|
||||
(defconst mu4e~compose-hidden-headers
|
||||
'("^References:" "^Face:" "^X-Face:" "^X-Draft-From:"
|
||||
"^User-Agent:" "^In-Reply-To:")
|
||||
"List of regexps with message headers that are to be hidden.")
|
||||
|
||||
(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', `new'. `edit' is for
|
||||
editing existing 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).
|
||||
|
||||
The name of the draft folder is constructed from the concatenation
|
||||
of `mu4e-maildir' and `mu4e-drafts-folder' (therefore, these must be
|
||||
set).
|
||||
|
||||
The message file name is a unique name determined by
|
||||
`mu4e-send-draft-file-name'.
|
||||
|
||||
The initial STR would be created from either
|
||||
`mu4e~compose-reply-construct', ar`mu4e~compose-forward-construct'
|
||||
or `mu4e~compose-newmsg-construct'. The editing buffer is using
|
||||
Gnus' `message-mode'."
|
||||
(unless mu4e-maildir (error "mu4e-maildir not set"))
|
||||
(unless mu4e-drafts-folder (error "mu4e-drafts-folder not set"))
|
||||
(let ((inhibit-read-only t)
|
||||
(draft
|
||||
(if (member compose-type '(reply forward new))
|
||||
(mu4e~compose-open-new-draft-file compose-type original-msg)
|
||||
(if (eq compose-type 'edit)
|
||||
(plist-get original-msg :path)
|
||||
(error "unsupported compose-type %S" compose-type)))))
|
||||
(find-file draft)
|
||||
;; insert mail-header-separator, which is needed by message mode to separate
|
||||
;; headers and body. will be removed before saving to disk
|
||||
(mu4e~compose-insert-mail-header-separator)
|
||||
(insert "\n") ;; insert a newline after 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))))
|
||||
|
||||
;; include the message header (if it's set); but not when editing an
|
||||
;; existing message.
|
||||
(unless (eq compose-type 'edit)
|
||||
(message-insert-signature))
|
||||
|
||||
;; hide some headers
|
||||
(let ((message-hidden-headers mu4e~compose-hidden-headers))
|
||||
(message-hide-headers))
|
||||
|
||||
;; set compose mode -- so now hooks can run
|
||||
(mu4e-compose-mode)
|
||||
|
||||
;; buffer is not user-modified yet
|
||||
(mu4e~compose-set-friendly-buffer-name compose-type)
|
||||
(set-buffer-modified-p nil)
|
||||
|
||||
;; now jump to some use positions, and start writing that mail!
|
||||
(if (member compose-type '(new forward))
|
||||
(message-goto-to)
|
||||
(message-goto-body))))
|
||||
|
||||
(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))
|
||||
(kill-buffer buf)))
|
||||
(mu4e-message "Message sent"))
|
||||
|
||||
(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.
|
||||
|
||||
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
|
||||
(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"))
|
||||
(when (and forwarded-from (string-match "<\\(.*\\)>" forwarded-from))
|
||||
(mu4e~proc-move (match-string 1 forwarded-from) nil "+P")))))))
|
||||
|
||||
|
||||
|
||||
(defun mu4e-compose (compose-type)
|
||||
"Start composing a message of COMPOSE-TYPE, where COMPOSE-TYPE is
|
||||
a symbol, one of `reply', `forward', `edit', `new'. All but `new'
|
||||
take the message at point as input. Symbol `edit' is only allowed
|
||||
for draft messages."
|
||||
(unless (member compose-type '(reply forward edit new))
|
||||
(error "Invalid compose type '%S'" compose-type))
|
||||
;; 'new is special, since it takes no existing message as arg therefore,
|
||||
;; we don't need to call thec backend, and call the handler *directly*
|
||||
(if (eq compose-type 'new)
|
||||
(mu4e~compose-handler 'new)
|
||||
;; otherwise, we need the doc-id
|
||||
(let ((docid (mu4e-field-at-point :docid)))
|
||||
;; note, the first two chars of the line (the mark margin) does *not*
|
||||
;; have the 'draft property; thus, we check one char before the end of
|
||||
;; the current line instead
|
||||
(unless (or (not (eq compose-type 'edit))
|
||||
(member 'draft (mu4e-field-at-point :flags)))
|
||||
(error "Editing is only allowed for draft messages"))
|
||||
;; 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 way
|
||||
;; 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 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-new ()
|
||||
"Start writing a new message."
|
||||
(interactive)
|
||||
(mu4e-compose 'new))
|
||||
|
||||
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; address completion; inspired by org-contacts.el
|
||||
(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))))
|
||||
(orig (buffer-substring-no-properties start end))
|
||||
(completion-ignore-case t))
|
||||
(list start end mu4e~contacts-for-completion)))))
|
||||
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; mu4e-compose-func and mu4e-send-func are wrappers so we can set ourselves
|
||||
;; as default emacs mailer (define-mail-user-agent etc.)
|
||||
|
||||
(defun mu4e~compose-mail (&optional to subject other-headers continue
|
||||
switch-function yank-action send-actions return-action)
|
||||
"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
|
||||
(define-mail-user-agent 'mu4e-user-agent
|
||||
'mu4e~compose-mail
|
||||
'message-send-and-exit
|
||||
'message-kill-buffer
|
||||
'message-send-hook)
|
||||
|
||||
(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)))))))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
(provide 'mu4e-compose)
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,152 @@
|
|||
;;; mu4e-main.el -- part of mu4e, the mu mail user agent
|
||||
;;
|
||||
;; Copyright (C) 2011-2012 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-utils) ;; utility functions
|
||||
|
||||
(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-show-window)
|
||||
|
||||
(define-key map "$" 'mu4e-show-log)
|
||||
(define-key map "A" 'mu4e-about)
|
||||
(define-key map "H" 'mu4e-display-manual)
|
||||
map)
|
||||
|
||||
"Keymap for the *mu4e-main* buffer.")
|
||||
(fset 'mu4e-main-mode-map mu4e-main-mode-map)
|
||||
|
||||
(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))
|
||||
|
||||
|
||||
(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
|
||||
"\\[\\(\\w+\\)\\]"
|
||||
(lambda(m)
|
||||
(format "[%s]"
|
||||
(propertize (match-string 1 str) '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 "\\w" newstr)
|
||||
(- (length newstr) 1) 'mouse-face 'highlight newstr) newstr))
|
||||
|
||||
|
||||
(defun mu4e~main-view ()
|
||||
"Show the mu4e main view."
|
||||
(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-view-header-key-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* [U]pdate email & database\n"
|
||||
'mu4e-update-mail-show-window)
|
||||
|
||||
;; show the queue functions if `smtpmail-queue-dir' is defined
|
||||
(if (file-directory-p smtpmail-queue-dir)
|
||||
(concat
|
||||
(mu4e~main-action-str "\t* toggle [m]ail sending mode "
|
||||
'mu4e~main-toggle-mail-sending-mode)
|
||||
"(" (propertize (if smtpmail-queue-mail "queued" "direct")
|
||||
'face 'mu4e-view-header-key-face) ")\n"
|
||||
(mu4e~main-action-str "\t* [f]lush queued mail\n"
|
||||
'smtpmail-send-queued-mail))
|
||||
"")
|
||||
"\n"
|
||||
(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)
|
||||
(switch-to-buffer buf))))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; Interactive functions
|
||||
(defun mu4e~main-toggle-mail-sending-mode ()
|
||||
"Toggle sending mail mode, either queued or direct."
|
||||
(interactive)
|
||||
(unless (file-directory-p smtpmail-queue-dir)
|
||||
(error "`smtp-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))
|
||||
|
||||
(provide 'mu4e-main)
|
|
@ -0,0 +1,312 @@
|
|||
;;; mu4e-mark.el -- part of mu4e, the mu mail user agent
|
||||
;;
|
||||
;; Copyright (C) 2011-2012 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)
|
||||
|
||||
(defcustom mu4e-headers-leave-behavior 'ask
|
||||
"What to do when user leaves the headers view (e.g. quits,
|
||||
refreshes or does a new search). Value is one of the following
|
||||
symbols:
|
||||
- ask (ask the user whether to ignore the marks)
|
||||
- apply (automatically apply the marks before doing anything else)
|
||||
- ignore (automatically ignore the marks without asking)."
|
||||
:type 'symbol
|
||||
:group 'mu4e-headers)
|
||||
|
||||
(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."
|
||||
(make-local-variable 'mu4e~mark-map)
|
||||
(setq mu4e~mark-map (make-hash-table :size 16)))
|
||||
|
||||
(defun mu4e~mark-clear ()
|
||||
"Clear the marks subsystem."
|
||||
(clrhash mu4e~mark-map))
|
||||
|
||||
(defun mu4e-mark-at-point (mark &optional target)
|
||||
"Mark (or unmark) message at point. MARK specifies the
|
||||
mark-type. For `move'-marks there is also the TARGET argument,
|
||||
which specifies to which maildir the message is to be moved. The
|
||||
functipn works in both headers buffers and message buffers.
|
||||
|
||||
The following marks are available, and the corresponding props:
|
||||
|
||||
MARK TARGET description
|
||||
----------------------------------------------------------
|
||||
`move' y move the message to some folder
|
||||
`trash' n move the message to `mu4e-trash-folder'
|
||||
`delete' n remove the message
|
||||
`read' n mark the message as read
|
||||
`unread' n mark the message as unread
|
||||
`flag' n mark this message for flagging
|
||||
`unflag' n mark this message for unflagging
|
||||
`deferred' n mark this message for *something* (decided later)
|
||||
`unmark' n unmark this message"
|
||||
(interactive)
|
||||
(let* ((docid (mu4e~headers-docid-at-point))
|
||||
;; 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.
|
||||
(markcell
|
||||
(case mark
|
||||
(move `("m" . ,target))
|
||||
(trash '("d" . "trash"))
|
||||
(delete '("D" . "delete"))
|
||||
(unread '("o" . "unread"))
|
||||
(read '("r" . "read"))
|
||||
(flag '("+" . "flag"))
|
||||
(unflag '("-" . "unflag"))
|
||||
(deferred '("*" . "deferred"))
|
||||
(unmark '(" " . nil))
|
||||
(otherwise (error "Invalid mark %S" mark))))
|
||||
(markkar (car markcell))
|
||||
(target (cdr markcell)))
|
||||
(unless docid (error "No message on this line"))
|
||||
(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 target mu4e-headers-show-target)
|
||||
(let* ((targetstr (propertize (concat "-> " 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-set (mark &optional target)
|
||||
"Mark the header at point, or, if region is active, mark all
|
||||
headers in the region."
|
||||
(interactive)
|
||||
(if (use-region-p)
|
||||
;; mark all messages in the region.
|
||||
(save-excursion
|
||||
(let ((b (region-beginning)) (e (region-end)))
|
||||
(goto-char b)
|
||||
(while (<= (line-beginning-position) e)
|
||||
(mu4e-mark-at-point mark target)
|
||||
(forward-line 1))))
|
||||
;; just a single message
|
||||
(mu4e-mark-at-point mark target)))
|
||||
|
||||
(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-for-move-set (&optional target)
|
||||
"Mark message at point or, if region is active, all messages in
|
||||
the region, for moving to maildir TARGET. If target is not
|
||||
provided, function asks for it."
|
||||
(interactive)
|
||||
(unless (mu4e~headers-docid-at-point)
|
||||
(error "No message at point."))
|
||||
(let* ((target (or 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)))
|
||||
(mu4e-mark-set 'move target))))
|
||||
|
||||
|
||||
(defun mu4e~mark-get-markpair (prompt &optional allow-deferred)
|
||||
"Ask user for a mark; return (MARK . TARGET). If ALLOW-DEFERRED
|
||||
is non-nil, allow the 'deferred' pseudo mark as well."
|
||||
(let* ((marks '(("move" . move)
|
||||
("dtrash" . trash)
|
||||
("Delete" . delete)
|
||||
("ounread" . unread)
|
||||
("read" . read)
|
||||
("+flag" . flag)
|
||||
("-unflag" . unflag)
|
||||
("unmark" . unmark)))
|
||||
(marks
|
||||
(if allow-deferred
|
||||
(append marks (list '("*deferred" . deferred)))
|
||||
marks))
|
||||
(mark (mu4e-read-option prompt marks))
|
||||
(target
|
||||
(when (eq mark 'move)
|
||||
(mu4e-ask-maildir-check-exists "Move message to: "))))
|
||||
(cons mark target)))
|
||||
|
||||
|
||||
(defun mu4e-mark-resolve-deferred-marks ()
|
||||
"Check if there are any deferred marks. If there are such marks,
|
||||
replace them with a _real_ mark (ask the user which one)."
|
||||
(interactive)
|
||||
(let ((markpair))
|
||||
(maphash
|
||||
(lambda (docid val)
|
||||
(let ((mark (car val)) (target (cdr val)))
|
||||
(when (eql mark 'deferred)
|
||||
(unless markpair
|
||||
(setq markpair
|
||||
(mu4e~mark-get-markpair "Set deferred mark to: " nil)))
|
||||
(save-excursion
|
||||
(when (mu4e~headers-goto-docid docid)
|
||||
(mu4e-mark-set (car markpair) (cdr markpair)))))))
|
||||
mu4e~mark-map)))
|
||||
|
||||
(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 matches 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 users
|
||||
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)
|
||||
(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)))
|
||||
;; note: whenever you do something with the message,
|
||||
;; it looses its N (new) flag
|
||||
(case mark
|
||||
(move (mu4e~proc-move docid target "-N"))
|
||||
(read (mu4e~proc-move docid nil "+S-u-N"))
|
||||
(unread (mu4e~proc-move docid nil "-S+u-N"))
|
||||
(flag (mu4e~proc-move docid nil "+F-u-N"))
|
||||
(unflag (mu4e~proc-move docid nil "-F-N"))
|
||||
(trash
|
||||
(unless mu4e-trash-folder
|
||||
(error "`mu4e-trash-folder' not set"))
|
||||
(mu4e~proc-move docid mu4e-trash-folder "+T-N"))
|
||||
(delete (mu4e~proc-remove docid))
|
||||
(otherwise (error "Unrecognized mark %S" mark)))))
|
||||
mu4e~mark-map))
|
||||
(mu4e-mark-unmark-all)
|
||||
(message nil))))
|
||||
|
||||
(defun mu4e-mark-unmark-all ()
|
||||
"Unmark all marked messages."
|
||||
(interactive)
|
||||
(when (or (null mu4e~mark-map) (zerop (hash-table-count mu4e~mark-map)))
|
||||
(error "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-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,
|
||||
quiting the buffer) is taken; returning t means 'take the following
|
||||
action', return nil means 'don't do anything'"
|
||||
(let ((marknum (if mu4e~mark-map (hash-table-count mu4e~mark-map) 0))
|
||||
(what mu4e-headers-leave-behavior))
|
||||
(unless (zerop marknum) ;; nothing to do
|
||||
(unless (or (eq what 'ignore) (eq what 'apply))
|
||||
;; if `mu4e-headers-leave-behavior' is not apply or ignore, ask the user
|
||||
(setq what
|
||||
(let ((what (mu4e-read-option
|
||||
"There are existing marks; should we: "
|
||||
'( ("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
|
|
@ -0,0 +1,9 @@
|
|||
;; auto-generated
|
||||
(defconst mu4e-mu-version "0.9.8.5"
|
||||
"Required mu binary version; mu4e's version must agree with
|
||||
this.")
|
||||
|
||||
(defconst mu4e-builddir "/build/buildd/maildir-utils-0.9.8.5"
|
||||
"Top-level build directory.")
|
||||
|
||||
(provide 'mu4e-meta)
|
|
@ -0,0 +1,471 @@
|
|||
;; mu4e-proc.el -- part of mu4e, the mu mail user agent
|
||||
;;
|
||||
;; Copyright (C) 2011-2012 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).")
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
|
||||
(defun mu4e~proc-start ()
|
||||
"Start the mu server process."
|
||||
(unless (file-executable-p mu4e-mu-binary)
|
||||
(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
|
||||
(when mu4e~proc-process
|
||||
(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 "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-is-running ()
|
||||
"Whether the mu process is running."
|
||||
(and mu4e~proc-process
|
||||
(memq (process-status mu4e~proc-process)
|
||||
'(run open listen connect stop))))
|
||||
|
||||
|
||||
(defun 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."
|
||||
(when mu4e~proc-buf
|
||||
;; 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
|
||||
(when b (string-to-number (match-string 1 mu4e~proc-buf) 16))))
|
||||
;; does mu4e~proc-buf contain the full sexp?
|
||||
(when (and b (>= (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.
|
||||
(let ((objcons
|
||||
(ignore-errors ;; note: this may fail if we killed the process
|
||||
;; in the middle
|
||||
(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 :version) (plist-get sexp :doccount)))
|
||||
|
||||
;; received a contacts message
|
||||
((plist-get 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 :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.h
|
||||
;;(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 (mu4e-message (format "mu server process received signal %d" code)))))
|
||||
((eq status 'exit)
|
||||
(cond
|
||||
((eq code 0)
|
||||
(message nil)) ;; don't do anything
|
||||
((eq code 11)
|
||||
(mu4e-message "Database is locked by another process"))
|
||||
((eq code 19)
|
||||
(mu4e-message "Database empty; try indexing some messages"))
|
||||
(t (mu4e-message "mu server process ended with exit code %d" code))))
|
||||
(t
|
||||
(mu4e-message "Something bad happened to the mu server process")))))
|
||||
|
||||
(defun mu4e~proc-send-command (frm &rest args)
|
||||
"Send as command to the mu server process; start the process if needed."
|
||||
(unless (mu4e~proc-is-running)
|
||||
(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--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 "remove docid:%d" docid))
|
||||
|
||||
(defun mu4e~proc-find (query threads sortfield revert maxnum)
|
||||
"Start a database query for QUERY. If THREADS is non-nil, show
|
||||
results in threaded fasion, SORTFIELD is a symmbol describing the
|
||||
field to sort by (or nil); see `mu4e~headers-sortfield-choices'. If
|
||||
REVERT is non-nil, sort Z->A instead of A->Z. MAXNUM determines the
|
||||
maximum number of results to return, or nil for 'unlimited'. 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
|
||||
"find query:\"%s\" threads:%s sortfield:%s reverse:%s maxnum:%d"
|
||||
query
|
||||
(if threads "true" "false")
|
||||
(format "%S" sortfield)
|
||||
(if revert "true" "false")
|
||||
(if maxnum maxnum -1)))
|
||||
|
||||
(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'.
|
||||
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)
|
||||
(error "At least one of maildir and flags must be specified"))
|
||||
(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\"" maildir))))
|
||||
(mu4e~proc-send-command "move %s %s %s"
|
||||
idparam (or flagstr "") (or path ""))))
|
||||
|
||||
|
||||
|
||||
(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 e.g. `mu4e-my-email-addresses')."
|
||||
(let ((addrs
|
||||
(when my-addresses
|
||||
(mapconcat 'identity my-addresses ","))))
|
||||
(if addrs
|
||||
(mu4e~proc-send-command "index path:\"%s\" my-addresses:%s"
|
||||
path addrs)
|
||||
(mu4e~proc-send-command "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>)."
|
||||
(mu4e~proc-send-command "add path:\"%s\" maildir:\"%s\""
|
||||
path 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 "sent path:\"%s\" maildir:\"%s\""
|
||||
path maildir))
|
||||
|
||||
(defun mu4e~proc-compose (type &optional docid)
|
||||
"Start composing a message of certain TYPE (a symbol, either
|
||||
`forward', `reply', `edit' or `new', based on an original
|
||||
message (ie, replying to, forwarding, editing) 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 new))
|
||||
(error "Unsupported compose-type %S" type))
|
||||
(unless (eq (null docid) (eq type 'new))
|
||||
(error "`new' implies docid not-nil, and vice-versa"))
|
||||
(mu4e~proc-send-command "compose type:%s docid:%d"
|
||||
(symbol-name type) docid))
|
||||
|
||||
(defun mu4e~proc-mkdir (path)
|
||||
"Create a new maildir-directory at filesystem PATH."
|
||||
(mu4e~proc-send-command "mkdir path:\"%s\"" path))
|
||||
|
||||
(defun mu4e~proc-extract (action docid partidx &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 "extract "
|
||||
(case action
|
||||
(save
|
||||
(format "action:save docid:%d index:%d path:\"%s\""
|
||||
docid partidx path))
|
||||
(open (format "action:open docid:%d index:%d" docid partidx))
|
||||
(temp
|
||||
(format "action:temp docid:%d index:%d what:%s param:\"%s\""
|
||||
docid partidx what param))
|
||||
(otherwise (error "Unsupported action %S" action))))))
|
||||
(mu4e~proc-send-command cmd)))
|
||||
|
||||
|
||||
(defun mu4e~proc-ping ()
|
||||
"Sends a ping to the mu server, expecting a (:pong ...) in
|
||||
response."
|
||||
(mu4e~proc-send-command "ping"))
|
||||
|
||||
(defun mu4e~proc-contacts (personal after)
|
||||
"Sends the contacts command to the mu server, expecting
|
||||
a (:contacts (<list>)) 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
|
||||
"contacts personal:%s after:%d"
|
||||
(if personal "true" "false")
|
||||
(or after 0)))
|
||||
|
||||
(defun mu4e~proc-view (docid-or-msgid &optional images)
|
||||
"Get one particular message based on its DOCID-OR-MSGID (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-message-func'."
|
||||
(mu4e~proc-send-command "view %s extract-images:%s"
|
||||
(mu4e--docid-msgid-param docid-or-msgid)
|
||||
(if images "true" "false")))
|
||||
|
||||
(provide 'mu4e-proc)
|
||||
|
||||
;; End of mu4e-proc.el
|
|
@ -0,0 +1,132 @@
|
|||
;;; mu4e-speedbar --- Speedbar support for mu4e
|
||||
|
||||
;; Copyright (C) 2012 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, you can either send email to this
|
||||
;; program's author (see below) or write to:
|
||||
;;
|
||||
;; The Free Software Foundation, Inc.
|
||||
;; 675 Mass Ave.
|
||||
;; Cambridge, MA 02139, USA.
|
||||
;;
|
||||
;; Please send bug reports, etc. to self@antono.info
|
||||
;;
|
||||
|
||||
;;; 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 mu4e-maildir)))
|
||||
|
||||
(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
|
|
@ -0,0 +1,876 @@
|
|||
;;; mu4e-utils.el -- part of mu4e, the mu mail user agent
|
||||
;;
|
||||
;; Copyright (C) 2011-2012 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:
|
||||
|
||||
;; Utility functions used in the mu4e
|
||||
|
||||
;;; Code:
|
||||
(eval-when-compile (byte-compile-disable-warning 'cl-functions))
|
||||
(require 'cl)
|
||||
|
||||
(require 'html2text)
|
||||
(require 'mu4e-vars)
|
||||
(require 'mu4e-about)
|
||||
(require 'doc-view)
|
||||
(require 'org) ;; for org-parse-time-string
|
||||
|
||||
(defcustom mu4e-html2text-command nil
|
||||
"Shell command that converts HTML from stdin into plain text on
|
||||
stdout. If this is not defined, the emacs `html2text' tool will be
|
||||
used when faced with html-only message. If you use htmltext, it's
|
||||
recommended you use \"html2text -utf8 -width 72\"."
|
||||
:type 'string
|
||||
:group 'mu4e-view
|
||||
:safe 'stringp)
|
||||
|
||||
(defcustom mu4e-view-prefer-html nil
|
||||
"Whether to base the body display on the HTML-version of the
|
||||
e-mail message (if there is any."
|
||||
:type 'boolean
|
||||
:group 'mu4e-view)
|
||||
|
||||
|
||||
(defun mu4e-create-maildir-maybe (dir)
|
||||
"Offer to create DIR if it does not exist yet. Return t if the
|
||||
dir already existed, or has been created, nil otherwise."
|
||||
(if (and (file-exists-p dir) (not (file-directory-p dir)))
|
||||
(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 yes. Create now?" dir))
|
||||
(mu4e~proc-mkdir dir))
|
||||
(t nil)))
|
||||
|
||||
(defun mu4e-format (frm &rest args)
|
||||
"Create [mu4e]-prefixed string based on format FRM and ARGS."
|
||||
(concat "[" mu4e-logo "] " (apply 'format frm args)))
|
||||
|
||||
(defun mu4e-message (frm &rest args)
|
||||
"Like `message', but prefixed with mu4e. If we're waiting for
|
||||
user-input, don't show anyhting."
|
||||
(unless (waiting-for-user-input-p)
|
||||
(message "%s" (apply 'mu4e-format frm args))))
|
||||
|
||||
(defun mu4e~read-char-choice (prompt choices)
|
||||
"Compatiblity wrapper for `read-char-choice', which is emacs-24
|
||||
only."
|
||||
(let ((choice) (ok) (inhibit-quit nil))
|
||||
(while (not ok)
|
||||
(message nil);; this seems needed...
|
||||
(setq choice (read-char-exclusive prompt))
|
||||
(setq ok (member choice choices)))
|
||||
choice))
|
||||
|
||||
(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)))
|
||||
(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 quit]")
|
||||
;; 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)))
|
||||
(unless chosen (error "%S not found" response))
|
||||
(cdr chosen)))
|
||||
|
||||
|
||||
(defun mu4e~get-maildirs-1 (path &optional mdir)
|
||||
"Get maildirs under path, recursively, as a list of relative
|
||||
paths."
|
||||
;; first, we get a list of all directory paths under PATH that have a
|
||||
;; directory 'cur' as leaf; then we we remove from that list all of those that
|
||||
;; don't have tmp, new sister dirs. And there we're done!
|
||||
;; 1. get all proper subdirs of the current dir
|
||||
(let* ((subdirs
|
||||
(remove-if
|
||||
(lambda (de)
|
||||
(or (not (file-directory-p (concat path mdir "/" de)))
|
||||
(string-match "\\.\\{1,2\\}$" de)))
|
||||
(directory-files (concat path mdir))))
|
||||
;; 2. get the list of dirs with a /cur leaf dir
|
||||
(maildirs '()))
|
||||
(dolist (dir subdirs)
|
||||
(when (string= dir "cur")
|
||||
;; be pedantic, and insist on there being a new/tmp as well
|
||||
(when (and (file-directory-p (concat path mdir "/new" ))
|
||||
(file-directory-p (concat path mdir "/tmp")))
|
||||
(add-to-list 'maildirs (if mdir mdir "/") t)))
|
||||
(setq maildirs (append maildirs
|
||||
(mu4e~get-maildirs-1 path (concat mdir "/" dir)))))
|
||||
maildirs))
|
||||
|
||||
(defvar mu4e~maildir-list nil "Cached list of maildirs.")
|
||||
|
||||
(defun mu4e-get-maildirs (path)
|
||||
"Get maildirs under path, 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-list
|
||||
(setq mu4e~maildir-list
|
||||
(sort (mu4e~get-maildirs-1 path)
|
||||
(lambda (m1 m2)
|
||||
(when (string= m1 "/")
|
||||
-1 ;; '/' comes first
|
||||
(compare-strings m1 0 nil m2 0 nil t))))))
|
||||
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."
|
||||
(unless mu4e-maildir (error "`mu4e-maildir' is not defined"))
|
||||
(let ((prompt (mu4e-format "%s" prompt)))
|
||||
(if (not mu4e-maildir-shortcuts)
|
||||
(ido-completing-read prompt
|
||||
(mu4e-get-maildirs mu4e-maildir))
|
||||
(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 (= kar ?o) ;; user chose 'other'?
|
||||
(ido-completing-read prompt (mu4e-get-maildirs mu4e-maildir))
|
||||
(or (car-safe
|
||||
(find-if (lambda (item) (= kar (cdr item))) mu4e-maildir-shortcuts))
|
||||
(error "Invalid 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-mark-for-move-set (&optional target)
|
||||
"Mark message at point or, if region is active, all messages in
|
||||
the region, for moving to maildir TARGET. If target is not
|
||||
provided, function asks for it."
|
||||
(interactive)
|
||||
(unless (mu4e~headers-docid-at-point)
|
||||
(error "No message at point."))
|
||||
(let* ((target (or 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
|
||||
(mu4e-format "%s does not exist. Create now?" fulltarget))
|
||||
(mu4e~proc-mkdir fulltarget)))
|
||||
(mu4e-mark-set 'move target))))
|
||||
|
||||
|
||||
(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 (error "`mu4e-bookmarks' is not 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
|
||||
(find-if
|
||||
(lambda (bm)
|
||||
(= kar (nth 2 bm)))
|
||||
mu4e-bookmarks)))
|
||||
(if chosen-bm
|
||||
(nth 0 chosen-bm)
|
||||
(error "Invalid shortcut '%c'" kar))))
|
||||
|
||||
|
||||
;;; 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-body-text (msg)
|
||||
"Get the body in text form for this message, which is either :body-txt,
|
||||
or if not available, :body-html converted to text. By default, it
|
||||
uses the emacs built-in `html2text'. Alternatively, if
|
||||
`mu4e-html2text-command' is non-nil, it will use that. Normally,
|
||||
function prefers the text part, but this can be changed by setting
|
||||
`mu4e-view-prefer-html'."
|
||||
(let* ((txt (plist-get msg :body-txt))
|
||||
(html (plist-get msg :body-html))
|
||||
(body
|
||||
(cond
|
||||
;; does it look like some text? ie., 20x the length of the text
|
||||
;; should be longer than the html, an heuristic to guard against
|
||||
;; 'This messages requires html' text bodies.
|
||||
((and (> (* 20 (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)
|
||||
;; if defined, use the external tool
|
||||
(if mu4e-html2text-command
|
||||
(shell-command-on-region (point-min) (point-max)
|
||||
mu4e-html2text-command nil t)
|
||||
;; otherwise...
|
||||
(html2text))
|
||||
(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-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-msg-field (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
|
||||
|
||||
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
|
||||
(plist-get msg field))
|
||||
|
||||
(defun mu4e-message-at-point (&optional raise-err)
|
||||
"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 RAISE-ERR is non-nil, raise an error when
|
||||
there is no message at point."
|
||||
(let ((msg
|
||||
(cond
|
||||
((eq major-mode 'mu4e-headers-mode)
|
||||
(get-text-property (point) 'msg))
|
||||
((eq major-mode 'mu4e-view-mode)
|
||||
mu4e~view-msg))))
|
||||
(if (and (null msg) raise-err)
|
||||
(error "No message at point")
|
||||
msg)))
|
||||
|
||||
(defun mu4e-field-at-point (field)
|
||||
"Get FIELD (a symbol, see `mu4e-header-names') for the message at
|
||||
point in eiter the headers buffer or the view buffer."
|
||||
(plist-get (mu4e-message-at-point t) field))
|
||||
|
||||
(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)))
|
||||
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; 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-message "Indexing... processed %d, updated %d"
|
||||
(plist-get info :processed) (plist-get info :updated))
|
||||
(mu4e-message
|
||||
"Indexing completed; processed %d, updated %d, cleaned-up %d"
|
||||
(plist-get info :processed) (plist-get info :updated)
|
||||
(plist-get info :cleaned-up))))
|
||||
((plist-get info :message)
|
||||
(mu4e-message "%s" (plist-get info :message))))))
|
||||
|
||||
|
||||
(defun mu4e-error-handler (errcode errmsg)
|
||||
"Handler function for showing an error."
|
||||
(case errcode
|
||||
(4 (mu4e-message "No matches for this search query."))
|
||||
(t (mu4e-message "Error %d: %s" errcode errmsg))))
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
|
||||
(defconst mu4e~update-buffer-name "*mu4e-update*"
|
||||
"Name of the buffer for message retrieval/database updating.")
|
||||
|
||||
(defconst mu4e~update-buffer-height 8
|
||||
"Height of the mu4e message retrieval/update buffer.")
|
||||
|
||||
(defun mu4e-update-mail-show-window ()
|
||||
"Try to retrieve mail (using the user-provided shell command),
|
||||
and update the database afterwards, and show the progress in a
|
||||
split-window."
|
||||
(interactive)
|
||||
(unless mu4e-get-mail-command
|
||||
(error "`mu4e-get-mail-command' is not defined"))
|
||||
;; delete any old update buffer
|
||||
(when (buffer-live-p mu4e~update-buffer-name)
|
||||
(with-current-buffer mu4e~update-buffer-name
|
||||
(kill-buffer-and-window)))
|
||||
;; create a new one
|
||||
(let ((buf (get-buffer-create mu4e~update-buffer-name))
|
||||
(win (split-window (selected-window)
|
||||
(- (window-height (selected-window)) 8))))
|
||||
(with-selected-window win
|
||||
(switch-to-buffer buf)
|
||||
(set-window-dedicated-p win t)
|
||||
(erase-buffer)
|
||||
(insert "\n") ;; FIXME -- needed so output starts
|
||||
(mu4e-update-mail buf))))
|
||||
|
||||
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; start and stopping
|
||||
(defun mu4e~fill-contacts (contacts)
|
||||
"We receive a list of contacts, which each contact of the form
|
||||
(:name NAME :mail EMAIL)
|
||||
and fill the list `mu4e~contacts-for-completion' with it, with
|
||||
each element looking like
|
||||
name <email>
|
||||
This is used by the completion function in mu4e-compose."
|
||||
(let ((lst))
|
||||
(dolist (contact contacts)
|
||||
(let ((name (plist-get contact :name))
|
||||
(mail (plist-get contact :mail)))
|
||||
(when mail
|
||||
(unless ;; ignore some address ('noreply' etc.)
|
||||
(and mu4e-compose-complete-ignore-address-regexp
|
||||
(string-match mu4e-compose-complete-ignore-address-regexp mail))
|
||||
(add-to-list 'lst
|
||||
(if name (format "%s <%s>" name mail) mail))))))
|
||||
(setq mu4e~contacts-for-completion lst)
|
||||
(mu4e-message "Contacts received: %d"
|
||||
(length mu4e~contacts-for-completion))))
|
||||
|
||||
|
||||
(defun mu4e~check-requirements ()
|
||||
"Check for the settings required for running mu4e."
|
||||
(unless (and mu4e-mu-binary (file-executable-p mu4e-mu-binary))
|
||||
(error "Please set `mu4e-mu-binary' to the full path to the mu
|
||||
binary."))
|
||||
(unless mu4e-maildir
|
||||
(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)
|
||||
mu4e-attachment-dir (expand-file-name mu4e-attachment-dir))
|
||||
(unless (mu4e-create-maildir-maybe mu4e-maildir)
|
||||
(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))
|
||||
(error "Please set %S" var))
|
||||
(let* ((dir (symbol-value var)) (path (concat mu4e-maildir dir)))
|
||||
(unless (string= (substring dir 0 1) "/")
|
||||
(error "%S must start with a '/'" dir))
|
||||
(unless (mu4e-create-maildir-maybe path)
|
||||
(error "%s (%S) does not exist" path var)))))
|
||||
|
||||
|
||||
(defun mu4e~start (&optional func)
|
||||
"If mu4e is already running, execute function FUNC (if non-nil). Otherwise,
|
||||
check various requirements, then start mu4e. When succesful, call
|
||||
FUNC (if non-nil) afterwards."
|
||||
;; if we're already running, simply go to the main view
|
||||
(if (mu4e~proc-is-running) ;; already running?
|
||||
(when func
|
||||
(funcall func))) ;; yup!
|
||||
(progn ;; nope: check whether all is okay;
|
||||
(mu4e~check-requirements)
|
||||
;; explicit version checks are a bit questionable,
|
||||
;; better to check for specific features
|
||||
(unless (>= emacs-major-version 23)
|
||||
(error "Emacs >= 23.x is required for mu4e"))
|
||||
|
||||
;; set up the 'pong' handler func
|
||||
(lexical-let ((func func))
|
||||
(setq mu4e-pong-func
|
||||
(lambda (version doccount)
|
||||
(unless (string= version mu4e-mu-version)
|
||||
(error "mu server has version %s, but we need %s"
|
||||
version mu4e-mu-version))
|
||||
(when func (funcall func))
|
||||
(when (and mu4e-update-interval (null mu4e-update-timer))
|
||||
(setq mu4e-update-timer
|
||||
(run-at-time
|
||||
0 mu4e-update-interval 'mu4e-update-mail)))
|
||||
(mu4e-message "Started mu4e with %d message%s in store"
|
||||
doccount (if (= doccount 1) "" "s")))))
|
||||
;; send the ping
|
||||
(mu4e~proc-ping)
|
||||
;; get the address list
|
||||
(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
|
||||
(org-parse-time-string mu4e-compose-complete-only-after))))))))
|
||||
|
||||
(defun mu4e~stop ()
|
||||
"Stop the mu4e session."
|
||||
(when mu4e-update-timer
|
||||
(cancel-timer mu4e-update-timer)
|
||||
(setq
|
||||
mu4e-update-timer nil
|
||||
mu4e~maildir-list nil))
|
||||
(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-update-timer nil
|
||||
"*internal* The mu4e update timer.")
|
||||
|
||||
(defconst mu4e-update-mail-name "*mu4e-update-mail*"
|
||||
"*internal* Name of the process to update mail")
|
||||
|
||||
(defun mu4e-update-mail (&optional buf)
|
||||
"Update mail (retrieve using `mu4e-get-mail-command' and update
|
||||
the database afterwards), with output going to BUF if not nil, or
|
||||
discarded if nil. After retrieving mail, update the database. Note,
|
||||
function is asynchronous, returns (almost) immediately, and all the
|
||||
processing takes part in the background, unless buf is non-nil."
|
||||
(unless mu4e-get-mail-command
|
||||
(error "`mu4e-get-mail-command' is not defined"))
|
||||
(let* ((process-connection-type t)
|
||||
(proc (start-process-shell-command
|
||||
mu4e-update-mail-name buf mu4e-get-mail-command)))
|
||||
(mu4e-message "Retrieving mail...")
|
||||
(set-process-sentinel proc
|
||||
(lambda (proc msg)
|
||||
(let* ((status (process-status proc))
|
||||
(code (process-exit-status proc))
|
||||
;; sadly, fetchmail returns '1' when there is no mail; this is
|
||||
;; not really an error of course, but it's hard to distinguish
|
||||
;; from a genuine error
|
||||
(maybe-error (or (not (eq status 'exit)) (/= code 0)))
|
||||
(buf (process-buffer proc)))
|
||||
(message nil)
|
||||
;; there may be an error, give the user up to 5 seconds to check
|
||||
(when maybe-error
|
||||
(sit-for 5))
|
||||
(mu4e~proc-index mu4e-maildir mu4e-my-email-addresses)
|
||||
(when (buffer-live-p buf)
|
||||
(kill-buffer buf)))))))
|
||||
|
||||
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; 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)
|
||||
(otherwise (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 " -> " )
|
||||
(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)
|
||||
(error "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)
|
||||
(error "Attachment %d bigger than maximum (%d)" x n))
|
||||
((< x 1)
|
||||
(error "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)
|
||||
"Display image IMG at point; optionally specify
|
||||
MAXWIDTH. Function tries to use imagemagick if available (ie.,
|
||||
emacs was compiled with inmagemagick support); otherwise MAXWIDTH
|
||||
is 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))))
|
||||
;;(message "DISPLAY: %S %S" imgpath img)
|
||||
(when img
|
||||
(newline)
|
||||
(insert-image img imgpath nil t))))
|
||||
|
||||
|
||||
|
||||
(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))
|
||||
(unless (one-window-p t)
|
||||
(delete-window win))))))) nil 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 (org-parse-time-string timestr))))
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(defconst mu4e~main-about-buffer-name "*mu4e-about*"
|
||||
"Name for the mu4e-about buffer.")
|
||||
|
||||
(defun mu4e-about ()
|
||||
"Show a buffer with the mu4e-about text."
|
||||
(interactive)
|
||||
(with-current-buffer
|
||||
(get-buffer-create mu4e~main-about-buffer-name)
|
||||
(let ((inhibit-read-only t))
|
||||
(erase-buffer)
|
||||
(insert mu4e-about)
|
||||
(org-mode)))
|
||||
(switch-to-buffer mu4e~main-about-buffer-name)
|
||||
(setq buffer-read-only t)
|
||||
(local-set-key "q" 'bury-buffer)
|
||||
(goto-char (point-min)))
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
|
||||
(provide 'mu4e-utils)
|
||||
;;; End of mu4e-utils.el
|
|
@ -0,0 +1,497 @@
|
|||
;;; mu4e-vars.el -- part of mu4e, the mu mail user agent
|
||||
;;
|
||||
;; Copyright (C) 2011-2012 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)
|
||||
|
||||
(defgroup mu4e nil
|
||||
"mu4e - mu for emacs"
|
||||
:group 'local)
|
||||
|
||||
(defcustom mu4e-mu-home nil
|
||||
"Location of the mu homedir, or nil for the default."
|
||||
:type 'directory
|
||||
:group 'mu4e
|
||||
: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")
|
||||
"Your Maildir directory; by default, mu4e assumes
|
||||
~/Maildir."
|
||||
:type 'directory
|
||||
:safe 'stringp
|
||||
:group 'mu4e)
|
||||
|
||||
(defcustom mu4e-get-mail-command nil
|
||||
"Shell command to run to retrieve new mail; e.g. 'offlineimap' or
|
||||
'fetchmail'. Note, when there is no mail, fetchmail will return 1
|
||||
as it error code, which mu4e interprets as an error."
|
||||
:type 'string
|
||||
:group 'mu4e
|
||||
:safe 'stringp)
|
||||
|
||||
(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 'integer
|
||||
:group 'mu4e
|
||||
:safe 'integerp)
|
||||
|
||||
(defcustom mu4e-attachment-dir (expand-file-name "~/")
|
||||
"Default directory for saving attachments."
|
||||
:type 'string
|
||||
:group 'mu4e
|
||||
:safe 'stringp)
|
||||
|
||||
(defcustom mu4e-user-mail-address-regexp "$^"
|
||||
"Regular expression matching the user's mail address(es). This is
|
||||
used to distinguish ourselves from others, e.g. when replying and
|
||||
in :from-or-to headers. By default, match nothing."
|
||||
:type 'string
|
||||
:group 'mu4e
|
||||
:safe 'stringp)
|
||||
|
||||
(defcustom mu4e-my-email-addresses `(,user-mail-address)
|
||||
"List of e-mail addresses to consider 'my email addresses',
|
||||
ie. addresses whose presence in an email imply that it is a
|
||||
personal message. This is used when indexing messages."
|
||||
:type '(string)
|
||||
:group 'mu4e)
|
||||
|
||||
(defvar mu4e-date-format-long "%c"
|
||||
"Date format to use in the message view, in the format of
|
||||
`format-time-string'.")
|
||||
|
||||
(defvar mu4e-search-results-limit 500
|
||||
"Maximum number of search results (or -1 for unlimited). Since
|
||||
limiting search results speeds up searches significantly, it's
|
||||
useful to limit this. Note, to ignore the limit, use a prefix
|
||||
argument (C-u) before invoking the search.")
|
||||
|
||||
(defvar mu4e-debug nil
|
||||
"When set to non-nil, log debug information to the *mu4e-log* buffer.")
|
||||
|
||||
(defvar 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.")
|
||||
|
||||
(defvar mu4e-split-view 'horizontal
|
||||
"How to show messages / headers; a symbol which is either: * a
|
||||
symbol 'horizontal: split horizontally (headers on top) * a symbol
|
||||
'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'.")
|
||||
|
||||
;; 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)."
|
||||
: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)
|
||||
|
||||
(defcustom mu4e-compose-complete-ignore-address-regexp "noreply"
|
||||
"Ignore any e-mail addresses for completion if they match this
|
||||
regexp."
|
||||
:type 'string
|
||||
:group 'mu4e-compose)
|
||||
|
||||
|
||||
|
||||
;; Folders
|
||||
(defgroup mu4e-folders nil
|
||||
"Special folders."
|
||||
:group 'mu4e)
|
||||
|
||||
(defcustom mu4e-sent-folder "/sent"
|
||||
"Your folder for sent messages, relative to `mu4e-maildir',
|
||||
e.g. \"/Sent Items\"."
|
||||
:type 'string
|
||||
:safe 'stringp
|
||||
:group 'mu4e-folders)
|
||||
|
||||
(defcustom mu4e-drafts-folder "/drafts"
|
||||
"Your folder for draft messages, relative to `mu4e-maildir',
|
||||
e.g. \"/drafts\""
|
||||
:type 'string
|
||||
:safe 'stringp
|
||||
:group 'mu4e-folders)
|
||||
|
||||
(defcustom mu4e-trash-folder "/trash"
|
||||
"Your folder for trashed messages, relative to `mu4e-maildir',
|
||||
e.g. \"/trash\"."
|
||||
:type 'string
|
||||
:safe 'stringp
|
||||
:group 'mu4e-folders)
|
||||
|
||||
|
||||
(defcustom mu4e-maildir-shortcuts nil
|
||||
"A list of maildir shortcuts to enable quickly going to the
|
||||
particular for, or quickly moving messages towards them (i.e.,
|
||||
archiving or refiling). The list contains elements of the form
|
||||
(maildir . shortcut), where MAILDIR is a maildir (such as
|
||||
\"/archive/\"), and shortcut a single shortcut character. With
|
||||
this, in the header buffer and view buffer you can execute
|
||||
`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.")
|
||||
|
||||
|
||||
|
||||
;; 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-builtin-face :bold t))
|
||||
"Face for a flagged 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 default :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-view-header-key-face
|
||||
'((t :inherit font-lock-builtin-face :bold t))
|
||||
"Face for a header key (such as \"Foo\" in \"Subject:\ Foo\") in
|
||||
the message view."
|
||||
:group 'mu4e-faces)
|
||||
|
||||
(defface mu4e-view-header-value-face
|
||||
'((t :inherit font-lock-doc-face))
|
||||
"Face for a header value (such as \"Re: Hello!\") in the message
|
||||
view."
|
||||
:group 'mu4e-faces)
|
||||
|
||||
(defface mu4e-view-link-face
|
||||
'((t :inherit font-lock-type-face :underline t))
|
||||
"Face for showing URLs and attachments in the message view."
|
||||
:group 'mu4e-faces)
|
||||
|
||||
(defface mu4e-highlight-face
|
||||
'((t :inherit font-lock-pseudo-keyword-face :bold t))
|
||||
"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-footer-face
|
||||
'((t :inherit font-lock-comment-face))
|
||||
"Face for message footers (signatures)."
|
||||
:group 'mu4e-faces)
|
||||
|
||||
(defface mu4e-view-url-number-face
|
||||
'((t :inherit font-lock-reference-face :bold t))
|
||||
"Face for the number tags for URLs."
|
||||
:group 'mu4e-faces)
|
||||
|
||||
(defface mu4e-view-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-pseudo-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)
|
||||
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; internal variables / constants
|
||||
|
||||
(defconst mu4e-header-names
|
||||
'( (:attachments . "Attach")
|
||||
(:bcc . "Bcc")
|
||||
(:cc . "Cc")
|
||||
(:date . "Date")
|
||||
(:flags . "Flags")
|
||||
(:from . "From")
|
||||
(:from-or-to . "From/To")
|
||||
(:maildir . "Maildir")
|
||||
(:path . "Path")
|
||||
(:subject . "Subject")
|
||||
(:to . "To"))
|
||||
"An alist of all possible header fields; this is used in the UI (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
|
||||
`mu4e-user-mail-address-regexp', in which case it will be equal to
|
||||
`:to'.")
|
||||
|
||||
|
||||
(defconst mu4e-logo
|
||||
(propertize "mu4e" 'face 'mu4e-title-face)
|
||||
"A propertized string for the mu4e 'logo'.")
|
||||
|
||||
(defconst mu4e-prefix
|
||||
(concat "[" mu4e-logo "]")
|
||||
"Prefix for mu4e minibuffer input.")
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; internal variables / constants
|
||||
|
||||
(defconst mu4e-header-names
|
||||
'( (:attachments . "Attach")
|
||||
(:bcc . "Bcc")
|
||||
(:cc . "Cc")
|
||||
(:date . "Date")
|
||||
(:flags . "Flags")
|
||||
(:from . "From")
|
||||
(:from-or-to . "From/To")
|
||||
(:maildir . "Maildir")
|
||||
(:path . "Path")
|
||||
(:subject . "Subject")
|
||||
(:to . "To"))
|
||||
"An alist of all possible header fields; this is used in the UI (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
|
||||
`mu4e-user-mail-address-regexp', in which case it will be equal to
|
||||
`:to'.")
|
||||
|
||||
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; 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")
|
||||
(defvar mu4e~view-buffer nil "The view buffer.")
|
||||
|
||||
(defvar mu4e~view-msg nil "The message being viewed in view mode.")
|
||||
|
||||
(defvar mu4e~contacts-for-completion nil
|
||||
"List of contacts (ie. 'name <e-mail>'),
|
||||
used by the completion functions in mu4e-compose, and filled when
|
||||
mu4e starts.")
|
||||
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; 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
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,90 @@
|
|||
;;; mu4e.el -- part of mu4e, the mu mail user agent
|
||||
;;
|
||||
;; Copyright (C) 2011-2012 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-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 define 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)
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
|
||||
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(defun mu4e ()
|
||||
"Start mu4e."
|
||||
(interactive)
|
||||
;; start mu4e, then show the main view
|
||||
(mu4e~start 'mu4e~main-view))
|
||||
|
||||
(defun mu4e-quit()
|
||||
"Quit the mu4e session."
|
||||
(interactive)
|
||||
(when (y-or-n-p (mu4e-format "Are you sure you want to quit?"))
|
||||
(mu4e~stop)))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(provide 'mu4e)
|
|
@ -0,0 +1,265 @@
|
|||
;;; 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 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:
|
||||
|
||||
(require 'org)
|
||||
(eval-when-compile (require 'org-exp))
|
||||
(eval-when-compile (require 'cl))
|
||||
(eval-when-compile (require '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 (org-make-link "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>"))
|
||||
(subject (or (plist-get msg :subject) "No subject"))
|
||||
link)
|
||||
(org-store-link-props :type "mu4e" :link link
|
||||
:message-id msgid :subject subject)
|
||||
(setq link (org-make-link "mu4e:msgid:" msgid))
|
||||
(org-add-link-props :link link :description subject)
|
||||
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-msgid (match-string 1 path)))
|
||||
((string-match "^query:\\(.+\\)" path)
|
||||
(mu4e-headers-search (match-string 1 path) current-prefix-arg))
|
||||
(t (message "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
|
||||
(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."
|
||||
(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
|
||||
"Wether to an 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
|
||||
(message "Converting to html")
|
||||
(org~mu4e-mime-convert-to-html)))
|
||||
|
||||
(defun org~mu4e-execute-key-sequence-in-compose-mode (keyseq)
|
||||
"Execute keysequence KEYSEQ by (temporarily) switching to compose
|
||||
mode."
|
||||
(mu4e-compose-mode)
|
||||
(add-hook 'post-command-hook 'org~mu4e-mime-switch-headers-or-body t t)
|
||||
(let ((func (lookup-key (current-local-map) keyseq)))
|
||||
(unless (functionp func)
|
||||
(error "Invalid key binding"))
|
||||
(add-hook 'message-send-hook 'org~mu4e-mime-convert-to-html-maybe t t)
|
||||
(funcall func)))
|
||||
|
||||
(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 ()
|
||||
(error "Switch to mu4e-compose-mode (M-m) before saving.")) nil t)
|
||||
(org~mu4e-mime-decorate-headers)
|
||||
(local-set-key (kbd "M-m")
|
||||
(lambda (key)
|
||||
(interactive "kEnter mu4e-compose-mode key sequence: ")
|
||||
(org~mu4e-execute-key-sequence-in-compose-mode key))))
|
||||
;; 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))
|
||||
(error "Need org-mode or mu4e-compose-mode"))
|
||||
(unless (executable-find "dvipng")
|
||||
(error "Required program dvipng not found"))
|
||||
;; we can check if we're already in mu4e-compose-mode by checking
|
||||
;; if the post-command-hook is set; hackish...
|
||||
(if (not (member 'org~mu4e-mime-switch-headers-or-body post-command-hook))
|
||||
(progn
|
||||
(org~mu4e-mime-switch-headers-or-body)
|
||||
(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
|
|
@ -0,0 +1,53 @@
|
|||
(require 'mu4e)
|
||||
|
||||
;; default
|
||||
;; (setq mu4e-maildir ("~/Maildir")
|
||||
|
||||
(setq mu4e-drafts-folder "/[Gmail].Drafts")
|
||||
(setq mu4e-sent-folder "/[Gmail].Sent Mail")
|
||||
(setq mu4e-trash-folder "/[Gmail].Trash")
|
||||
|
||||
;; don't save message to Sent Messages, Gmail/IMAP takes care of this
|
||||
(setq mu4e-sent-messages-behavior 'delete)
|
||||
|
||||
;; setup some handy shortcuts
|
||||
;; you can quickly switch to your Inbox -- press ``ji''
|
||||
;; then, when you want archive some messages, move them to
|
||||
;; the 'All Mail' folder by pressing ``ma''.
|
||||
|
||||
(setq mu4e-maildir-shortcuts
|
||||
'( ("/INBOX" . ?i)
|
||||
("/[Gmail].Sent Mail" . ?s)
|
||||
("/[Gmail].Trash" . ?t)
|
||||
("/[Gmail].All Mail" . ?a)))
|
||||
|
||||
;; allow for updating mail using 'U' in the main view:
|
||||
(setq mu4e-get-mail-command "offlineimap")
|
||||
|
||||
;; something about ourselves
|
||||
(setq
|
||||
user-mail-address "sylvain.benner@gmail.com"
|
||||
user-full-name "Sylvain Benner"
|
||||
message-signature "syl20bn")
|
||||
|
||||
;; alternatively, for emacs-24 you can use:
|
||||
(setq message-send-mail-function 'smtpmail-send-it
|
||||
smtpmail-stream-type 'starttls
|
||||
smtpmail-default-smtp-server "smtp.gmail.com"
|
||||
smtpmail-smtp-server "smtp.gmail.com"
|
||||
smtpmail-smtp-service 587)
|
||||
|
||||
;; don't keep message buffers around
|
||||
(setq message-kill-buffer-on-exit t)
|
||||
|
||||
;; inline images
|
||||
;;(setq mu4e-view-show-images t)
|
||||
;; use imagemagick, if available
|
||||
;; (when (fboundp 'imagemagick-register-types)
|
||||
;; (imagemagick-register-types))
|
||||
|
||||
;; prefere html version
|
||||
(setq mu4e-view-prefer-html t)
|
||||
;; html to text conversion program
|
||||
;;(setq mu4e-html2text-command "html2text -utf8 -width 72")
|
||||
(setq mu4e-html2text-command "html2markdown | grep -v ' _place_holder;'")
|
|
@ -0,0 +1,5 @@
|
|||
(require 'org-install)
|
||||
(add-to-list 'auto-mode-alist '("\\.org$" . org-mode))
|
||||
(define-key global-map "\C-cl" 'org-store-link)
|
||||
(define-key global-map "\C-ca" 'org-agenda)
|
||||
(setq org-log-done t)
|
|
@ -31,6 +31,7 @@
|
|||
multiple-cursors
|
||||
multi-term
|
||||
nose
|
||||
org
|
||||
p4
|
||||
paredit
|
||||
powerline
|
||||
|
|
Loading…
Reference in New Issue