From 2dd96d6bd642c3b32ce1997528f69665019b7497 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Sun, 13 Nov 2016 14:08:50 +0000 Subject: [PATCH] emacs: resume messages Provide functionality to resume editing a message previously saved with notmuch-draft-save, including decoding the X-Notmuch-Emacs-Secure header. Resume gets the raw file from notmuch and using the emacs function mime-to-mml reconstructs the message (including attachments). 'e' is bound to resume a draft from show or tree mode. --- emacs/notmuch-draft.el | 62 ++++++++++++++++++++++++++++++++++++++++ emacs/notmuch-show.el | 8 ++++++ emacs/notmuch-tree.el | 10 +++++++ test/T630-emacs-draft.sh | 15 ++++++++++ 4 files changed, 95 insertions(+) diff --git a/emacs/notmuch-draft.el b/emacs/notmuch-draft.el index f1da9c33..fb7f4f55 100644 --- a/emacs/notmuch-draft.el +++ b/emacs/notmuch-draft.el @@ -27,6 +27,7 @@ (require 'notmuch-tag) (declare-function notmuch-show-get-message-id "notmuch-show" (&optional bare)) +(declare-function notmuch-message-mode "notmuch-mua") (defgroup notmuch-draft nil "Saving and editing drafts in Notmuch." @@ -118,6 +119,27 @@ Used when a new version is saved, or the message is sent." (goto-char (+ (match-beginning 0) 2)) (insert "!")))))) +(defun notmuch-draft-unquote-some-mml () + "Unquote the mml tags in `notmuch-draft-quoted-tags`." + (save-excursion + (when notmuch-draft-quoted-tags + (let ((re (concat "<#!+/?\\(" + (mapconcat 'regexp-quote notmuch-draft-quoted-tags "\\|") + "\\)"))) + (message-goto-body) + (while (re-search-forward re nil t) + ;; Remove one ! from after the #. + (goto-char (+ (match-beginning 0) 2)) + (delete-char 1)))) + (let (secure-tag) + (save-restriction + (message-narrow-to-headers) + (setq secure-tag (message-fetch-field "X-Notmuch-Emacs-Secure" 't)) + (message-remove-header "X-Notmuch-Emacs-Secure")) + (message-goto-body) + (when secure-tag + (insert secure-tag "\n"))))) + (defun notmuch-draft--has-encryption-tag () "Returns t if there is an mml secure tag." (save-excursion @@ -197,6 +219,46 @@ applied to newly inserted messages)." (notmuch-draft-save) (kill-buffer)) +(defun notmuch-draft-resume (id) + "Resume editing of message with id ID." + (let* ((tags (process-lines notmuch-command "search" "--output=tags" + "--exclude=false" id)) + (draft (equal tags (notmuch-update-tags tags notmuch-draft-tags)))) + (when (or draft + (yes-or-no-p "Message does not appear to be a draft: really resume? ")) + (switch-to-buffer (get-buffer-create (concat "*notmuch-draft-" id "*"))) + (setq buffer-read-only nil) + (erase-buffer) + (let ((coding-system-for-read 'no-conversion)) + (call-process notmuch-command nil t nil "show" "--format=raw" id)) + (mime-to-mml) + (goto-char (point-min)) + (when (re-search-forward "^$" nil t) + (replace-match mail-header-separator t t)) + ;; Remove the Date and Message-ID headers (unless the user has + ;; explicitly customized emacs to tell us not to) as they will + ;; be replaced when the message is sent. + (save-restriction + (message-narrow-to-headers) + (when (member 'Message-ID message-deletable-headers) + (message-remove-header "Message-ID")) + (when (member 'Date message-deletable-headers) + (message-remove-header "Date")) + ;; The X-Notmuch-Emacs-Draft header is a more reliable + ;; indication of whether the message really is a draft. + (setq draft (> (message-remove-header "X-Notmuch-Emacs-Draft") 0))) + ;; If the message is not a draft we should not unquote any mml. + (when draft + (notmuch-draft-unquote-some-mml)) + (notmuch-message-mode) + (message-goto-body) + (set-buffer-modified-p nil) + ;; If the resumed message was a draft then set the draft + ;; message-id so that we can delete the current saved draft if the + ;; message is resaved or sent. + (setq notmuch-draft-id (when draft id))))) + + (add-hook 'message-send-hook 'notmuch-draft--mark-deleted) diff --git a/emacs/notmuch-show.el b/emacs/notmuch-show.el index fcf7e6ee..364004b8 100644 --- a/emacs/notmuch-show.el +++ b/emacs/notmuch-show.el @@ -38,6 +38,7 @@ (require 'notmuch-mua) (require 'notmuch-crypto) (require 'notmuch-print) +(require 'notmuch-draft) (declare-function notmuch-call-notmuch-process "notmuch" (&rest args)) (declare-function notmuch-search-next-thread "notmuch" nil) @@ -50,6 +51,7 @@ (&optional query query-context target buffer-name open-target)) (declare-function notmuch-tree-get-message-properties "notmuch-tree" nil) (declare-function notmuch-read-query "notmuch" (prompt)) +(declare-function notmuch-draft-resume "notmuch-draft" (id)) (defcustom notmuch-message-headers '("Subject" "To" "Cc" "Date") "Headers that should be shown in a message, in this order. @@ -1445,6 +1447,7 @@ reset based on the original query." (define-key map "|" 'notmuch-show-pipe-message) (define-key map "w" 'notmuch-show-save-attachments) (define-key map "V" 'notmuch-show-view-raw-message) + (define-key map "e" 'notmuch-show-resume-message) (define-key map "c" 'notmuch-show-stash-map) (define-key map "h" 'notmuch-show-toggle-visibility-headers) (define-key map "k" 'notmuch-tag-jump) @@ -1982,6 +1985,11 @@ to show, nil otherwise." (setq buffer-read-only t) (view-buffer buf 'kill-buffer-if-not-modified))) +(defun notmuch-show-resume-message () + "Resume EDITING the current draft message." + (interactive) + (notmuch-draft-resume (notmuch-show-get-message-id))) + (put 'notmuch-show-pipe-message 'notmuch-doc "Pipe the contents of the current message to a command.") (put 'notmuch-show-pipe-message 'notmuch-prefix-doc diff --git a/emacs/notmuch-tree.el b/emacs/notmuch-tree.el index 8398eb10..7bebdbab 100644 --- a/emacs/notmuch-tree.el +++ b/emacs/notmuch-tree.el @@ -293,6 +293,7 @@ FUNC." (define-key map "*" 'notmuch-tree-tag-thread) (define-key map " " 'notmuch-tree-scroll-or-next) (define-key map (kbd "DEL") 'notmuch-tree-scroll-message-window-back) + (define-key map "e" 'notmuch-tree-resume-message) map)) (fset 'notmuch-tree-mode-map notmuch-tree-mode-map) @@ -405,6 +406,15 @@ NOT change the database." (list (notmuch-read-tag-changes (notmuch-tree-get-tags) "Tag message" "-"))) (notmuch-tree-tag tag-changes)) +(defun notmuch-tree-resume-message () + "Resume EDITING the current draft message." + (interactive) + (notmuch-tree-close-message-window) + (let ((id (notmuch-tree-get-message-id))) + (if id + (notmuch-draft-resume id) + (message "No message to resume!")))) + ;; The next two functions close the message window before calling ;; notmuch-search or notmuch-tree but they do so after the user has ;; entered the query (in case the user was basing the query on diff --git a/test/T630-emacs-draft.sh b/test/T630-emacs-draft.sh index 689ccfb8..46fc356f 100755 --- a/test/T630-emacs-draft.sh +++ b/test/T630-emacs-draft.sh @@ -52,4 +52,19 @@ count2=$(notmuch count subject:draft-test-0004) test_expect_equal "$count1,$count2" "3,0" +test_begin_subtest "Resuming a signed draft" + +test_emacs '(notmuch-show "subject:draft-test-0003") + (notmuch-show-resume-message) + (test-output)' +notmuch_dir_sanitize OUTPUT > OUTPUT.clean +cat <EXPECTED +From: Notmuch Test Suite +To: +Subject: draft-test-0003 +Fcc: MAIL_DIR/sent +--text follows this line-- +<#secure method=pgpmime mode=sign> +EOF +test_expect_equal_file EXPECTED OUTPUT.clean test_done