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