mirror of
https://git.notmuchmail.org/git/notmuch
synced 2024-11-22 02:48:08 +01:00
emacs: postpone a message
This provides initial support for postponing in the emacs frontend; resuming will follow in a later commit. On saving/postponing it uses notmuch insert to put the message in the notmuch database Current bindings are C-x C-s to save a draft, C-c C-p to postpone a draft (save and exit compose buffer). Previous drafts get tagged deleted on subsequent saves, or on the message being sent. Each draft gets its own message-id, and we use the namespace draft-.... for draft message ids (so, at least for most people, drafts are easily distinguisable).
This commit is contained in:
parent
f8bdba37d3
commit
d820b97477
4 changed files with 215 additions and 1 deletions
|
@ -20,7 +20,8 @@ emacs_sources := \
|
||||||
$(dir)/notmuch-print.el \
|
$(dir)/notmuch-print.el \
|
||||||
$(dir)/notmuch-version.el \
|
$(dir)/notmuch-version.el \
|
||||||
$(dir)/notmuch-jump.el \
|
$(dir)/notmuch-jump.el \
|
||||||
$(dir)/notmuch-company.el
|
$(dir)/notmuch-company.el \
|
||||||
|
$(dir)/notmuch-draft.el
|
||||||
|
|
||||||
$(dir)/notmuch-version.el: $(dir)/Makefile.local version.stamp
|
$(dir)/notmuch-version.el: $(dir)/Makefile.local version.stamp
|
||||||
$(dir)/notmuch-version.el: $(srcdir)/$(dir)/notmuch-version.el.tmpl
|
$(dir)/notmuch-version.el: $(srcdir)/$(dir)/notmuch-version.el.tmpl
|
||||||
|
|
167
emacs/notmuch-draft.el
Normal file
167
emacs/notmuch-draft.el
Normal file
|
@ -0,0 +1,167 @@
|
||||||
|
;;; notmuch-draft.el --- functions for postponing and editing drafts
|
||||||
|
;;
|
||||||
|
;; Copyright © Mark Walters
|
||||||
|
;; Copyright © David Bremner
|
||||||
|
;;
|
||||||
|
;; This file is part of Notmuch.
|
||||||
|
;;
|
||||||
|
;; Notmuch is free software: you can redistribute it and/or modify it
|
||||||
|
;; under the terms of the GNU General Public License as published by
|
||||||
|
;; the Free Software Foundation, either version 3 of the License, or
|
||||||
|
;; (at your option) any later version.
|
||||||
|
;;
|
||||||
|
;; Notmuch is distributed in the hope that it will be useful, but
|
||||||
|
;; WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
;; General Public License for more details.
|
||||||
|
;;
|
||||||
|
;; You should have received a copy of the GNU General Public License
|
||||||
|
;; along with Notmuch. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
;;
|
||||||
|
;; Authors: Mark Walters <markwalters1009@gmail.com>
|
||||||
|
;; David Bremner <david@tethera.net>
|
||||||
|
|
||||||
|
;;; Code:
|
||||||
|
|
||||||
|
(require 'notmuch-maildir-fcc)
|
||||||
|
(require 'notmuch-tag)
|
||||||
|
|
||||||
|
(declare-function notmuch-show-get-message-id "notmuch-show" (&optional bare))
|
||||||
|
|
||||||
|
(defgroup notmuch-draft nil
|
||||||
|
"Saving and editing drafts in Notmuch."
|
||||||
|
:group 'notmuch)
|
||||||
|
|
||||||
|
(defcustom notmuch-draft-tags '("+draft")
|
||||||
|
"List of tags changes to apply to a draft message when it is saved in the database.
|
||||||
|
|
||||||
|
Tags starting with \"+\" (or not starting with either \"+\" or
|
||||||
|
\"-\") in the list will be added, and tags starting with \"-\"
|
||||||
|
will be removed from the message being stored.
|
||||||
|
|
||||||
|
For example, if you wanted to give the message a \"draft\" tag
|
||||||
|
but not the (normally added by default) \"inbox\" tag, you would
|
||||||
|
set:
|
||||||
|
(\"+draft\" \"-inbox\")"
|
||||||
|
:type '(repeat string)
|
||||||
|
:group 'notmuch-draft)
|
||||||
|
|
||||||
|
(defcustom notmuch-draft-folder "drafts"
|
||||||
|
"Folder to save draft messages in.
|
||||||
|
|
||||||
|
This should be specified relative to the root of the notmuch
|
||||||
|
database. It will be created if necessary."
|
||||||
|
:type 'string
|
||||||
|
:group 'notmuch-draft)
|
||||||
|
|
||||||
|
(defcustom notmuch-draft-quoted-tags '()
|
||||||
|
"Mml tags to quote.
|
||||||
|
|
||||||
|
This should be a list of mml tags to quote before saving. You do
|
||||||
|
not need to include \"secure\" as that is handled separately.
|
||||||
|
|
||||||
|
If you include \"part\" then attachments will not be saved with
|
||||||
|
the draft -- if not then they will be saved with the draft. The
|
||||||
|
former means the attachments may not still exist when you resume
|
||||||
|
the message, the latter means that the attachments as they were
|
||||||
|
when you postponed will be sent with the resumed message.
|
||||||
|
|
||||||
|
Note you may get strange results if you change this between
|
||||||
|
postponing and resuming a message."
|
||||||
|
:type '(repeat string)
|
||||||
|
:group 'notmuch-send)
|
||||||
|
|
||||||
|
(defvar notmuch-draft-id nil
|
||||||
|
"Message-id of the most recent saved draft of this message")
|
||||||
|
(make-variable-buffer-local 'notmuch-draft-id)
|
||||||
|
|
||||||
|
(defun notmuch-draft--mark-deleted ()
|
||||||
|
"Tag the last saved draft deleted.
|
||||||
|
|
||||||
|
Used when a new version is saved, or the message is sent."
|
||||||
|
(when notmuch-draft-id
|
||||||
|
(notmuch-tag notmuch-draft-id '("+deleted"))))
|
||||||
|
|
||||||
|
(defun notmuch-draft-quote-some-mml ()
|
||||||
|
"Quote the mml tags in `notmuch-draft-quoted-tags`."
|
||||||
|
(save-excursion
|
||||||
|
;; First we deal with any secure tag separately.
|
||||||
|
(message-goto-body)
|
||||||
|
(when (looking-at "<#secure[^\n]*>\n")
|
||||||
|
(let ((secure-tag (match-string 0)))
|
||||||
|
(delete-region (match-beginning 0) (match-end 0))
|
||||||
|
(message-add-header (concat "X-Notmuch-Emacs-Secure: " secure-tag))))
|
||||||
|
;; This is copied from mml-quote-region but only quotes the
|
||||||
|
;; specified tags.
|
||||||
|
(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)
|
||||||
|
;; Insert ! after the #.
|
||||||
|
(goto-char (+ (match-beginning 0) 2))
|
||||||
|
(insert "!"))))))
|
||||||
|
|
||||||
|
(defun notmuch-draft--make-message-id ()
|
||||||
|
;; message-make-message-id gives the id inside a "<" ">" pair,
|
||||||
|
;; but notmuch doesn't want that form, so remove them.
|
||||||
|
(concat "draft-" (substring (message-make-message-id) 1 -1)))
|
||||||
|
|
||||||
|
(defun notmuch-draft-save ()
|
||||||
|
"Save the current draft message in the notmuch database.
|
||||||
|
|
||||||
|
This saves the current message in the database with tags
|
||||||
|
`notmuch-draft-tags` (in addition to any default tags
|
||||||
|
applied to newly inserted messages)."
|
||||||
|
(interactive)
|
||||||
|
(let ((id (notmuch-draft--make-message-id)))
|
||||||
|
(with-temporary-notmuch-message-buffer
|
||||||
|
;; We insert a Date header and a Message-ID header, the former
|
||||||
|
;; so that it is easier to search for the message, and the
|
||||||
|
;; latter so we have a way of accessing the saved message (for
|
||||||
|
;; example to delete it at a later time). We check that the
|
||||||
|
;; user has these in `message-deletable-headers` (the default)
|
||||||
|
;; as otherwise they are doing something strange and we
|
||||||
|
;; shouldn't interfere. Note, since we are doing this in a new
|
||||||
|
;; buffer we don't change the version in the compose buffer.
|
||||||
|
(cond
|
||||||
|
((member 'Message-ID message-deletable-headers)
|
||||||
|
(message-remove-header "Message-ID")
|
||||||
|
(message-add-header (concat "Message-ID: <" id ">")))
|
||||||
|
(t
|
||||||
|
(message "You have customized emacs so Message-ID is not a deletable header, so not changing it")
|
||||||
|
(setq id nil)))
|
||||||
|
(cond
|
||||||
|
((member 'Date message-deletable-headers)
|
||||||
|
(message-remove-header "Date")
|
||||||
|
(message-add-header (concat "Date: " (message-make-date))))
|
||||||
|
(t
|
||||||
|
(message "You have customized emacs so Date is not a deletable header, so not changing it")))
|
||||||
|
(message-add-header "X-Notmuch-Emacs-Draft: True")
|
||||||
|
(notmuch-draft-quote-some-mml)
|
||||||
|
(notmuch-maildir-setup-message-for-saving)
|
||||||
|
(notmuch-maildir-notmuch-insert-current-buffer
|
||||||
|
notmuch-draft-folder 't notmuch-draft-tags))
|
||||||
|
;; We are now back in the original compose buffer. Note the
|
||||||
|
;; function notmuch-call-notmuch-process (called by
|
||||||
|
;; notmuch-maildir-notmuch-insert-current-buffer) signals an error
|
||||||
|
;; on failure, so to get to this point it must have
|
||||||
|
;; succeeded. Also, notmuch-draft-id is still the id of the
|
||||||
|
;; previous draft, so it is safe to mark it deleted.
|
||||||
|
(notmuch-draft--mark-deleted)
|
||||||
|
(setq notmuch-draft-id (concat "id:" id))
|
||||||
|
(set-buffer-modified-p nil)))
|
||||||
|
|
||||||
|
(defun notmuch-draft-postpone ()
|
||||||
|
"Save the draft message in the notmuch database and exit buffer."
|
||||||
|
(interactive)
|
||||||
|
(notmuch-draft-save)
|
||||||
|
(kill-buffer))
|
||||||
|
|
||||||
|
(add-hook 'message-send-hook 'notmuch-draft--mark-deleted)
|
||||||
|
|
||||||
|
|
||||||
|
(provide 'notmuch-draft)
|
||||||
|
|
||||||
|
;;; notmuch-draft.el ends here
|
|
@ -33,6 +33,8 @@
|
||||||
(declare-function notmuch-show-insert-body "notmuch-show" (msg body depth))
|
(declare-function notmuch-show-insert-body "notmuch-show" (msg body depth))
|
||||||
(declare-function notmuch-fcc-header-setup "notmuch-maildir-fcc" ())
|
(declare-function notmuch-fcc-header-setup "notmuch-maildir-fcc" ())
|
||||||
(declare-function notmuch-maildir-message-do-fcc "notmuch-maildir-fcc" ())
|
(declare-function notmuch-maildir-message-do-fcc "notmuch-maildir-fcc" ())
|
||||||
|
(declare-function notmuch-draft-postpone "notmuch-draft" ())
|
||||||
|
(declare-function notmuch-draft-save "notmuch-draft" ())
|
||||||
|
|
||||||
;;
|
;;
|
||||||
|
|
||||||
|
@ -289,6 +291,8 @@ mutiple parts get a header."
|
||||||
|
|
||||||
(define-key notmuch-message-mode-map (kbd "C-c C-c") #'notmuch-mua-send-and-exit)
|
(define-key notmuch-message-mode-map (kbd "C-c C-c") #'notmuch-mua-send-and-exit)
|
||||||
(define-key notmuch-message-mode-map (kbd "C-c C-s") #'notmuch-mua-send)
|
(define-key notmuch-message-mode-map (kbd "C-c C-s") #'notmuch-mua-send)
|
||||||
|
(define-key notmuch-message-mode-map (kbd "C-c C-p") #'notmuch-draft-postpone)
|
||||||
|
(define-key notmuch-message-mode-map (kbd "C-x C-s") #'notmuch-draft-save)
|
||||||
|
|
||||||
(defun notmuch-mua-pop-to-buffer (name switch-function)
|
(defun notmuch-mua-pop-to-buffer (name switch-function)
|
||||||
"Pop to buffer NAME, and warn if it already exists and is
|
"Pop to buffer NAME, and warn if it already exists and is
|
||||||
|
|
42
test/T630-emacs-draft.sh
Executable file
42
test/T630-emacs-draft.sh
Executable file
|
@ -0,0 +1,42 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
test_description="Emacs Draft Handling"
|
||||||
|
. ./test-lib.sh || exit 1
|
||||||
|
|
||||||
|
add_email_corpus
|
||||||
|
|
||||||
|
notmuch config set search.exclude_tags deleted
|
||||||
|
|
||||||
|
test_begin_subtest "Saving a draft indexes it"
|
||||||
|
test_emacs '(notmuch-mua-mail)
|
||||||
|
(message-goto-subject)
|
||||||
|
(insert "draft-test-0001")
|
||||||
|
(notmuch-draft-save)
|
||||||
|
(test-output)'
|
||||||
|
count1=$(notmuch count tag:draft)
|
||||||
|
count2=$(notmuch count subject:draft-test-0001)
|
||||||
|
test_expect_equal "$count1=$count2" "1=1"
|
||||||
|
|
||||||
|
test_begin_subtest "Saving a draft tags previous draft as deleted"
|
||||||
|
test_emacs '(notmuch-mua-mail)
|
||||||
|
(message-goto-subject)
|
||||||
|
(insert "draft-test-0002")
|
||||||
|
(notmuch-draft-save)
|
||||||
|
(notmuch-draft-save)
|
||||||
|
(test-output)'
|
||||||
|
count1=$(notmuch count tag:draft)
|
||||||
|
count2=$(notmuch count subject:draft-test-0002)
|
||||||
|
|
||||||
|
test_expect_equal "$count1,$count2" "2,1"
|
||||||
|
|
||||||
|
test_begin_subtest "Saving a signed draft adds header"
|
||||||
|
test_emacs '(notmuch-mua-mail)
|
||||||
|
(message-goto-subject)
|
||||||
|
(insert "draft-test-0003")
|
||||||
|
(mml-secure-message-sign)
|
||||||
|
(notmuch-draft-save)
|
||||||
|
(test-output)'
|
||||||
|
header_count=$(notmuch show --format=raw subject:draft-test-0003 | grep -c ^X-Notmuch-Emacs-Secure)
|
||||||
|
body_count=$(notmuch notmuch show --format=raw subject:draft-test-0003 | grep -c '^\<#secure')
|
||||||
|
test_expect_equal "$header_count,$body_count" "1,0"
|
||||||
|
|
||||||
|
test_done
|
Loading…
Reference in a new issue