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.
This commit is contained in:
David Bremner 2016-11-13 14:08:50 +00:00
parent 90248f862b
commit 2dd96d6bd6
4 changed files with 95 additions and 0 deletions

View file

@ -27,6 +27,7 @@
(require 'notmuch-tag) (require 'notmuch-tag)
(declare-function notmuch-show-get-message-id "notmuch-show" (&optional bare)) (declare-function notmuch-show-get-message-id "notmuch-show" (&optional bare))
(declare-function notmuch-message-mode "notmuch-mua")
(defgroup notmuch-draft nil (defgroup notmuch-draft nil
"Saving and editing drafts in Notmuch." "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)) (goto-char (+ (match-beginning 0) 2))
(insert "!")))))) (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 () (defun notmuch-draft--has-encryption-tag ()
"Returns t if there is an mml secure tag." "Returns t if there is an mml secure tag."
(save-excursion (save-excursion
@ -197,6 +219,46 @@ applied to newly inserted messages)."
(notmuch-draft-save) (notmuch-draft-save)
(kill-buffer)) (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) (add-hook 'message-send-hook 'notmuch-draft--mark-deleted)

View file

@ -38,6 +38,7 @@
(require 'notmuch-mua) (require 'notmuch-mua)
(require 'notmuch-crypto) (require 'notmuch-crypto)
(require 'notmuch-print) (require 'notmuch-print)
(require 'notmuch-draft)
(declare-function notmuch-call-notmuch-process "notmuch" (&rest args)) (declare-function notmuch-call-notmuch-process "notmuch" (&rest args))
(declare-function notmuch-search-next-thread "notmuch" nil) (declare-function notmuch-search-next-thread "notmuch" nil)
@ -50,6 +51,7 @@
(&optional query query-context target buffer-name open-target)) (&optional query query-context target buffer-name open-target))
(declare-function notmuch-tree-get-message-properties "notmuch-tree" nil) (declare-function notmuch-tree-get-message-properties "notmuch-tree" nil)
(declare-function notmuch-read-query "notmuch" (prompt)) (declare-function notmuch-read-query "notmuch" (prompt))
(declare-function notmuch-draft-resume "notmuch-draft" (id))
(defcustom notmuch-message-headers '("Subject" "To" "Cc" "Date") (defcustom notmuch-message-headers '("Subject" "To" "Cc" "Date")
"Headers that should be shown in a message, in this order. "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 "|" 'notmuch-show-pipe-message)
(define-key map "w" 'notmuch-show-save-attachments) (define-key map "w" 'notmuch-show-save-attachments)
(define-key map "V" 'notmuch-show-view-raw-message) (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 "c" 'notmuch-show-stash-map)
(define-key map "h" 'notmuch-show-toggle-visibility-headers) (define-key map "h" 'notmuch-show-toggle-visibility-headers)
(define-key map "k" 'notmuch-tag-jump) (define-key map "k" 'notmuch-tag-jump)
@ -1982,6 +1985,11 @@ to show, nil otherwise."
(setq buffer-read-only t) (setq buffer-read-only t)
(view-buffer buf 'kill-buffer-if-not-modified))) (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 (put 'notmuch-show-pipe-message 'notmuch-doc
"Pipe the contents of the current message to a command.") "Pipe the contents of the current message to a command.")
(put 'notmuch-show-pipe-message 'notmuch-prefix-doc (put 'notmuch-show-pipe-message 'notmuch-prefix-doc

View file

@ -293,6 +293,7 @@ FUNC."
(define-key map "*" 'notmuch-tree-tag-thread) (define-key map "*" 'notmuch-tree-tag-thread)
(define-key map " " 'notmuch-tree-scroll-or-next) (define-key map " " 'notmuch-tree-scroll-or-next)
(define-key map (kbd "DEL") 'notmuch-tree-scroll-message-window-back) (define-key map (kbd "DEL") 'notmuch-tree-scroll-message-window-back)
(define-key map "e" 'notmuch-tree-resume-message)
map)) map))
(fset 'notmuch-tree-mode-map notmuch-tree-mode-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" "-"))) (list (notmuch-read-tag-changes (notmuch-tree-get-tags) "Tag message" "-")))
(notmuch-tree-tag tag-changes)) (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 ;; The next two functions close the message window before calling
;; notmuch-search or notmuch-tree but they do so after the user has ;; 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 ;; entered the query (in case the user was basing the query on

View file

@ -52,4 +52,19 @@ count2=$(notmuch count subject:draft-test-0004)
test_expect_equal "$count1,$count2" "3,0" 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 <<EOF | notmuch_dir_sanitize >EXPECTED
From: Notmuch Test Suite <test_suite@notmuchmail.org>
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 test_done