Re: [PATCH] emacs: wash: make word-wrap bound message width
[notmuch-archives.git] / fa / 654471f8b97bb8bad9989fb1178c83c8968639
1 Return-Path: <amdragon@mit.edu>\r
2 X-Original-To: notmuch@notmuchmail.org\r
3 Delivered-To: notmuch@notmuchmail.org\r
4 Received: from localhost (localhost [127.0.0.1])\r
5         by olra.theworths.org (Postfix) with ESMTP id 98606429E4C\r
6         for <notmuch@notmuchmail.org>; Sun, 29 Jan 2012 14:58:02 -0800 (PST)\r
7 X-Virus-Scanned: Debian amavisd-new at olra.theworths.org\r
8 X-Spam-Flag: NO\r
9 X-Spam-Score: -0.7\r
10 X-Spam-Level: \r
11 X-Spam-Status: No, score=-0.7 tagged_above=-999 required=5\r
12         tests=[RCVD_IN_DNSWL_LOW=-0.7] autolearn=disabled\r
13 Received: from olra.theworths.org ([127.0.0.1])\r
14         by localhost (olra.theworths.org [127.0.0.1]) (amavisd-new, port 10024)\r
15         with ESMTP id xZymCiIjACJx for <notmuch@notmuchmail.org>;\r
16         Sun, 29 Jan 2012 14:58:01 -0800 (PST)\r
17 Received: from dmz-mailsec-scanner-4.mit.edu (DMZ-MAILSEC-SCANNER-4.MIT.EDU\r
18         [18.9.25.15])\r
19         by olra.theworths.org (Postfix) with ESMTP id D4D7F431E64\r
20         for <notmuch@notmuchmail.org>; Sun, 29 Jan 2012 14:58:00 -0800 (PST)\r
21 X-AuditID: 1209190f-b7f8a6d000000914-14-4f25cef8cadb\r
22 Received: from mailhub-auth-1.mit.edu ( [18.9.21.35])\r
23         by dmz-mailsec-scanner-4.mit.edu (Symantec Messaging Gateway) with SMTP\r
24         id 7D.D6.02324.8FEC52F4; Sun, 29 Jan 2012 17:58:00 -0500 (EST)\r
25 Received: from outgoing.mit.edu (OUTGOING-AUTH.MIT.EDU [18.7.22.103])\r
26         by mailhub-auth-1.mit.edu (8.13.8/8.9.2) with ESMTP id q0TMvxIA014616; \r
27         Sun, 29 Jan 2012 17:58:00 -0500\r
28 Received: from awakening.csail.mit.edu (awakening.csail.mit.edu [18.26.4.91])\r
29         (authenticated bits=0)\r
30         (User authenticated as amdragon@ATHENA.MIT.EDU)\r
31         by outgoing.mit.edu (8.13.6/8.12.4) with ESMTP id q0TMvwHX022164\r
32         (version=TLSv1/SSLv3 cipher=AES256-SHA bits=256 verify=NOT);\r
33         Sun, 29 Jan 2012 17:57:59 -0500 (EST)\r
34 Received: from amthrax by awakening.csail.mit.edu with local (Exim 4.77)\r
35         (envelope-from <amdragon@mit.edu>)\r
36         id 1RrdgM-00007O-Hb; Sun, 29 Jan 2012 17:57:10 -0500\r
37 Date: Sun, 29 Jan 2012 17:57:10 -0500\r
38 From: Austin Clements <amdragon@MIT.EDU>\r
39 To: Dmitry Kurochkin <dmitry.kurochkin@gmail.com>\r
40 Subject: Re: [PATCH 3/6] emacs: make "+" and "-" tagging operations more\r
41  robust\r
42 Message-ID: <20120129225710.GG17991@mit.edu>\r
43 References: <1327725684-5887-1-git-send-email-dmitry.kurochkin@gmail.com>\r
44         <1327725684-5887-3-git-send-email-dmitry.kurochkin@gmail.com>\r
45 MIME-Version: 1.0\r
46 Content-Type: text/plain; charset=us-ascii\r
47 Content-Disposition: inline\r
48 In-Reply-To: <1327725684-5887-3-git-send-email-dmitry.kurochkin@gmail.com>\r
49 User-Agent: Mutt/1.5.21 (2010-09-15)\r
50 X-Brightmail-Tracker:\r
51  H4sIAAAAAAAAA+NgFprEKsWRmVeSWpSXmKPExsUixCmqrPvjnKq/wesjPBZXt/azW1y/OZPZ\r
52         gclj56y77B7PVt1iDmCK4rJJSc3JLEst0rdL4Mp4f3Quc8HMXsaKnt0X2BoYfxZ2MXJySAiY\r
53         SKx8fIoJwhaTuHBvPVsXIxeHkMA+Rol1p2+yQjgbGCU6Hx5ih3BOMkns29zDBOEsYZRY/fAu\r
54         M0g/i4CqxMaz6xlBbDYBDYlt+5eD2SIChhK3Lr4Cq2EWkJb49rsZbJ+wQKBE1+W1LCA2r4CO\r
55         xJ6t0xkhhnYySsyd/QIqIShxcuYTFohmLYkb/14CNXOADVr+jwMkzCngJXFnzyJ2EFtUQEVi\r
56         ysltbBMYhWYh6Z6FpHsWQvcCRuZVjLIpuVW6uYmZOcWpybrFyYl5ealFuiZ6uZkleqkppZsY\r
57         wcEtyb+D8dtBpUOMAhyMSjy8J5aq+AuxJpYVV+YeYpTkYFIS5W05o+ovxJeUn1KZkVicEV9U\r
58         mpNafIhRgoNZSYR3zjKgHG9KYmVValE+TEqag0VJnFdN652fkEB6YklqdmpqQWoRTFaGg0NJ\r
59         gjcPGMVCgkWp6akVaZk5JQhpJg5OkOE8QMN7QGp4iwsSc4sz0yHypxh1Od7O33+eUYglLz8v\r
60         VUqcNxqkSACkKKM0D24OLCm9YhQHekuYNxKkigeY0OAmvQJawgS05DkD2JKSRISUVAOjdWMZ\r
61         177jsb8vzs8+ctXyvpi80ObOVV3icqyFdcK/hesjtHef7PqUFvtx/lp930mz1pw4Z/vPR917\r
62         zpwDleldHrrLW6JNmufd+1Dr1PzFYuOkCUL3bKT4avk9+DfIsMkpaCjUzItkMunq3z/7Qrlh\r
63         t5b+wfVXCuZmZ/X3vAhi7f215E5Cp4USS3FGoqEWc1FxIgC9rLRQJQMAAA==\r
64 Cc: notmuch@notmuchmail.org\r
65 X-BeenThere: notmuch@notmuchmail.org\r
66 X-Mailman-Version: 2.1.13\r
67 Precedence: list\r
68 List-Id: "Use and development of the notmuch mail system."\r
69         <notmuch.notmuchmail.org>\r
70 List-Unsubscribe: <http://notmuchmail.org/mailman/options/notmuch>,\r
71         <mailto:notmuch-request@notmuchmail.org?subject=unsubscribe>\r
72 List-Archive: <http://notmuchmail.org/pipermail/notmuch>\r
73 List-Post: <mailto:notmuch@notmuchmail.org>\r
74 List-Help: <mailto:notmuch-request@notmuchmail.org?subject=help>\r
75 List-Subscribe: <http://notmuchmail.org/mailman/listinfo/notmuch>,\r
76         <mailto:notmuch-request@notmuchmail.org?subject=subscribe>\r
77 X-List-Received-Date: Sun, 29 Jan 2012 22:58:02 -0000\r
78 \r
79 I'm looking forward to having this.  I think it'll streamline tagging\r
80 operations.\r
81 \r
82 Quoth Dmitry Kurochkin on Jan 28 at  8:41 am:\r
83 > Before the change, "+" and "-" tagging operations in notmuch-search\r
84 > and notmuch-show views accepted only a single tag.  The patch makes\r
85 > them use the recently added `notmuch-select-tags-with-completion'\r
86 > function, which allows to enter multiple tags with "+" and "-"\r
87 > prefixes.  So after the change, "+" and "-" bindings allow to both add\r
88 > and remove multiple tags.  The only difference between "+" and "-" is\r
89 > the minibuffer initial input ("+" and "-" respectively).\r
90 \r
91 This patch was a little difficult to review because it was largish and\r
92 the diff happened to contain a bunch of forward references.  If it's\r
93 convenient (don't bother if it's not), could you divide up the next\r
94 version a little?  Something simple like sending the show changes as a\r
95 separate patch would probably make it a lot easier.\r
96 \r
97 > ---\r
98 >  emacs/notmuch-show.el |   65 +++++++------------\r
99 >  emacs/notmuch.el      |  165 +++++++++++++++++++++++++------------------------\r
100 >  2 files changed, 107 insertions(+), 123 deletions(-)\r
101\r
102 > diff --git a/emacs/notmuch-show.el b/emacs/notmuch-show.el\r
103 > index 84ac624..03eadfb 100644\r
104 > --- a/emacs/notmuch-show.el\r
105 > +++ b/emacs/notmuch-show.el\r
106 > @@ -38,8 +38,9 @@\r
107 >  \r
108 >  (declare-function notmuch-call-notmuch-process "notmuch" (&rest args))\r
109 >  (declare-function notmuch-fontify-headers "notmuch" nil)\r
110 > -(declare-function notmuch-select-tag-with-completion "notmuch" (prompt &rest search-terms))\r
111 > +(declare-function notmuch-select-tags-with-completion "notmuch" (&optional initial-input &rest search-terms))\r
112 >  (declare-function notmuch-search-show-thread "notmuch" nil)\r
113 > +(declare-function notmuch-update-tags "notmuch" (current-tags changed-tags))\r
114 >  \r
115 >  (defcustom notmuch-message-headers '("Subject" "To" "Cc" "Date")\r
116 >    "Headers that should be shown in a message, in this order.\r
117 > @@ -1267,7 +1268,7 @@ Some useful entries are:\r
118 >  \r
119 >  (defun notmuch-show-mark-read ()\r
120 >    "Mark the current message as read."\r
121 > -  (notmuch-show-remove-tag "unread"))\r
122 > +  (notmuch-show-tag-message "-unread"))\r
123 >  \r
124 >  ;; Functions for getting attributes of several messages in the current\r
125 >  ;; thread.\r
126 > @@ -1470,51 +1471,33 @@ than only the current message."\r
127 >           (message (format "Command '%s' exited abnormally with code %d"\r
128 >                            shell-command exit-code))))))))\r
129 >  \r
130 > -(defun notmuch-show-add-tags-worker (current-tags add-tags)\r
131 > -  "Add to `current-tags' with any tags from `add-tags' not\r
132 > -currently present and return the result."\r
133 > -  (let ((result-tags (copy-sequence current-tags)))\r
134 > -    (mapc (lambda (add-tag)\r
135 > -         (unless (member add-tag current-tags)\r
136 > -           (setq result-tags (push add-tag result-tags))))\r
137 > -         add-tags)\r
138 > -    (sort result-tags 'string<)))\r
139 > -\r
140 > -(defun notmuch-show-del-tags-worker (current-tags del-tags)\r
141 > -  "Remove any tags in `del-tags' from `current-tags' and return\r
142 > -the result."\r
143 > -  (let ((result-tags (copy-sequence current-tags)))\r
144 > -    (mapc (lambda (del-tag)\r
145 > -         (setq result-tags (delete del-tag result-tags)))\r
146 > -       del-tags)\r
147 > -    result-tags))\r
148 > -\r
149 > -(defun notmuch-show-add-tag (&rest toadd)\r
150 > -  "Add a tag to the current message."\r
151 > -  (interactive\r
152 > -   (list (notmuch-select-tag-with-completion "Tag to add: ")))\r
153 > +(defun notmuch-show-tag-message (&rest changed-tags)\r
154 > +  "Change tags for the current message.\r
155 >  \r
156 > +`Changed-tags' is a list of tag operations for \"notmuch tag\",\r
157 > +i.e. a list of tags to change with '+' and '-' prefixes."\r
158 \r
159 Ticks in a docstring indicate functions (and will be hyperlinked as\r
160 such by describe-function).  Typically, argument names are indicated\r
161 by writing them in all caps.\r
162 \r
163 Also, it probably makes more sense to reference `notmuch-tag' than\r
164 "notmuch tag", since this is Lisp land (and, since that will be\r
165 helpfully hyperlinked, you probably don't need to explain changed-tags\r
166 here).\r
167 \r
168 >    (let* ((current-tags (notmuch-show-get-tags))\r
169 > -      (new-tags (notmuch-show-add-tags-worker current-tags toadd)))\r
170 > -\r
171 > +      (new-tags (notmuch-update-tags current-tags changed-tags)))\r
172 >      (unless (equal current-tags new-tags)\r
173 > -      (apply 'notmuch-tag (notmuch-show-get-message-id)\r
174 > -          (mapcar (lambda (s) (concat "+" s)) toadd))\r
175 > +      (apply 'notmuch-tag (notmuch-show-get-message-id) changed-tags)\r
176 >        (notmuch-show-set-tags new-tags))))\r
177 >  \r
178 > -(defun notmuch-show-remove-tag (&rest toremove)\r
179 > -  "Remove a tag from the current message."\r
180 > -  (interactive\r
181 > -   (list (notmuch-select-tag-with-completion\r
182 > -       "Tag to remove: " (notmuch-show-get-message-id))))\r
183 > +(defun notmuch-show-tag (&optional initial-input)\r
184 > +  "Change tags for the current message, read input from the minibuffer."\r
185 > +  (interactive)\r
186 > +  (let ((changed-tags (notmuch-select-tags-with-completion\r
187 > +                    initial-input (notmuch-show-get-message-id))))\r
188 > +    (apply 'notmuch-show-tag-message changed-tags)))\r
189 >  \r
190 > -  (let* ((current-tags (notmuch-show-get-tags))\r
191 > -      (new-tags (notmuch-show-del-tags-worker current-tags toremove)))\r
192 > +(defun notmuch-show-add-tag ()\r
193 > +  "Same as `notmuch-show-tag' but sets initial input to '+'."\r
194 > +  (interactive)\r
195 > +  (notmuch-show-tag "+"))\r
196 >  \r
197 > -    (unless (equal current-tags new-tags)\r
198 > -      (apply 'notmuch-tag (notmuch-show-get-message-id)\r
199 > -          (mapcar (lambda (s) (concat "-" s)) toremove))\r
200 > -      (notmuch-show-set-tags new-tags))))\r
201 > +(defun notmuch-show-remove-tag ()\r
202 > +  "Same as `notmuch-show-tag' but sets initial input to '-'."\r
203 > +  (interactive)\r
204 > +  (notmuch-show-tag "-"))\r
205 \r
206 Should notmuch-show-{add,remove}-tag be considered public functions?\r
207 Previously, they were amenable to creating bindings for adding or\r
208 removing individual tags, and I believe people have done this.  If\r
209 we're okay with breaking backward-compatibility, there should at least\r
210 be a NEWS item explaining how to convert such custom bindings to use\r
211 notmuch-show-tag-message.\r
212 \r
213 >  \r
214 >  (defun notmuch-show-toggle-headers ()\r
215 >    "Toggle the visibility of the current message headers."\r
216 > @@ -1559,7 +1542,7 @@ argument, hide all of the messages."\r
217 >  (defun notmuch-show-archive-thread-internal (show-next)\r
218 >    ;; Remove the tag from the current set of messages.\r
219 >    (goto-char (point-min))\r
220 > -  (loop do (notmuch-show-remove-tag "inbox")\r
221 > +  (loop do (notmuch-show-tag-message "-inbox")\r
222 >       until (not (notmuch-show-goto-message-next)))\r
223 >    ;; Move to the next item in the search results, if any.\r
224 >    (let ((parent-buffer notmuch-show-parent-buffer))\r
225 > diff --git a/emacs/notmuch.el b/emacs/notmuch.el\r
226 > index ff46617..24b0ea3 100644\r
227 > --- a/emacs/notmuch.el\r
228 > +++ b/emacs/notmuch.el\r
229 > @@ -76,38 +76,56 @@ For example:\r
230 >  (defvar notmuch-query-history nil\r
231 >    "Variable to store minibuffer history for notmuch queries")\r
232 >  \r
233 > -(defun notmuch-tag-completions (&optional prefixes search-terms)\r
234 > -  (let ((tag-list\r
235 > -      (split-string\r
236 > -       (with-output-to-string\r
237 > -         (with-current-buffer standard-output\r
238 > -           (apply 'call-process notmuch-command nil t\r
239 > -                  nil "search-tags" search-terms)))\r
240 > -       "\n+" t)))\r
241 > -    (if (null prefixes)\r
242 > -     tag-list\r
243 > -      (apply #'append\r
244 > -          (mapcar (lambda (tag)\r
245 > -                    (mapcar (lambda (prefix)\r
246 > -                              (concat prefix tag)) prefixes))\r
247 > -                  tag-list)))))\r
248 > +(defun notmuch-tag-completions (&optional search-terms)\r
249 > +  (split-string\r
250 > +   (with-output-to-string\r
251 > +     (with-current-buffer standard-output\r
252 > +       (apply 'call-process notmuch-command nil t\r
253 > +           nil "search-tags" search-terms)))\r
254 > +   "\n+" t))\r
255 >  \r
256 >  (defun notmuch-select-tag-with-completion (prompt &rest search-terms)\r
257 > -  (let ((tag-list (notmuch-tag-completions nil search-terms)))\r
258 > +  (let ((tag-list (notmuch-tag-completions search-terms)))\r
259 >      (completing-read prompt tag-list)))\r
260 >  \r
261 > -(defun notmuch-select-tags-with-completion (prompt &optional prefixes &rest search-terms)\r
262 > -  (let ((tag-list (notmuch-tag-completions prefixes search-terms))\r
263 > -     (crm-separator " ")\r
264 > -     ;; By default, space is bound to "complete word" function.\r
265 > -     ;; Re-bind it to insert a space instead.  Note that <tab>\r
266 > -     ;; still does the completion.\r
267 > -     (crm-local-completion-map\r
268 > -      (let ((map (make-sparse-keymap)))\r
269 > -        (set-keymap-parent map crm-local-completion-map)\r
270 > -        (define-key map " " 'self-insert-command)\r
271 > -        map)))\r
272 > -    (delete "" (completing-read-multiple prompt tag-list))))\r
273 > +(defun notmuch-select-tags-with-completion (&optional initial-input &rest search-terms)\r
274 \r
275 I don't know if notmuch-select-tags-with-completion is the right name\r
276 for this now that it hard-codes the +/- prefixes (which seems like the\r
277 right thing to do, BTW).  Maybe notmuch-read-tags-add-remove?\r
278 \r
279 > +  (let* ((add-tag-list (mapcar (apply-partially 'concat "+")\r
280 > +                            (notmuch-tag-completions)))\r
281 > +      (remove-tag-list (mapcar (apply-partially 'concat "-")\r
282 > +                               (notmuch-tag-completions search-terms)))\r
283 \r
284 This will make two calls to notmuch search, but often one will\r
285 suffice.  It's probably worth optimizing the case were search-terms is\r
286 nil.\r
287 \r
288 > +      (tag-list (append add-tag-list remove-tag-list))\r
289 > +      (crm-separator " ")\r
290 > +      ;; By default, space is bound to "complete word" function.\r
291 > +      ;; Re-bind it to insert a space instead.  Note that <tab>\r
292 > +      ;; still does the completion.\r
293 > +      (crm-local-completion-map\r
294 > +       (let ((map (make-sparse-keymap)))\r
295 > +         (set-keymap-parent map crm-local-completion-map)\r
296 > +         (define-key map " " 'self-insert-command)\r
297 > +         map)))\r
298 > +    (delete "" (completing-read-multiple\r
299 > +             "Operations (+add -drop): notmuch tag " tag-list nil\r
300 \r
301 I don't think the "notmuch tag" part is necessary.  From the\r
302 perspective of a person who only uses the Emacs UI, this will be\r
303 meaningless.  Maybe "Tag changes (+add -drop): " or even just "Tags\r
304 (+add -drop): " since the "+add -drop" part implies what you're doing.\r
305 \r
306 > +             nil initial-input))))\r
307 > +\r
308 > +(defun notmuch-update-tags (current-tags changed-tags)\r
309 \r
310 Maybe just "tags" instead of "current-tags"?  Nothing says they have\r
311 to be current.  It's just a list of tags.\r
312 \r
313 Also, changed-tags makes it sound like a list of tags, which is isn't.\r
314 Maybe tag-changes?\r
315 \r
316 > +  "Update `current-tags' with `changed-tags' and return the result.\r
317 > +\r
318 > +`Changed-tags' is a list of tag operations given to \"notmuch\r
319 > +tag\", i.e. a list of changed tags with '+' and '-' prefixes."\r
320 \r
321 Same comment about ticks and "notmuch tag".\r
322 \r
323 I found this docstring a bit confusing.  I wasn't sure exactly what it\r
324 meant to "update current-tags with changed-tags" (though replacing\r
325 changed-tags with tag-changes would probably help).  Plus, this\r
326 function does not, in fact, update current-tags.  Maybe,\r
327 \r
328   "Return a copy of TAGS with additions and removals from TAG-CHANGES.\r
329 \r
330 TAG-CHANGES must be a list of tags names, each prefixed with either a\r
331 \"+\" to indicate the tag should be added to TAGS if not present or a\r
332 \"-\" to indicate that the tag should be removed from TAGS if\r
333 present."\r
334 \r
335 > +  (let ((result-tags (copy-sequence current-tags)))\r
336 > +    (mapc (lambda (changed-tag)\r
337 \r
338 Consider dolist instead of mapc, though this is a matter of taste.  It\r
339 leads to less indentation (and does have precedent in the notmuch\r
340 code, though mapc is more common).\r
341 \r
342 Too bad Elisp doesn't have fold.\r
343 \r
344 > +         (unless (string= changed-tag "")\r
345 > +           (let ((op (substring changed-tag 0 1))\r
346 > +                 (tag (substring changed-tag 1)))\r
347 > +             (cond ((string= op "+")\r
348 > +                    (unless (member tag result-tags)\r
349 > +                      (push tag result-tags)))\r
350 > +                   ((string= op "-")\r
351 > +                    (setq result-tags (delete tag result-tags)))\r
352 > +                   (t\r
353 > +                    (error "Changed tag must be of the form `+this_tag' or `-that_tag'"))))))\r
354 \r
355 I would suggest case instead of cond, but, again, that's a matter of\r
356 taste.\r
357 \r
358 > +       changed-tags)\r
359 > +    (sort result-tags 'string<)))\r
360 >  \r
361 >  (defun notmuch-foreach-mime-part (function mm-handle)\r
362 >    (cond ((stringp (car mm-handle))\r
363 > @@ -447,6 +465,10 @@ Complete list of currently available key bindings:\r
364 >    "Return a list of threads for the current region"\r
365 >    (notmuch-search-properties-in-region 'notmuch-search-thread-id beg end))\r
366 >  \r
367 > +(defun notmuch-search-find-thread-id-region-search (beg end)\r
368 > +  "Return a search string for threads for the current region"\r
369 > +  (mapconcat 'identity (notmuch-search-find-thread-id-region beg end) " or "))\r
370 > +\r
371 >  (defun notmuch-search-find-authors ()\r
372 >    "Return the authors for the current thread"\r
373 >    (get-text-property (point) 'notmuch-search-authors))\r
374 > @@ -590,74 +612,55 @@ the messages that were tagged"\r
375 >       (forward-line 1))\r
376 >        output)))\r
377 >  \r
378 > -(defun notmuch-search-add-tag-thread (tag)\r
379 > -  (notmuch-search-add-tag-region tag (point) (point)))\r
380 > +(defun notmuch-search-tag-thread (&rest tags)\r
381 > +  "Change tags for the currently selected thread.\r
382 >  \r
383 > -(defun notmuch-search-add-tag-region (tag beg end)\r
384 > -  (let ((search-id-string (mapconcat 'identity (notmuch-search-find-thread-id-region beg end) " or ")))\r
385 > -    (notmuch-tag search-id-string (concat "+" tag))\r
386 > -    (save-excursion\r
387 > -      (let ((last-line (line-number-at-pos end))\r
388 > -         (max-line (- (line-number-at-pos (point-max)) 2)))\r
389 > -     (goto-char beg)\r
390 > -     (while (<= (line-number-at-pos) (min last-line max-line))\r
391 > -       (notmuch-search-set-tags (delete-dups (sort (cons tag (notmuch-search-get-tags)) 'string<)))\r
392 > -       (forward-line))))))\r
393 > +See `notmuch-search-tag-region' for details."\r
394 > +  (apply 'notmuch-search-tag-region (point) (point) tags))\r
395 >  \r
396 > -(defun notmuch-search-remove-tag-thread (tag)\r
397 > -  (notmuch-search-remove-tag-region tag (point) (point)))\r
398 > +(defun notmuch-search-tag-region (beg end &rest tags)\r
399 > +  "Change tags for threads in the given region.\r
400 >  \r
401 > -(defun notmuch-search-remove-tag-region (tag beg end)\r
402 > -  (let ((search-id-string (mapconcat 'identity (notmuch-search-find-thread-id-region beg end) " or ")))\r
403 > -    (notmuch-tag search-id-string (concat "-" tag))\r
404 > +`Tags' is a list of tag operations for \"notmuch tag\", i.e. a\r
405 > +list of tags to change with '+' and '-' prefixes.  The tags are\r
406 > +added or removed for all threads in the region from `beg' to\r
407 > +`end'."\r
408 \r
409 Same comment about ticks and "notmuch tag".\r
410 \r
411 > +  (let ((search-string (notmuch-search-find-thread-id-region-search beg end)))\r
412 > +    (apply 'notmuch-tag search-string tags)\r
413 >      (save-excursion\r
414 >        (let ((last-line (line-number-at-pos end))\r
415 >           (max-line (- (line-number-at-pos (point-max)) 2)))\r
416 >       (goto-char beg)\r
417 >       (while (<= (line-number-at-pos) (min last-line max-line))\r
418 > -       (notmuch-search-set-tags (delete tag (notmuch-search-get-tags)))\r
419 > +       (notmuch-search-set-tags\r
420 > +        (notmuch-update-tags (notmuch-search-get-tags) tags))\r
421 >         (forward-line))))))\r
422 >  \r
423 > -(defun notmuch-search-add-tag (tag)\r
424 > -  "Add a tag to the currently selected thread or region.\r
425 > -\r
426 > -The tag is added to all messages in the currently selected thread\r
427 > -or threads in the current region."\r
428 > -  (interactive\r
429 > -   (list (notmuch-select-tag-with-completion "Tag to add: ")))\r
430 > -  (save-excursion\r
431 > -    (if (region-active-p)\r
432 > -     (let* ((beg (region-beginning))\r
433 > -            (end (region-end)))\r
434 > -       (notmuch-search-add-tag-region tag beg end))\r
435 > -      (notmuch-search-add-tag-thread tag))))\r
436 > -\r
437 > -(defun notmuch-search-remove-tag (tag)\r
438 > -  "Remove a tag from the currently selected thread or region.\r
439 > +(defun notmuch-search-tag (&optional initial-input)\r
440 > +  "Change tags for the currently selected thread or region."\r
441 > +  (interactive)\r
442 > +  (let* ((beg (if (region-active-p) (region-beginning) (point)))\r
443 > +      (end (if (region-active-p) (region-end) (point)))\r
444 \r
445 While you're in here, these should probably be `use-region-p'.\r
446 \r
447 > +      (search-string (notmuch-search-find-thread-id-region-search beg end))\r
448 > +      (tags (notmuch-select-tags-with-completion initial-input search-string)))\r
449 > +    (apply 'notmuch-search-tag-region beg end tags)))\r
450 > +\r
451 > +(defun notmuch-search-add-tag ()\r
452 > +  "Same as `notmuch-search-tag' but sets initial input to '+'."\r
453 > +  (interactive)\r
454 > +  (notmuch-search-tag "+"))\r
455 >  \r
456 > -The tag is removed from all messages in the currently selected\r
457 > -thread or threads in the current region."\r
458 > -  (interactive\r
459 > -   (list (notmuch-select-tag-with-completion\r
460 > -       "Tag to remove: "\r
461 > -       (if (region-active-p)\r
462 > -           (mapconcat 'identity\r
463 > -                      (notmuch-search-find-thread-id-region (region-beginning) (region-end))\r
464 > -                      " ")\r
465 > -         (notmuch-search-find-thread-id)))))\r
466 > -  (save-excursion\r
467 > -    (if (region-active-p)\r
468 > -     (let* ((beg (region-beginning))\r
469 > -            (end (region-end)))\r
470 > -       (notmuch-search-remove-tag-region tag beg end))\r
471 > -      (notmuch-search-remove-tag-thread tag))))\r
472 > +(defun notmuch-search-remove-tag ()\r
473 > +  "Same as `notmuch-search-tag' but sets initial input to '-'."\r
474 > +  (interactive)\r
475 > +  (notmuch-search-tag "-"))\r
476 >  \r
477 >  (defun notmuch-search-archive-thread ()\r
478 >    "Archive the currently selected thread (remove its \"inbox\" tag).\r
479 >  \r
480 >  This function advances the next thread when finished."\r
481 >    (interactive)\r
482 > -  (notmuch-search-remove-tag-thread "inbox")\r
483 > +  (notmuch-search-tag-thread "-inbox")\r
484 >    (notmuch-search-next-thread))\r
485 >  \r
486 >  (defvar notmuch-search-process-filter-data nil\r
487 > @@ -893,9 +896,7 @@ will prompt for tags to be added or removed. Tags prefixed with\r
488 >  Each character of the tag name may consist of alphanumeric\r
489 >  characters as well as `_.+-'.\r
490 >  "\r
491 > -  (interactive (notmuch-select-tags-with-completion\r
492 > -             "Operations (+add -drop): notmuch tag "\r
493 > -             '("+" "-")))\r
494 > +  (interactive (notmuch-select-tags-with-completion))\r
495 >    (apply 'notmuch-tag notmuch-search-query-string actions))\r
496 >  \r
497 >  (defun notmuch-search-buffer-title (query)\r