Return-Path: X-Original-To: notmuch@notmuchmail.org Delivered-To: notmuch@notmuchmail.org Received: from localhost (localhost [127.0.0.1]) by olra.theworths.org (Postfix) with ESMTP id 06F4241ED92 for ; Wed, 15 Feb 2012 19:12:58 -0800 (PST) X-Virus-Scanned: Debian amavisd-new at olra.theworths.org X-Spam-Flag: NO X-Spam-Score: 0 X-Spam-Level: X-Spam-Status: No, score=0 tagged_above=-999 required=5 tests=[RCVD_IN_DNSWL_NONE=-0.0001] autolearn=disabled Received: from olra.theworths.org ([127.0.0.1]) by localhost (olra.theworths.org [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id 0mBuM5jTZ4mF for ; Wed, 15 Feb 2012 19:12:54 -0800 (PST) Received: from idcmail-mo2no.shaw.ca (idcmail-mo2no.shaw.ca [64.59.134.9]) by olra.theworths.org (Postfix) with ESMTP id 9DDCA429E4F for ; Wed, 15 Feb 2012 19:12:47 -0800 (PST) Received: from lb7f8hsrpno-svcs.dcs.int.inet (HELO pd6ml1no-ssvc.prod.shaw.ca) ([10.0.144.222]) by pd6mo1no-svcs.prod.shaw.ca with ESMTP; 15 Feb 2012 20:12:47 -0700 X-Cloudmark-SP-Filtered: true X-Cloudmark-SP-Result: v=1.1 cv=Cufqhujp69hR+MhiHu40yK/USb/hlYs0+irVU46A6+k= c=1 sm=1 a=Z5iDYLMGwAcA:10 a=BLceEmwcHowA:10 a=yQp6g8lIsgqumF79BAsFDg==:17 a=H4IEW4q-AAAA:8 a=7343-z1_AAAA:8 a=pGLkceISAAAA:8 a=sW8ZUVT9fjtYQyWcCR4A:9 a=dH1UOewsV4vhkg3hdfQA:7 a=0BPXsuqt4rsA:10 a=Kw4u8EAyA4wA:10 a=0c-eHkXYtrgA:10 a=q9ffXd82REXWyX97:21 a=OF_rVAV-Bfowtp03:21 a=HpAAvcLHHh0Zw7uRqdWCyQ==:117 Received: from unknown (HELO lagos.xvx.ca) ([96.52.216.56]) by pd6ml1no-dmz.prod.shaw.ca with ESMTP; 15 Feb 2012 20:12:46 -0700 Received: by lagos.xvx.ca (Postfix, from userid 1000) id 40B318000D51; Wed, 15 Feb 2012 20:12:46 -0700 (MST) From: Adam Wolfe Gordon To: notmuch@notmuchmail.org Subject: [PATCH v5.2 7/7] emacs: Use the new JSON reply format and message-cite-original Date: Wed, 15 Feb 2012 20:12:37 -0700 Message-Id: <1329361957-28493-8-git-send-email-awg+notmuch@xvx.ca> X-Mailer: git-send-email 1.7.5.4 In-Reply-To: <1329361957-28493-1-git-send-email-awg+notmuch@xvx.ca> References: <1329361957-28493-1-git-send-email-awg+notmuch@xvx.ca> X-BeenThere: notmuch@notmuchmail.org X-Mailman-Version: 2.1.13 Precedence: list List-Id: "Use and development of the notmuch mail system." List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Thu, 16 Feb 2012 03:12:58 -0000 Use the new JSON reply format to create replies in emacs. Quote HTML parts nicely by using mm-display-part to turn them into displayable text, then quoting them with message-cite-original. This is very useful for users who regularly receive HTML-only email. Use message-mode's message-cite-original function to create the quoted body for reply messages. In order to make this act like the existing notmuch defaults, you will need to set the following in your emacs configuration: message-citation-line-format "On %a, %d %b %Y, %f wrote:" message-citation-line-function 'message-insert-formatted-citation-line The tests have been updated to reflect the (ugly) emacs default. --- emacs/notmuch-lib.el | 6 ++ emacs/notmuch-mua.el | 127 +++++++++++++++++++++++++++++++++++--------------- test/emacs | 8 ++-- 3 files changed, 100 insertions(+), 41 deletions(-) diff --git a/emacs/notmuch-lib.el b/emacs/notmuch-lib.el index 7e3f110..3fc7aff 100644 --- a/emacs/notmuch-lib.el +++ b/emacs/notmuch-lib.el @@ -206,6 +206,12 @@ the user hasn't set this variable with the old or new value." (setq seq (nconc (delete elem seq) (list elem)))))) seq)) +(defun notmuch-parts-filter-by-type (parts type) + "Given a vector of message parts, return a vector containing the ones matching the given type." + (loop for part across parts + if (notmuch-match-content-type (cdr (assq 'content-type part)) type) + vconcat (list part))) + ;; Compatibility functions for versions of emacs before emacs 23. ;; ;; Both functions here were copied from emacs 23 with the following copyright: diff --git a/emacs/notmuch-mua.el b/emacs/notmuch-mua.el index 4be7c13..7d43821 100644 --- a/emacs/notmuch-mua.el +++ b/emacs/notmuch-mua.el @@ -19,11 +19,15 @@ ;; ;; Authors: David Edmondson +(require 'json) (require 'message) +(require 'format-spec) (require 'notmuch-lib) (require 'notmuch-address) +(eval-when-compile (require 'cl)) + ;; (defcustom notmuch-mua-send-hook '(notmuch-mua-message-send-hook) @@ -72,56 +76,105 @@ list." (push header message-hidden-headers))) notmuch-mua-hidden-headers)) +(defun notmuch-mua-get-displayed-part (part query-string) + (with-temp-buffer + (if (assq 'content part) + (insert (cdr (assq 'content part))) + (call-process notmuch-command nil t nil "show" "--format=raw" + (format "--part=%s" (cdr (assq 'id part))) + query-string)) + + (let ((handle (mm-make-handle (current-buffer) (list (cdr (assq 'content-type part))))) + (end-of-orig (point-max))) + (mm-display-part handle) + (delete-region (point-min) end-of-orig) + (buffer-substring (point-min) (point-max))))) + +(defun notmuch-mua-multipart/*-to-list (parts) + (loop for part across parts + collect (cdr (assq 'content-type part)))) + +(defun notmuch-mua-get-quotable-parts (parts) + (loop for part across parts + if (notmuch-match-content-type (cdr (assq 'content-type part)) "multipart/alternative") + append (let* ((subparts (cdr (assq 'content part))) + (types (notmuch-mua-multipart/*-to-list subparts)) + (chosen-type (car (notmuch-multipart/alternative-choose types)))) + (notmuch-mua-get-quotable-parts (notmuch-parts-filter-by-type subparts chosen-type))) + else if (notmuch-match-content-type (cdr (assq 'content-type part)) "multipart/*") + append (notmuch-mua-get-quotable-parts (cdr (assq 'content part))) + else if (notmuch-match-content-type (cdr (assq 'content-type part)) "text/*") + collect part)) + (defun notmuch-mua-reply (query-string &optional sender reply-all) - (let (headers - body - (args '("reply"))) - (if notmuch-show-process-crypto - (setq args (append args '("--decrypt")))) + (let ((args '("reply" "--format=json")) + reply + original) + (when notmuch-show-process-crypto + (setq args (append args '("--decrypt")))) + (if reply-all (setq args (append args '("--reply-to=all"))) (setq args (append args '("--reply-to=sender")))) (setq args (append args (list query-string))) - ;; This make assumptions about the output of `notmuch reply', but - ;; really only that the headers come first followed by a blank - ;; line and then the body. + + ;; Get the reply object as JSON, and parse it into an elisp object. (with-temp-buffer (apply 'call-process (append (list notmuch-command nil (list t t) nil) args)) (goto-char (point-min)) - (if (re-search-forward "^$" nil t) - (save-excursion - (save-restriction - (narrow-to-region (point-min) (point)) - (goto-char (point-min)) - (setq headers (mail-header-extract))))) - (forward-line 1) - (setq body (buffer-substring (point) (point-max)))) - ;; If sender is non-nil, set the From: header to its value. - (when sender - (mail-header-set 'from sender headers)) - (let - ;; Overlay the composition window on that being used to read - ;; the original message. - ((same-window-regexps '("\\*mail .*"))) - (notmuch-mua-mail (mail-header 'to headers) - (mail-header 'subject headers) - (message-headers-to-generate headers t '(to subject)))) - ;; insert the message body - but put it in front of the signature - ;; if one is present - (goto-char (point-max)) - (if (re-search-backward message-signature-separator nil t) + (setq reply (json-read))) + + ;; Extract the original message to simplify the following code. + (setq original (cdr (assq 'original reply))) + + ;; Extract the headers of both the reply and the original message. + (let* ((original-headers (cdr (assq 'headers original))) + (reply-headers (cdr (assq 'reply-headers reply)))) + + ;; If sender is non-nil, set the From: header to its value. + (when sender + (mail-header-set 'From sender reply-headers)) + (let + ;; Overlay the composition window on that being used to read + ;; the original message. + ((same-window-regexps '("\\*mail .*"))) + (notmuch-mua-mail (mail-header 'To reply-headers) + (mail-header 'Subject reply-headers) + (message-headers-to-generate reply-headers t '(To Subject)))) + ;; Insert the message body - but put it in front of the signature + ;; if one is present + (goto-char (point-max)) + (if (re-search-backward message-signature-separator nil t) (forward-line -1) - (goto-char (point-max))) - (insert body) - (push-mark)) - (set-buffer-modified-p nil) + (goto-char (point-max))) + + (let ((from (cdr (assq 'From original-headers))) + (date (cdr (assq 'Date original-headers))) + (start (point))) + + (insert "From: " from "\n") + (insert "Date: " date "\n\n") + + ;; Get the parts of the original message that should be quoted; this includes + ;; all the text parts, except the non-preferred ones in a multipart/alternative. + (let ((quotable-parts (notmuch-mua-get-quotable-parts (cdr (assq 'body original))))) + (mapc (lambda (part) + (insert (notmuch-mua-get-displayed-part part query-string))) + quotable-parts)) + + (push-mark) + (goto-char start) + ;; Quote the original message according to the user's configured style. + (message-cite-original)))) + (push-mark) (message-goto-body) ;; Original message may contain (malicious) MML tags. We must ;; properly quote them in the reply. Note that using `point-max' ;; instead of `mark' here is wrong. The buffer may include user's ;; signature which should not be MML-quoted. - (mml-quote-region (point) (mark))) + (mml-quote-region (point) (mark)) + (set-buffer-modified-p nil)) (defun notmuch-mua-forward-message () (message-forward) @@ -147,7 +200,7 @@ OTHER-ARGS are passed through to `message-mail'." (when (not (string= "" user-agent)) (push (cons "User-Agent" user-agent) other-headers)))) - (unless (mail-header 'from other-headers) + (unless (mail-header 'From other-headers) (push (cons "From" (concat (notmuch-user-name) " <" (notmuch-user-primary-email) ">")) other-headers)) @@ -210,7 +263,7 @@ the From: address first." (interactive "P") (let ((other-headers (when (or prompt-for-sender notmuch-always-prompt-for-sender) - (list (cons 'from (notmuch-mua-prompt-for-sender)))))) + (list (cons 'From (notmuch-mua-prompt-for-sender)))))) (notmuch-mua-mail nil nil other-headers))) (defun notmuch-mua-new-forward-message (&optional prompt-for-sender) diff --git a/test/emacs b/test/emacs index c3a75e9..a6786d4 100755 --- a/test/emacs +++ b/test/emacs @@ -268,13 +268,13 @@ Subject: Re: Testing message sent via SMTP In-Reply-To: Fcc: $(pwd)/mail/sent --text follows this line-- -On 01 Jan 2000 12:00:00 -0000, Notmuch Test Suite wrote: +Notmuch Test Suite writes: + > This is a test that messages are sent via SMTP EOF test_expect_equal_file OUTPUT EXPECTED test_begin_subtest "Reply within emacs to a multipart/mixed message" -test_subtest_known_broken test_emacs '(notmuch-show "id:20091118002059.067214ed@hikari") (notmuch-show-reply) (test-output)' @@ -334,7 +334,6 @@ EOF test_expect_equal_file OUTPUT EXPECTED test_begin_subtest "Reply within emacs to a multipart/alternative message" -test_subtest_known_broken test_emacs '(notmuch-show "id:cf0c4d610911171136h1713aa59w9cf9aa31f052ad0a@mail.gmail.com") (notmuch-show-reply) (test-output)' @@ -385,7 +384,8 @@ Subject: Re: Quote MML tags in reply In-Reply-To: Fcc: ${MAIL_DIR}/sent --text follows this line-- -On Fri, 05 Jan 2001 15:43:57 +0000, Notmuch Test Suite wrote: +Notmuch Test Suite writes: + > <#!part disposition=inline> EOF test_expect_equal_file OUTPUT EXPECTED -- 1.7.5.4