[PATCH v2 3/3] emacs: postpone/resume support
authorMark Walters <markwalters1009@gmail.com>
Fri, 3 Jun 2016 17:49:55 +0000 (18:49 +0100)
committerW. Trevor King <wking@tremily.us>
Sat, 20 Aug 2016 23:21:57 +0000 (16:21 -0700)
27/f3aa1f4cf8126aa3da8f65b8e6a3a78a0c8db3 [new file with mode: 0644]

diff --git a/27/f3aa1f4cf8126aa3da8f65b8e6a3a78a0c8db3 b/27/f3aa1f4cf8126aa3da8f65b8e6a3a78a0c8db3
new file mode 100644 (file)
index 0000000..47339c3
--- /dev/null
@@ -0,0 +1,333 @@
+Return-Path: <markwalters1009@gmail.com>\r
+X-Original-To: notmuch@notmuchmail.org\r
+Delivered-To: notmuch@notmuchmail.org\r
+Received: from localhost (localhost [127.0.0.1])\r
+ by arlo.cworth.org (Postfix) with ESMTP id 13F616DE02B5\r
+ for <notmuch@notmuchmail.org>; Fri,  3 Jun 2016 10:51:02 -0700 (PDT)\r
+X-Virus-Scanned: Debian amavisd-new at cworth.org\r
+X-Spam-Flag: NO\r
+X-Spam-Score: -0.326\r
+X-Spam-Level: \r
+X-Spam-Status: No, score=-0.326 tagged_above=-999 required=5 tests=[AWL=0.244,\r
+  DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1,\r
+ FREEMAIL_ENVFROM_END_DIGIT=0.25, FREEMAIL_FROM=0.001, RCVD_IN_DNSWL_LOW=-0.7,\r
+ RCVD_IN_MSPIKE_H3=-0.01, RCVD_IN_MSPIKE_WL=-0.01, SPF_PASS=-0.001]\r
+ autolearn=disabled\r
+Received: from arlo.cworth.org ([127.0.0.1])\r
+ by localhost (arlo.cworth.org [127.0.0.1]) (amavisd-new, port 10024)\r
+ with ESMTP id M6XvkTQGq4qs for <notmuch@notmuchmail.org>;\r
+ Fri,  3 Jun 2016 10:50:54 -0700 (PDT)\r
+Received: from mail-wm0-f68.google.com (mail-wm0-f68.google.com\r
+ [74.125.82.68]) by arlo.cworth.org (Postfix) with ESMTPS id BF9B46DE02C2 for\r
+ <notmuch@notmuchmail.org>; Fri,  3 Jun 2016 10:50:40 -0700 (PDT)\r
+Received: by mail-wm0-f68.google.com with SMTP id n184so1025809wmn.1\r
+ for <notmuch@notmuchmail.org>; Fri, 03 Jun 2016 10:50:40 -0700 (PDT)\r
+DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113;\r
+ h=from:to:cc:subject:date:message-id:in-reply-to:references;\r
+ bh=gNYisbpf26N2J8lEIETN27enaX0zvp1wrXoKwp1aVRk=;\r
+ b=XGxzVJtIWMs4J3ic+giMu+5GoJ5topMIDT036cNcia2Ts3O3MWTLCYJ8/cDzkbuK/F\r
+ qYmStXTayLhWKZm6T60GYfzJ3eRaK46c8B+J9jc/4g2DWfkvjz9CySseWWMTsx+LqGJh\r
+ CzqBf3kGMJEy+bFa63agwqg5jA4dOOoAZT/Kx9UVlIJUzt0tBDlZNpKw4nzieyg08C3/\r
+ VV/bfyLP2ZhsTvUG3H2AIJGO69odSt/O1SP5+n8mLV3RehmSAMFmDI8VIZT0LBQOIe57\r
+ iVRSAPSz+DTLWUtFMfFiPFIytsPYQCfSIAdUl0ElFcTfyFbkJXxwFGnOreenkemxqJVn\r
+ 8SgA==\r
+X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;\r
+ d=1e100.net; s=20130820;\r
+ h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to\r
+ :references;\r
+ bh=gNYisbpf26N2J8lEIETN27enaX0zvp1wrXoKwp1aVRk=;\r
+ b=JVQg2iZmcQAWVmQWGE9dhqXYd2mMCO4fKCOXp+FjpYV8MdWiOc0SMGw0pirHQ8K7fZ\r
+ LTxEAtjc5orZYG0Na3AOuMr/2Sw2HcLkDHwg71efCp1WiOuilXkJ3uqrga5XDp+Ccb4O\r
+ Jj3kOjZtDvsWZyf1uHWFNwVJ/I8Yy/q+tWR5Fno6Qof7CXQiEcz9rUxFfdMbrvHpv97A\r
+ J1ZJofcEIxWh4G04RkFfxsaPHQUOATLYHqTGNAHMA78mSI6HnZPcOwKd2sOdIhu7lk3F\r
+ PAZgSyS4itkMB7GGJ3h97H/SVLcjopvfXlXzpckND7mQSZikEO0PBjnfEqGjm+23tLUQ\r
+ 5GzA==\r
+X-Gm-Message-State:\r
+ ALyK8tJLvX7hufuig5qvZKfMSiEfMGP511wAo2VuKAgabluIp5J+AEVefi2KdiXcg0+ryA==\r
+X-Received: by 10.28.138.13 with SMTP id m13mr677018wmd.3.1464976239118;\r
+ Fri, 03 Jun 2016 10:50:39 -0700 (PDT)\r
+Received: from localhost (5751dfa2.skybroadband.com. [87.81.223.162])\r
+ by smtp.gmail.com with ESMTPSA id l9sm6840402wjm.0.2016.06.03.10.50.33\r
+ (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128);\r
+ Fri, 03 Jun 2016 10:50:35 -0700 (PDT)\r
+From: Mark Walters <markwalters1009@gmail.com>\r
+To: notmuch@notmuchmail.org\r
+Subject: [PATCH v2 3/3] emacs:  postpone/resume support\r
+Date: Fri,  3 Jun 2016 18:49:55 +0100\r
+Message-Id: <1464976195-23134-4-git-send-email-markwalters1009@gmail.com>\r
+X-Mailer: git-send-email 2.1.4\r
+In-Reply-To: <1464976195-23134-1-git-send-email-markwalters1009@gmail.com>\r
+References: <1464976195-23134-1-git-send-email-markwalters1009@gmail.com>\r
+X-BeenThere: notmuch@notmuchmail.org\r
+X-Mailman-Version: 2.1.20\r
+Precedence: list\r
+List-Id: "Use and development of the notmuch mail system."\r
+ <notmuch.notmuchmail.org>\r
+List-Unsubscribe: <https://notmuchmail.org/mailman/options/notmuch>,\r
+ <mailto:notmuch-request@notmuchmail.org?subject=unsubscribe>\r
+List-Archive: <http://notmuchmail.org/pipermail/notmuch/>\r
+List-Post: <mailto:notmuch@notmuchmail.org>\r
+List-Help: <mailto:notmuch-request@notmuchmail.org?subject=help>\r
+List-Subscribe: <https://notmuchmail.org/mailman/listinfo/notmuch>,\r
+ <mailto:notmuch-request@notmuchmail.org?subject=subscribe>\r
+X-List-Received-Date: Fri, 03 Jun 2016 17:51:02 -0000\r
+\r
+This provides preliminary support for postponing and resuming in the\r
+emacs frontend. On postponing it uses notmuch insert to put the\r
+message in the notmuch database; resume gets the raw file from notmuch\r
+and using the emacs function mime-to-mml reconstructs the message\r
+(including attachments).\r
+\r
+Current bindings are C-x C-s to save a draft, C-c C-p to postpone a\r
+draft (save and exit compose buffer), and e to resume a draft from\r
+show or tree mode.\r
+\r
+Previous drafts get tagged deleted on subsequent saves, or on the\r
+message being sent.\r
+\r
+Each draft gets its own message-id, and we use the namespace\r
+draft-.... for draft message ids (so, at least for most people, drafts\r
+are easily distinguisable).\r
+---\r
+ emacs/notmuch-message.el | 135 +++++++++++++++++++++++++++++++++++++++++++++++\r
+ emacs/notmuch-mua.el     |   4 ++\r
+ emacs/notmuch-show.el    |  13 +++++\r
+ emacs/notmuch-tree.el    |   1 +\r
+ 4 files changed, 153 insertions(+)\r
+\r
+diff --git a/emacs/notmuch-message.el b/emacs/notmuch-message.el\r
+index d437b85..0bfb4d3 100644\r
+--- a/emacs/notmuch-message.el\r
++++ b/emacs/notmuch-message.el\r
+@@ -25,6 +25,8 @@\r
+ (require 'notmuch-tag)\r
+ (require 'notmuch-mua)\r
\r
++(declare-function notmuch-show-get-message-id "notmuch-show" (&optional bare))\r
++\r
+ (defcustom notmuch-message-replied-tags '("+replied")\r
+   "List of tag changes to apply to a message when it has been replied to.\r
\r
+@@ -38,6 +40,32 @@ the \"inbox\" and \"todo\" tags, you would set:\r
+   :type '(repeat string)\r
+   :group 'notmuch-send)\r
\r
++(defcustom notmuch-message-draft-tags '("+draft")\r
++  "List of tags changes to apply to a draft message when it is saved in the database.\r
++\r
++Tags starting with \"+\" (or not starting with either \"+\" or\r
++\"-\") in the list will be added, and tags starting with \"-\"\r
++will be removed from the message being stored.\r
++\r
++For example, if you wanted to give the message a \"draft\" tag\r
++but not the (normally added by default) \"inbox\" tag, you would\r
++set:\r
++    (\"+draft\" \"-inbox\")"\r
++  :type '(repeat string)\r
++  :group 'notmuch-send)\r
++\r
++(defcustom notmuch-message-draft-folder "drafts"\r
++  "Folder to save draft messages in.\r
++\r
++This should be specified relative to the root of the notmuch\r
++database. It will be created if necessary."\r
++  :type 'string\r
++  :group 'notmuch-send)\r
++\r
++(defvar notmuch-message-draft-id nil\r
++  "Message-id of the most recent saved draft of this message")\r
++(make-variable-buffer-local 'notmuch-message-draft-id)\r
++\r
+ (defun notmuch-message-mark-replied ()\r
+   ;; get the in-reply-to header and parse it for the message id.\r
+   (let ((rep (mail-header-parse-addresses (message-field-value "In-Reply-To"))))\r
+@@ -45,7 +73,114 @@ the \"inbox\" and \"todo\" tags, you would set:\r
+       (notmuch-tag (notmuch-id-to-query (car (car rep)))\r
+              (notmuch-tag-change-list notmuch-message-replied-tags)))))\r
\r
++(defun notmuch-message-mark-draft-deleted ()\r
++  "Tag the last saved draft deleted.\r
++\r
++Used when a new version is saved, or the message is sent."\r
++  (when notmuch-message-draft-id\r
++    (notmuch-tag notmuch-message-draft-id '("+deleted"))))\r
++\r
++(defun notmuch-message-save-draft ()\r
++  "Save the current draft message in the notmuch database.\r
++\r
++This saves the current message in the database with tags\r
++`notmuch-message-draft-tags` (in addition to any default tags\r
++applied to newly inserted messages)."\r
++  (interactive)\r
++\r
++  ;; This is based on message-do-fcc but modified for our needs.\r
++  (let ((case-fold-search t)\r
++      (buf (current-buffer))\r
++      (mml-externalize-attachments nil)\r
++      ;; We generate a message id now as we will need it later. Note\r
++      ;; message-make-message-id gives the id inside a "<" ">" pair,\r
++      ;; but notmuch doesn't want that form, so remove them.\r
++      (id (concat "draft-" (substring (message-make-message-id) 1 -1))))\r
++    (with-current-buffer (get-buffer-create " *message temp*")\r
++      (erase-buffer)\r
++      (insert-buffer-substring buf)\r
++      ;; We insert a Date header and a Message-ID header, the former\r
++      ;; so that it is easier to search for the message, and the\r
++      ;; latter so we have a way of accessing the saved message (for\r
++      ;; example to delete it at a later time). We check that the\r
++      ;; user has these in `message-deletable-headers` (the default)\r
++      ;; as otherwise they are doing something strange and we\r
++      ;; shouldn't interfere. Note, since we are doing this in a new\r
++      ;; buffer we don't change the version in the compose buffer.\r
++      (if (member 'Message-ID message-deletable-headers)\r
++        (progn\r
++          (message-remove-header "Message-ID")\r
++          (message-add-header (concat "Message-ID: <" id ">")))\r
++      (message "You have customized emacs so Message-ID is not a deletable header, so not changing it")\r
++      (setq id nil))\r
++      (if (member 'Date message-deletable-headers)\r
++        (progn\r
++          (message-remove-header "Date")\r
++          (message-add-header (concat "Date: " (message-make-date))))\r
++      (message "You have customized emacs so Date is not a deletable header, so not changing it"))\r
++\r
++      ;; Back to following message-do-fcc\r
++      (message-encode-message-body)\r
++      (save-restriction\r
++      (message-narrow-to-headers)\r
++      (let ((mail-parse-charset message-default-charset)\r
++            (rfc2047-header-encoding-alist\r
++             (cons '("Newsgroups" . default)\r
++                   rfc2047-header-encoding-alist)))\r
++        (mail-encode-encoded-word-buffer)))\r
++      (goto-char (point-min))\r
++      (when (re-search-forward\r
++           (concat "^" (regexp-quote mail-header-separator) "$")\r
++           nil t)\r
++      (replace-match "" t t ))\r
++\r
++      (apply 'notmuch-call-notmuch-process :stdin-string (buffer-string)\r
++           "insert" "--create-folder"\r
++           (concat "--folder=" notmuch-message-draft-folder)\r
++           notmuch-message-draft-tags))\r
++    ;; We are now back in the original compose buffer. Note the\r
++    ;; function notmuch-call-notmuch-process signals an error on\r
++    ;; failure, so to get to this point it must have succeeded. Note\r
++    ;; notmuch-message-draft-id is still the id of the previous draft,\r
++    ;; so it is safe to mark it deleted.\r
++    (notmuch-message-mark-draft-deleted)\r
++    (setq notmuch-message-draft-id (concat "id:" id))\r
++    (set-buffer-modified-p nil)))\r
++\r
++(defun notmuch-message-postpone ()\r
++  "Save the draft message in the notmuch database and exit buffer."\r
++  (interactive)\r
++  (notmuch-message-save-draft)\r
++  (kill-buffer))\r
++\r
++(defun notmuch-message-resume (id)\r
++  "Resume editting of message with id ID."\r
++  (switch-to-buffer (get-buffer-create (concat "*notmuch-draft-" id "*")))\r
++  (setq buffer-read-only nil)\r
++  (erase-buffer)\r
++  (let ((coding-system-for-read 'no-conversion))\r
++      (call-process notmuch-command nil t nil "show" "--format=raw" id))\r
++  (mime-to-mml)\r
++  (goto-char (point-min))\r
++  (when (re-search-forward "^$" nil t)\r
++    (replace-match mail-header-separator t t))\r
++  ;; Remove our added Date and Message-ID headers (unless the user has\r
++  ;; explicitly customized emacs to tell us not to).\r
++  (save-restriction\r
++    (message-narrow-to-headers)\r
++    (when (member 'Message-ID message-deletable-headers)\r
++      (message-remove-header "Message-ID"))\r
++    (when (member 'Date message-deletable-headers)\r
++      (message-remove-header "Date")))\r
++  (notmuch-message-mode)\r
++  (set-buffer-modified-p t)\r
++  ;; Set the draft message-id so that we can delete the current saved draft if the\r
++  ;; message is resaved or sent.\r
++  (setq notmuch-message-draft-id id))\r
++\r
++\r
+ (add-hook 'message-send-hook 'notmuch-message-mark-replied)\r
++(add-hook 'message-send-hook 'notmuch-message-mark-draft-deleted)\r
\r
+ (provide 'notmuch-message)\r
\r
+diff --git a/emacs/notmuch-mua.el b/emacs/notmuch-mua.el\r
+index 399e138..3118e5d 100644\r
+--- a/emacs/notmuch-mua.el\r
++++ b/emacs/notmuch-mua.el\r
+@@ -33,6 +33,8 @@\r
+ (declare-function notmuch-show-insert-body "notmuch-show" (msg body depth))\r
+ (declare-function notmuch-fcc-header-setup "notmuch-maildir-fcc" ())\r
+ (declare-function notmuch-fcc-handler "notmuch-maildir-fcc" (destdir))\r
++(declare-function notmuch-message-postpone "notmuch-message" ())\r
++(declare-function notmuch-message-save-draft "notmuch-message" ())\r
\r
+ ;;\r
\r
+@@ -283,6 +285,8 @@ mutiple parts get a header."\r
\r
+ (define-key notmuch-message-mode-map (kbd "C-c C-c") #'notmuch-mua-send-and-exit)\r
+ (define-key notmuch-message-mode-map (kbd "C-c C-s") #'notmuch-mua-send)\r
++(define-key notmuch-message-mode-map (kbd "C-c C-p") #'notmuch-message-postpone)\r
++(define-key notmuch-message-mode-map (kbd "C-x C-s") #'notmuch-message-save-draft)\r
\r
+ (defun notmuch-mua-pop-to-buffer (name switch-function)\r
+   "Pop to buffer NAME, and warn if it already exists and is\r
+diff --git a/emacs/notmuch-show.el b/emacs/notmuch-show.el\r
+index f33096c..998fd27 100644\r
+--- a/emacs/notmuch-show.el\r
++++ b/emacs/notmuch-show.el\r
+@@ -38,6 +38,7 @@\r
+ (require 'notmuch-mua)\r
+ (require 'notmuch-crypto)\r
+ (require 'notmuch-print)\r
++(require 'notmuch-message)\r
\r
+ (declare-function notmuch-call-notmuch-process "notmuch" (&rest args))\r
+ (declare-function notmuch-search-next-thread "notmuch" nil)\r
+@@ -1425,6 +1426,7 @@ reset based on the original query."\r
+     (define-key map "|" 'notmuch-show-pipe-message)\r
+     (define-key map "w" 'notmuch-show-save-attachments)\r
+     (define-key map "V" 'notmuch-show-view-raw-message)\r
++    (define-key map "e" 'notmuch-show-resume-message)\r
+     (define-key map "c" 'notmuch-show-stash-map)\r
+     (define-key map "h" 'notmuch-show-toggle-visibility-headers)\r
+     (define-key map "*" 'notmuch-show-tag-all)\r
+@@ -1955,6 +1957,17 @@ to show, nil otherwise."\r
+     (setq buffer-read-only t)\r
+     (view-buffer buf 'kill-buffer-if-not-modified)))\r
\r
++(defun notmuch-show-resume-message ()\r
++  "Resume EDITING the current draft message.\r
++\r
++Resume the current message. Queries if the message does not\r
++appear to be a draft."\r
++  (interactive)\r
++  (let ((tags (notmuch-show-get-tags)))\r
++    (when (or (equal tags (notmuch-update-tags tags notmuch-message-draft-tags))\r
++            (yes-or-no-p "Message does not appear to be a draft: really resume?"))\r
++      (notmuch-message-resume (notmuch-show-get-message-id)))))\r
++\r
+ (put 'notmuch-show-pipe-message 'notmuch-doc\r
+      "Pipe the contents of the current message to a command.")\r
+ (put 'notmuch-show-pipe-message 'notmuch-prefix-doc\r
+diff --git a/emacs/notmuch-tree.el b/emacs/notmuch-tree.el\r
+index 6c35543..c759290 100644\r
+--- a/emacs/notmuch-tree.el\r
++++ b/emacs/notmuch-tree.el\r
+@@ -261,6 +261,7 @@ FUNC."\r
+     (define-key map "r" (notmuch-tree-close-message-pane-and #'notmuch-show-reply-sender))\r
+     (define-key map "R" (notmuch-tree-close-message-pane-and #'notmuch-show-reply))\r
+     (define-key map "V" (notmuch-tree-close-message-pane-and #'notmuch-show-view-raw-message))\r
++    (define-key map "e" (notmuch-tree-close-message-pane-and #'notmuch-show-resume-message))\r
\r
+     ;; The main tree view bindings\r
+     (define-key map (kbd "RET") 'notmuch-tree-show-message)\r
+-- \r
+2.1.4\r
+\r