diff --git a/emacs/mu4e-compose.el b/emacs/mu4e-compose.el index b6e2c684..b6f5845e 100644 --- a/emacs/mu4e-compose.el +++ b/emacs/mu4e-compose.el @@ -159,13 +159,11 @@ e-mail addresses. If LST is nil, returns nil." 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 (plist-get origmsg :reply-to)) - (to-lst - (or reply-to - (delete-duplicates - (plist-get origmsg :from) - :test #'mu4e~compose-address-cell-equal)))) - to-lst)) + (message "REPLY TO %S" (plist-get origmsg :reply-to)) + (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 @@ -435,8 +433,7 @@ needed, set the Fcc header, and register the handler function." (let ((message-hidden-headers mu4e~compose-hidden-headers)) (use-local-map mu4e-compose-mode-map) (make-local-variable 'message-default-charset) - (message-hide-headers) - + ;; if the default charset is not set, use UTF-8 (unless message-default-charset (setq message-default-charset 'utf-8)) @@ -541,15 +538,17 @@ Gnus' `message-mode'." ;; set compose mode -- so now hooks can run (mu4e-compose-mode) + (message-hide-headers) - ;; 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)))) + ;; 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 @@ -702,4 +701,4 @@ message." ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -(provide 'mu4e-compose) +(provide 'mu4e-compose) diff --git a/emacs/mu4e.texi b/emacs/mu4e.texi index f8f357b8..54ce2b67 100644 --- a/emacs/mu4e.texi +++ b/emacs/mu4e.texi @@ -1327,6 +1327,7 @@ with other tools. @menu * Setting the default emacs mail program:: * Creating org-mode links:: +* Rich-text messages with org-mode:: * Maintaining an address-book with org-contacts:: * Getting new mail notifications with Sauron:: * Speedbar support:: @@ -1369,6 +1370,50 @@ to the query or message the link points to with either @t{M-x org-agenda-open-link} in agenda buffers, or @t{M-x org-open-at-point} elsewhere - both are typically bound to @kbd{C-c C-o}. +@node Rich-text messages with org-mode +@section Rich-text messages with org-mode + +@t{org-mode} has some nice facilities for editing texts -- creating lists, +tables, mathematical formulae etc. In addition, it can convert them to +@abbr{HTML}. + +An @emph{experimental} @t{mu4e} feature lets you edit your messages with +@t{org-mode}, and (optionally) convert them on the fly (when sending them) to +messages with an HTML-part containing the rich-text version of your messages. + +To enable all this, make sure you have +@lisp +(require 'org-mu4e) +@end lisp +somewhere in your setup, and also make sure that the @t{dvipng} program is +available in your path. + +Then, when composing a message, you can use @code{M-x +org-mu4e-compose-org-mode} to enable this mode, or, alternatively, put in the +mode-hook for @t{mu4e-compose-mode}. + +@t{org-mu4e-compose-org-mode} behaves more or less like a minor-mode. When it +is active, editing the message body takes place in @t{org-mode}, while editing +the headers uses the normal message editing mode, @t{mu4e-compose-mode}. + +Now, if you want to automatically convert the @t{org-mode} markup to rich-text +when sending messages, you need to set the variable +@code{org-mu4e-convert-to-html} to non-nil: + +@lisp +(setq org-mu4e-convert-to-html t) +@end lisp + +To send the message or execute other @t{mu4e-compose-mode}/@t{message-mode} +commands on the message, first press @key{M-m}. Thus, for example, to send the +message, you'd press @key{M-m C-c}. + +The code for doing the conversion is based on Eric Schultze's +@t{org-mime}@footnote{@url{http://orgmode.org/worg/org-contrib/org-mime.php}}, +but has been customized for use with @t{mu4e}. In particular, the +mode-switching between @t{org-mode} and @t{mu4e-compose-mode} is +@t{mu4e-specific}. + @node Maintaining an address-book with org-contacts @section Maintaining an address-book with org-contacts diff --git a/emacs/org-mu4e.el b/emacs/org-mu4e.el index 0392203e..688ec504 100644 --- a/emacs/org-mu4e.el +++ b/emacs/org-mu4e.el @@ -86,7 +86,7 @@ the query (for paths starting with 'query:')." (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 @@ -99,7 +99,7 @@ and images in a multipart/related part." 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) @@ -147,88 +147,96 @@ and images in a multipart/related part." (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) + (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")))))) + 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 (where) - "If WHERE is the symbol 'body, prefix headers with '#' block; -when it is 'headers, remove this." +(defun org~mu4e-mime-decorate-headers () + "Make the headers visually distinctive (org-mode)." (save-excursion - (message-narrow-to-headers) - (goto-char (point-min)) - (case where - (headers - (when (looking-at "#") - (goto-char (point-min)) - (while (re-search-forward "^#" nil t) - (replace-match "")))) - (body - (unless (looking-at "#") - (goto-char (point-min)) - (while (re-search-forward "^" nil t) - (replace-match "#"))))) - (widen))) + (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." - (message "Converting to html") (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) + (setq org-mu4e-compose-org-mode t) + (add-hook 'post-command-hook 'org~mu4e-mime-switch-headers-or-body t t) + (let ((func (lookup-key (current-local-map) key))) + (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 (&optional firsttime) + +(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))) - (where ;; is point in the headers or in the body? - (if (> (point) sepapoint) 'body 'headers)) - (first-char (buffer-substring (point-min) (1+ (point-min)))) - (state-changed - (if (string= first-char "#") - (eq where 'headers) - (eq where 'body)))) - (when (or firsttime state-changed) - ;; now make sure the headers correspond to the mode... - (case where - (headers - (org~mu4e-mime-decorate-headers 'headers) + (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. + (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 t t)) - (body - (org-mode) - (org~mu4e-mime-decorate-headers 'body) - (local-set-key (kbd "M-m") - (lambda () - (interactive) - (org~mu4e-mime-decorate-headers 'headers) - (mu4e-compose-mode) - (setq org-mu4e-compose-org-mode t) - (add-hook 'message-send-hook - 'org~mu4e-mime-convert-to-html-maybe t t)))) - (otherwise (error "unexpected where %S" where))) - (setq org-mu4e-compose-org-mode t) + '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)))) + 'org~mu4e-mime-switch-headers-or-body t t)))) -(defvar org-mu4e-compose-org-mode nil) -(setq org-mu4e-compose-org-mode nil) (defun org-mu4e-compose-org-mode () "Pseudo-Minor mode for mu4e-compose-mode, to edit the message @@ -236,19 +244,23 @@ or org-mode (when in the body)," (interactive) (unless (member major-mode '(org-mode mu4e-compose-mode)) (error "Need org-mode or mu4e-compose-mode")) - (if (not org-mu4e-compose-org-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 t) - (message "org-mu4e-compose-org-mode enabled")) + (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-decorate-headers 'headers) ;; shut off org-mode stuff + (org~mu4e-mime-undecorate-headers) ;; shut off org-mode stuff (setq org-mu4e-compose-org-mode nil) (mu4e-compose-mode) (message "org-mu4e-compose-org-mode disabled")))) - ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - - ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (provide 'org-mu4e)