From: Mark Walters <markwalters1009@gmail.com>
To: notmuch@notmuchmail.org
Subject: [PATCH v6 2/4] emacs: postpone a message
Date: Sun, 13 Nov 2016 11:21:44 +0000 [thread overview]
Message-ID: <1479036106-32453-3-git-send-email-markwalters1009@gmail.com> (raw)
In-Reply-To: <1479036106-32453-1-git-send-email-markwalters1009@gmail.com>
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).
---
emacs/Makefile.local | 3 +-
emacs/notmuch-draft.el | 167 +++++++++++++++++++++++++++++++++++++++++++++++
emacs/notmuch-mua.el | 4 ++
test/T630-emacs-draft.sh | 42 ++++++++++++
4 files changed, 215 insertions(+), 1 deletion(-)
create mode 100644 emacs/notmuch-draft.el
create mode 100755 test/T630-emacs-draft.sh
diff --git a/emacs/Makefile.local b/emacs/Makefile.local
index 2d6aedb..6896ff9 100644
--- a/emacs/Makefile.local
+++ b/emacs/Makefile.local
@@ -20,7 +20,8 @@ emacs_sources := \
$(dir)/notmuch-print.el \
$(dir)/notmuch-version.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: $(srcdir)/$(dir)/notmuch-version.el.tmpl
diff --git a/emacs/notmuch-draft.el b/emacs/notmuch-draft.el
new file mode 100644
index 0000000..b8a5e67
--- /dev/null
+++ b/emacs/notmuch-draft.el
@@ -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
diff --git a/emacs/notmuch-mua.el b/emacs/notmuch-mua.el
index f333655..b68cdf2 100644
--- a/emacs/notmuch-mua.el
+++ b/emacs/notmuch-mua.el
@@ -33,6 +33,8 @@
(declare-function notmuch-show-insert-body "notmuch-show" (msg body depth))
(declare-function notmuch-fcc-header-setup "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-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)
"Pop to buffer NAME, and warn if it already exists and is
diff --git a/test/T630-emacs-draft.sh b/test/T630-emacs-draft.sh
new file mode 100755
index 0000000..e39690c
--- /dev/null
+++ b/test/T630-emacs-draft.sh
@@ -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
--
2.1.4
next prev parent reply other threads:[~2016-11-13 11:21 UTC|newest]
Thread overview: 6+ messages / expand[flat|nested] mbox.gz Atom feed top
2016-11-13 11:21 [PATCH v6 0/4] emacs postpone/resume patches Mark Walters
2016-11-13 11:21 ` [PATCH v6 1/4] emacs: tree: remove binding for pressing button in message pane Mark Walters
2016-11-13 11:21 ` Mark Walters [this message]
2016-11-13 11:21 ` [PATCH v6 3/4] emacs: check drafts for encryption tags before saving Mark Walters
2016-11-13 11:21 ` [PATCH v6 4/4] emacs: resume messages Mark Walters
2016-11-13 13:19 ` [PATCH v6 0/4] emacs postpone/resume patches David Bremner
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
List information: https://notmuchmail.org/
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=1479036106-32453-3-git-send-email-markwalters1009@gmail.com \
--to=markwalters1009@gmail.com \
--cc=notmuch@notmuchmail.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
Code repositories for project(s) associated with this public inbox
https://yhetil.org/notmuch.git/
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).