From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from localhost (localhost [127.0.0.1]) by arlo.cworth.org (Postfix) with ESMTP id ACE4F6DE0AAB for ; Sun, 13 Nov 2016 06:08:59 -0800 (PST) X-Virus-Scanned: Debian amavisd-new at cworth.org X-Spam-Flag: NO X-Spam-Score: 0.059 X-Spam-Level: X-Spam-Status: No, score=0.059 tagged_above=-999 required=5 tests=[AWL=-0.071, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, FREEMAIL_ENVFROM_END_DIGIT=0.25, FREEMAIL_FROM=0.001, RCVD_IN_DNSWL_NONE=-0.0001, RCVD_IN_MSPIKE_H3=-0.01, RCVD_IN_MSPIKE_WL=-0.01, SPF_PASS=-0.001] autolearn=disabled Received: from arlo.cworth.org ([127.0.0.1]) by localhost (arlo.cworth.org [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id Fjf4NpeKHtbC for ; Sun, 13 Nov 2016 06:08:57 -0800 (PST) Received: from mail-wm0-f66.google.com (mail-wm0-f66.google.com [74.125.82.66]) by arlo.cworth.org (Postfix) with ESMTPS id 2E8FE6DE0008 for ; Sun, 13 Nov 2016 06:08:57 -0800 (PST) Received: by mail-wm0-f66.google.com with SMTP id g23so8471421wme.1 for ; Sun, 13 Nov 2016 06:08:57 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113; h=from:to:cc:subject:date:message-id:in-reply-to:references :mime-version:content-transfer-encoding; bh=q1WH9TNeXYTJ5+v3+iMFcrL91VkG0qUik6clQc6LAvM=; b=0gjwXUpEd96/+oR3FzZrletCn9bhHap9nbEn4jBXDM+aFowVOIOUHpTea3h/K7gWAH ey3ZHhZU2qwzWHzb8tHE/RPIoo219H8ujuE31Yr/1C1thc2HEdwV2kV624UgKGQ6kiBK C81rGypx6H1QXZHmyr3jKPyjCMViOUNQqJ7PaosWK2P0zUor2YWFcwla5BvtXGuNCZ8b XNRjwaF1iyyJEgv3QpSaLvpbxoELifaq3SBYfS4GVLxAHWDMgW8TNb8oJGdIbdnsWgS6 m3hiqPXhjnl9ah1m1rL8lhLt1JYMNfP112wFsykVn1+Na2iXtnc/DY+OheAKbFYw1rS8 Wg+A== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20130820; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=q1WH9TNeXYTJ5+v3+iMFcrL91VkG0qUik6clQc6LAvM=; b=fu+l5jjFo/FuTxXi5qfWu0G0zPdxJGRP3siwPIE1OYu3toc2ToaTnPfHYO9gUE8/Ut feGFBFD91ZjDeyRbXIRSLS3n3tfAWID6lByx2OEICn99zcnUk9Z/i+89IUcrMZSeKpC3 UrxqoJnyPoLRZQWphZCLwGQf4QNuliW/fE5inw2J7ADHcH4aqNb14abM8Dae+ftBUSIp e5KCKycqee/4ws1pSwnUDxATBdgkqPTwr6oL8zynhZq5Xr2sF3rLsgZEcqkHiCWwX8ab U3XgAtgPAP3vyyDM2TC5r+kGuiJhWUwvP33Lxi+DIgeQZGSBE7itjDoF8SYs6KVLy5se 3LLA== X-Gm-Message-State: ABUngvcPb/pl2yR/IiqnMYuXrzc15dKXxckM0k70rK/DXCwR9X3sgDkPYs71xDw4Rc5NyQ== X-Received: by 10.28.141.18 with SMTP id p18mr6065056wmd.31.1479046135511; Sun, 13 Nov 2016 06:08:55 -0800 (PST) Received: from localhost (5751dfa2.skybroadband.com. [87.81.223.162]) by smtp.gmail.com with ESMTPSA id a13sm19719510wma.18.2016.11.13.06.08.54 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Sun, 13 Nov 2016 06:08:55 -0800 (PST) From: Mark Walters To: notmuch@notmuchmail.org Subject: [PATCH v7 2/4] emacs: postpone a message Date: Sun, 13 Nov 2016 14:08:48 +0000 Message-Id: <1479046130-2716-3-git-send-email-markwalters1009@gmail.com> X-Mailer: git-send-email 2.1.4 In-Reply-To: <1479046130-2716-1-git-send-email-markwalters1009@gmail.com> References: <1479046130-2716-1-git-send-email-markwalters1009@gmail.com> MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit X-BeenThere: notmuch@notmuchmail.org X-Mailman-Version: 2.1.22 Precedence: list List-Id: "Use and development of the notmuch mail system." List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Sun, 13 Nov 2016 14:08:59 -0000 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 . +;; +;; Authors: Mark Walters +;; David Bremner + +;;; 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