[PATCH 2/2] emacs: Allow tagging regions in notmuch-tree

Pierre Neidhardt mail at ambrevar.xyz
Tue Apr 9 09:47:45 PDT 2019

 emacs/notmuch-tree.el | 101 +++++++++++++++++++++++++++++++++++-------
 1 file changed, 86 insertions(+), 15 deletions(-)

diff --git a/emacs/notmuch-tree.el b/emacs/notmuch-tree.el
index c00315e8..ff471c19 100644
--- a/emacs/notmuch-tree.el
+++ b/emacs/notmuch-tree.el
@@ -297,13 +297,15 @@ FUNC."
 (fset 'notmuch-tree-mode-map notmuch-tree-mode-map)
-(defun notmuch-tree-get-message-properties ()
+(defun notmuch-tree-get-message-properties (&optional pos)
   "Return the properties of the current message as a plist.
 Some useful entries are:
 :headers - Property list containing the headers :Date, :Subject, :From, etc.
 :tags - Tags for this message"
+  (setq pos (or pos (point)))
+    (goto-char pos)
     (get-text-property (point) :notmuch-message-properties)))
@@ -332,6 +334,13 @@ Some useful entries are:
   "Return the tags of the current message."
   (notmuch-tree-get-prop :tags))
+(defun notmuch-tree-get-tags-region (beg end)
+  (let (output)
+    (notmuch-tree-foreach-result beg end
+      (lambda (pos)
+	(setq output (append output (notmuch-tree-get-tags)))))
+    output))
 (defun notmuch-tree-get-message-id (&optional bare)
   "Return the message id of the current message."
   (let ((id (notmuch-tree-get-prop :id)))
@@ -387,24 +396,86 @@ NOT change the database."
 	  (when (string= tree-msg-id (notmuch-show-get-message-id))
 	    (notmuch-show-update-tags new-tags)))))))
-(defun notmuch-tree-tag (tag-changes)
+(defun notmuch-tree-result-beginning (&optional pos)
+  "Return the point at the beginning of the message at POS (or point).
+If there is no thread at POS (or point), returns nil."
+  (when (notmuch-tree-get-message-properties pos)
+    ;; We pass 1+point because previous-single-property-change starts
+    ;; searching one before the position we give it.
+    (previous-single-property-change (1+ (or pos (point)))
+				     :notmuch-message-properties nil (point-min))))
+(defun notmuch-tree-result-end (&optional pos)
+  "Return the point at the end of the message at POS (or point).
+The returned point will be just after the newline character that
+ends the result line.  If there is no thread at POS (or point),
+returns nil"
+  (when (notmuch-tree-get-message-properties pos)
+    (next-single-property-change (or pos (point)) :notmuch-message-properties
+				 nil (point-max))))
+(defun notmuch-tree-foreach-result (beg end fn)
+  "Invoke FN for each result between BEG and END.
+FN should take one argument.  It will be applied to the
+character position of the beginning of each result that overlaps
+the region between points BEG and END.  As a special case, if (=
+BEG END), FN will be applied to the result containing point
+  (lexical-let ((pos (notmuch-tree-result-beginning beg))
+		;; End must be a marker in case fn changes the
+		;; text.
+		(end (copy-marker end))
+		;; Make sure we examine at least one result, even if
+		;; (= beg end).
+		(first t))
+    ;; We have to be careful if the region extends beyond the results.
+    ;; In this case, pos could be null or there could be no result at
+    ;; pos.
+    (while (and pos (or (< pos end) first))
+      (when (notmuch-tree-get-message-properties pos)
+	(funcall fn pos))
+      (setq pos (notmuch-tree-result-end pos)
+	    first nil))))
+(put 'notmuch-tree-foreach-result 'lisp-indent-function 2)
+(defun notmuch-tree-interactive-tag-changes (&optional initial-input)
+  "Prompt for tag changes for the current message or region.
+  (let* ((region (notmuch-interactive-region))
+	 (beg (first region)) (end (second region))
+	 (prompt (if (= beg end) "Tag message" "Tag region")))
+    (cons (notmuch-read-tag-changes
+	   (notmuch-tree-get-tags-region beg end) prompt initial-input)
+	  region)))
+(defun notmuch-tree-tag (tag-changes &optional beg end)
   "Change tags for the current message"
-  (interactive
-   (list (notmuch-read-tag-changes (notmuch-tree-get-tags) "Tag message")))
-  (notmuch-tag (notmuch-tree-get-message-id) tag-changes)
-  (notmuch-tree-tag-update-display tag-changes))
-(defun notmuch-tree-add-tag (tag-changes)
+  (interactive (notmuch-tree-interactive-tag-changes))
+  (unless (and beg end)
+    (setq beg (car (notmuch-interactive-region))
+          end (cadr (notmuch-interactive-region))))
+  (notmuch-tree-foreach-result beg end
+    (lambda (pos)
+      (save-mark-and-excursion
+        (goto-char pos)
+	(notmuch-tag (notmuch-tree-get-message-id)
+                     tag-changes)
+	(notmuch-tree-tag-update-display tag-changes)))))
+(defun notmuch-tree-add-tag (tag-changes &optional beg end)
   "Same as `notmuch-tree-tag' but sets initial input to '+'."
-  (interactive
-   (list (notmuch-read-tag-changes (notmuch-tree-get-tags) "Tag message" "+")))
-  (notmuch-tree-tag tag-changes))
+  (interactive (notmuch-tree-interactive-tag-changes "+"))
+  (notmuch-tree-tag tag-changes beg end))
-(defun notmuch-tree-remove-tag (tag-changes)
+(defun notmuch-tree-remove-tag (tag-changes &optional beg end)
   "Same as `notmuch-tree-tag' but sets initial input to '-'."
-  (interactive
-   (list (notmuch-read-tag-changes (notmuch-tree-get-tags) "Tag message" "-")))
-  (notmuch-tree-tag tag-changes))
+  (interactive (notmuch-tree-interactive-tag-changes "-"))
+  (notmuch-tree-tag tag-changes beg end))
 (defun notmuch-tree-resume-message ()
   "Resume EDITING the current draft message."

More information about the notmuch mailing list