mirror of
https://git.notmuchmail.org/git/notmuch
synced 2024-11-28 13:44:12 +01:00
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:
parent
90248f862b
commit
2dd96d6bd6
4 changed files with 95 additions and 0 deletions
|
@ -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)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue