This commit is contained in:
syl20bnr 2013-01-03 01:53:00 -05:00
parent a60d268ff4
commit 4bf880cf49
24 changed files with 6143 additions and 0 deletions

1
.gitignore vendored
View File

@ -10,3 +10,4 @@ eproject.lst
/.emacs.desktop.lock
/eshell/alias
/eshell/lastdir
/url/cookies

View File

@ -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

View File

@ -3,6 +3,7 @@
emacs-eclim
evil-plugins
flymake
mu4e
))
;; load extensions

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

1074
extensions/mu4e/mu4e-view.el Normal file

File diff suppressed because it is too large Load Diff

90
extensions/mu4e/mu4e.el Normal file
View File

@ -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)

265
extensions/mu4e/org-mu4e.el Normal file
View File

@ -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

View File

@ -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 '&nbsp_place_holder;'")

5
init-package/init-org.el Normal file
View File

@ -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)

View File

@ -31,6 +31,7 @@
multiple-cursors
multi-term
nose
org
p4
paredit
powerline