[PATCH 3/5] contrib: add notmuch-pick.el file itself
[notmuch-archives.git] / cc / fbd5631266d0cae2869a94d332651e72e39fc7
1 Return-Path: <markwalters1009@gmail.com>\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 9D131431FAF\r
6         for <notmuch@notmuchmail.org>; Tue, 24 Jul 2012 14:22:17 -0700 (PDT)\r
7 X-Virus-Scanned: Debian amavisd-new at olra.theworths.org\r
8 X-Spam-Flag: NO\r
9 X-Spam-Score: 0.201\r
10 X-Spam-Level: \r
11 X-Spam-Status: No, score=0.201 tagged_above=-999 required=5\r
12         tests=[DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1,\r
13         FREEMAIL_ENVFROM_END_DIGIT=1, FREEMAIL_FROM=0.001,\r
14         RCVD_IN_DNSWL_LOW=-0.7] autolearn=disabled\r
15 Received: from olra.theworths.org ([127.0.0.1])\r
16         by localhost (olra.theworths.org [127.0.0.1]) (amavisd-new, port 10024)\r
17         with ESMTP id UcKtctcjcjZv for <notmuch@notmuchmail.org>;\r
18         Tue, 24 Jul 2012 14:22:09 -0700 (PDT)\r
19 Received: from mail-wg0-f45.google.com (mail-wg0-f45.google.com\r
20  [74.125.82.45])        (using TLSv1 with cipher RC4-SHA (128/128 bits))        (No client\r
21  certificate requested) by olra.theworths.org (Postfix) with ESMTPS id\r
22  7288D431FD2    for <notmuch@notmuchmail.org>; Tue, 24 Jul 2012 14:22:06 -0700\r
23  (PDT)\r
24 Received: by wgbdt14 with SMTP id dt14so24284wgb.2\r
25         for <notmuch@notmuchmail.org>; Tue, 24 Jul 2012 14:22:05 -0700 (PDT)\r
26 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113;\r
27         h=from:to:cc:subject:date:message-id:x-mailer:in-reply-to:references\r
28         :mime-version:content-type:content-transfer-encoding;\r
29         bh=C2ekRFqLNylYCyr20CBKvn0cQOdyz3CxirE2LcObEWM=;\r
30         b=kCxLcxbWq75mGurnT78XShz25BTMukcXR4/0oG0kaOlq88XRK7g+9zbi0nnpe621/N\r
31         f9A+ENLe3i5vF4bi0N+An81BXzoF4zne/uWvoSG6WUexe2t6fGZ1DSVOKs1TrWVRh5Qs\r
32         j6wjUAKZc2fa32c9trqUCr/dPI5ZhFV5Zolfp9C4ewth0NXvsavZMB9iugl/clM/DhPN\r
33         NEkPFGzji4uNUsUzJopSPW42qFe3FmNohR7aFPVdyUMYaLR/CUSV62nTbbXWUDJesebt\r
34         CUETx7iAUX4zR270kA3Q+DHPzETBb7mC+k447avb1LXwNipcPjXWwq2leTBxd636/SHJ\r
35         i9gg==\r
36 Received: by 10.180.103.4 with SMTP id fs4mr10433029wib.16.1343164925218;\r
37         Tue, 24 Jul 2012 14:22:05 -0700 (PDT)\r
38 Received: from localhost (94-192-233-223.zone6.bethere.co.uk.\r
39  [94.192.233.223])      by mx.google.com with ESMTPS id\r
40  o2sm9664507wiz.11.2012.07.24.14.22.03  (version=TLSv1/SSLv3 cipher=OTHER);\r
41         Tue, 24 Jul 2012 14:22:04 -0700 (PDT)\r
42 From: Mark Walters <markwalters1009@gmail.com>\r
43 To: notmuch@notmuchmail.org\r
44 Subject: [PATCH 3/5] contrib: add notmuch-pick.el file itself\r
45 Date: Tue, 24 Jul 2012 22:21:49 +0100\r
46 Message-Id: <1343164911-31589-4-git-send-email-markwalters1009@gmail.com>\r
47 X-Mailer: git-send-email 1.7.9.1\r
48 In-Reply-To: <1343164911-31589-1-git-send-email-markwalters1009@gmail.com>\r
49 References: <1343164911-31589-1-git-send-email-markwalters1009@gmail.com>\r
50 MIME-Version: 1.0\r
51 Content-Type: text/plain; charset=UTF-8\r
52 Content-Transfer-Encoding: 8bit\r
53 X-BeenThere: notmuch@notmuchmail.org\r
54 X-Mailman-Version: 2.1.13\r
55 Precedence: list\r
56 List-Id: "Use and development of the notmuch mail system."\r
57         <notmuch.notmuchmail.org>\r
58 List-Unsubscribe: <http://notmuchmail.org/mailman/options/notmuch>,\r
59         <mailto:notmuch-request@notmuchmail.org?subject=unsubscribe>\r
60 List-Archive: <http://notmuchmail.org/pipermail/notmuch>\r
61 List-Post: <mailto:notmuch@notmuchmail.org>\r
62 List-Help: <mailto:notmuch-request@notmuchmail.org?subject=help>\r
63 List-Subscribe: <http://notmuchmail.org/mailman/listinfo/notmuch>,\r
64         <mailto:notmuch-request@notmuchmail.org?subject=subscribe>\r
65 X-List-Received-Date: Tue, 24 Jul 2012 21:22:17 -0000\r
66 \r
67 This adds the main notmuch-pick.el file.\r
68 ---\r
69  contrib/notmuch-pick/notmuch-pick.el |  876 ++++++++++++++++++++++++++++++++++\r
70  1 files changed, 876 insertions(+), 0 deletions(-)\r
71  create mode 100644 contrib/notmuch-pick/notmuch-pick.el\r
72 \r
73 diff --git a/contrib/notmuch-pick/notmuch-pick.el b/contrib/notmuch-pick/notmuch-pick.el\r
74 new file mode 100644\r
75 index 0000000..ded0c42\r
76 --- /dev/null\r
77 +++ b/contrib/notmuch-pick/notmuch-pick.el\r
78 @@ -0,0 +1,876 @@\r
79 +;; notmuch-pick.el --- displaying notmuch forests.\r
80 +;;\r
81 +;; Copyright © Carl Worth\r
82 +;; Copyright © David Edmondson\r
83 +;; Copyright © Mark Walters\r
84 +;;\r
85 +;; This file is part of Notmuch.\r
86 +;;\r
87 +;; Notmuch is free software: you can redistribute it and/or modify it\r
88 +;; under the terms of the GNU General Public License as published by\r
89 +;; the Free Software Foundation, either version 3 of the License, or\r
90 +;; (at your option) any later version.\r
91 +;;\r
92 +;; Notmuch is distributed in the hope that it will be useful, but\r
93 +;; WITHOUT ANY WARRANTY; without even the implied warranty of\r
94 +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\r
95 +;; General Public License for more details.\r
96 +;;\r
97 +;; You should have received a copy of the GNU General Public License\r
98 +;; along with Notmuch.  If not, see <http://www.gnu.org/licenses/>.\r
99 +;;\r
100 +;; Authors: David Edmondson <dme@dme.org>\r
101 +;;          Mark Walters <markwalters1009@gmail.com>\r
102 +\r
103 +(require 'mail-parse)\r
104 +\r
105 +(require 'notmuch-lib)\r
106 +(require 'notmuch-query)\r
107 +(require 'notmuch-show)\r
108 +(eval-when-compile (require 'cl))\r
109 +\r
110 +(declare-function notmuch-call-notmuch-process "notmuch" (&rest args))\r
111 +(declare-function notmuch-show "notmuch-show" (&rest args))\r
112 +(declare-function notmuch-tag "notmuch" (query &rest tags))\r
113 +(declare-function notmuch-show-strip-re "notmuch-show" (subject))\r
114 +(declare-function notmuch-show-clean-address "notmuch-show" (parsed-address))\r
115 +(declare-function notmuch-show-spaces-n "notmuch-show" (n))\r
116 +(declare-function notmuch-read-query "notmuch" (prompt))\r
117 +(declare-function notmuch-read-tag-changes "notmuch" (&optional initial-input &rest search-terms))\r
118 +(declare-function notmuch-update-tags "notmuch" (current-tags tag-changes))\r
119 +(declare-function notmuch-hello-trim "notmuch-hello" (search))\r
120 +(declare-function notmuch-search-find-thread-id "notmuch" ())\r
121 +(declare-function notmuch-search-find-subject "notmuch" ())\r
122 +\r
123 +;; the following variable is defined in notmuch.el\r
124 +(defvar notmuch-search-query-string)\r
125 +\r
126 +(defvar notmuch-pick-process-state nil\r
127 +  "Parsing state of the search process filter.")\r
128 +\r
129 +(defgroup notmuch-pick nil\r
130 +  "Showing message and thread structure."\r
131 +  :group 'notmuch)\r
132 +\r
133 +;; This is ugly. We can't run setup-show-out until it has been defined\r
134 +;; which needs the keymap to be defined. So we defer setting up to\r
135 +;; notmuch-pick-init.\r
136 +(defcustom notmuch-pick-show-out nil\r
137 +  "View selected messages in new window rather than split-pane."\r
138 +  :type 'boolean\r
139 +  :group 'notmuch-pick\r
140 +  :set (lambda (symbol value)\r
141 +        (set-default symbol value)\r
142 +        (when (fboundp 'notmuch-pick-setup-show-out)\r
143 +          (notmuch-pick-setup-show-out))))\r
144 +\r
145 +(defcustom notmuch-pick-result-format\r
146 +  `(("date" . "%12s ")\r
147 +    ("authors" . "%21s ")\r
148 +    ("subject" . "%-54s ")\r
149 +    ("tags" . "(%s)"))\r
150 +  "Result formatting for Pick. Supported fields are:\r
151 +        date, authors, subject, tags\r
152 +Note subject includes the tree structure graphics.\r
153 +For example:\r
154 +        (setq notmuch-pick-result-format \(\(\"authors\" . \"%-40s\"\)\r
155 +                                             \(\"subject\" . \"%s\"\)\)\)"\r
156 +  :type '(alist :key-type (string) :value-type (string))\r
157 +  :group 'notmuch-pick)\r
158 +\r
159 +(defcustom notmuch-pick-asynchronous-parser t\r
160 +  "Use the asynchronous parser."\r
161 +  :type 'boolean\r
162 +  :group 'notmuch-pick)\r
163 +\r
164 +;; Faces for messages that match the query.\r
165 +(defface notmuch-pick-match-date-face\r
166 +  '((t :inherit default))\r
167 +  "Face used in pick mode for the date in messages matching the query."\r
168 +  :group 'notmuch-pick\r
169 +  :group 'notmuch-faces)\r
170 +\r
171 +(defface notmuch-pick-match-author-face\r
172 +  '((((class color)\r
173 +      (background dark))\r
174 +     (:foreground "OliveDrab1"))\r
175 +    (((class color)\r
176 +      (background light))\r
177 +     (:foreground "dark blue"))\r
178 +    (t\r
179 +     (:bold t)))\r
180 +  "Face used in pick mode for the date in messages matching the query."\r
181 +  :group 'notmuch-pick\r
182 +  :group 'notmuch-faces)\r
183 +\r
184 +(defface notmuch-pick-match-subject-face\r
185 +  '((t :inherit default))\r
186 +  "Face used in pick mode for the subject in messages matching the query."\r
187 +  :group 'notmuch-pick\r
188 +  :group 'notmuch-faces)\r
189 +\r
190 +(defface notmuch-pick-match-tag-face\r
191 +  '((((class color)\r
192 +      (background dark))\r
193 +     (:foreground "OliveDrab1"))\r
194 +    (((class color)\r
195 +      (background light))\r
196 +     (:foreground "navy blue" :bold t))\r
197 +    (t\r
198 +     (:bold t)))\r
199 +  "Face used in pick mode for tags in messages matching the query."\r
200 +  :group 'notmuch-pick\r
201 +  :group 'notmuch-faces)\r
202 +\r
203 +;; Faces for messages that do not match the query.\r
204 +(defface notmuch-pick-no-match-date-face\r
205 +  '((t (:foreground "gray")))\r
206 +  "Face used in pick mode for non-matching dates."\r
207 +  :group 'notmuch-pick\r
208 +  :group 'notmuch-faces)\r
209 +\r
210 +(defface notmuch-pick-no-match-subject-face\r
211 +  '((t (:foreground "gray")))\r
212 +  "Face used in pick mode for non-matching subjects."\r
213 +  :group 'notmuch-pick\r
214 +  :group 'notmuch-faces)\r
215 +\r
216 +(defface notmuch-pick-no-match-author-face\r
217 +  '((t (:foreground "gray")))\r
218 +  "Face used in pick mode for the date in messages matching the query."\r
219 +  :group 'notmuch-pick\r
220 +  :group 'notmuch-faces)\r
221 +\r
222 +(defface notmuch-pick-no-match-tag-face\r
223 +  '((t (:foreground "gray")))\r
224 +  "Face used in pick mode face for non-matching tags."\r
225 +  :group 'notmuch-pick\r
226 +  :group 'notmuch-faces)\r
227 +\r
228 +;; should this be done as a variable?\r
229 +(defvar notmuch-pick-previous-subject "")\r
230 +(make-variable-buffer-local 'notmuch-pick-previous-subject)\r
231 +\r
232 +;; The basic query i.e. the key part of the search request.\r
233 +(defvar notmuch-pick-basic-query nil)\r
234 +(make-variable-buffer-local 'notmuch-pick-basic-query)\r
235 +;; The context of the search: i.e., useful but can be dropped.\r
236 +(defvar notmuch-pick-query-context nil)\r
237 +(make-variable-buffer-local 'notmuch-pick-query-context)\r
238 +(defvar notmuch-pick-buffer-name nil)\r
239 +(make-variable-buffer-local 'notmuch-pick-buffer-name)\r
240 +(defvar notmuch-pick-message-window nil)\r
241 +(make-variable-buffer-local 'notmuch-pick-message-window)\r
242 +(put 'notmuch-pick-message-window 'permanent-local t)\r
243 +(defvar notmuch-pick-message-buffer nil)\r
244 +(make-variable-buffer-local 'notmuch-pick-message-buffer-name)\r
245 +(put 'notmuch-pick-message-buffer-name 'permanent-local t)\r
246 +\r
247 +(defvar notmuch-pick-mode-map\r
248 +  (let ((map (make-sparse-keymap)))\r
249 +    (define-key map [mouse-1] 'notmuch-pick-show-message)\r
250 +    (define-key map "q" 'notmuch-pick-quit)\r
251 +    (define-key map "x" 'notmuch-pick-quit)\r
252 +    (define-key map "?" 'notmuch-help)\r
253 +    (define-key map "a" 'notmuch-pick-archive-message)\r
254 +    (define-key map "=" 'notmuch-pick-refresh-view)\r
255 +    (define-key map "s" 'notmuch-search)\r
256 +    (define-key map "z" 'notmuch-pick)\r
257 +    (define-key map "m" 'notmuch-pick-new-mail)\r
258 +    (define-key map "f" 'notmuch-pick-forward-message)\r
259 +    (define-key map "r" 'notmuch-pick-reply-sender)\r
260 +    (define-key map "R" 'notmuch-pick-reply)\r
261 +    (define-key map "n" 'notmuch-pick-next-matching-message)\r
262 +    (define-key map "p" 'notmuch-pick-prev-matching-message)\r
263 +    (define-key map "N" 'notmuch-pick-next-message)\r
264 +    (define-key map "P" 'notmuch-pick-prev-message)\r
265 +    (define-key map "|" 'notmuch-pick-pipe-message)\r
266 +    (define-key map "-" 'notmuch-pick-remove-tag)\r
267 +    (define-key map "+" 'notmuch-pick-add-tag)\r
268 +    (define-key map " " 'notmuch-pick-scroll-or-next)\r
269 +    (define-key map "b" 'notmuch-pick-scroll-message-window-back)\r
270 +    map))\r
271 +(fset 'notmuch-pick-mode-map notmuch-pick-mode-map)\r
272 +\r
273 +(defun notmuch-pick-setup-show-out ()\r
274 +  (let ((map notmuch-pick-mode-map))\r
275 +    (if notmuch-pick-show-out\r
276 +       (progn\r
277 +         (define-key map (kbd "M-RET") 'notmuch-pick-show-message)\r
278 +         (define-key map (kbd "RET") 'notmuch-pick-show-message-out))\r
279 +      (progn\r
280 +       (define-key map (kbd "RET") 'notmuch-pick-show-message)\r
281 +       (define-key map (kbd "M-RET") 'notmuch-pick-show-message-out)))))\r
282 +\r
283 +(defun notmuch-pick-get-message-properties ()\r
284 +  "Return the properties of the current message as a plist.\r
285 +\r
286 +Some useful entries are:\r
287 +:headers - Property list containing the headers :Date, :Subject, :From, etc.\r
288 +:tags - Tags for this message"\r
289 +  (save-excursion\r
290 +    (beginning-of-line)\r
291 +    (get-text-property (point) :notmuch-message-properties)))\r
292 +\r
293 +(defun notmuch-pick-set-message-properties (props)\r
294 +  (save-excursion\r
295 +    (beginning-of-line)\r
296 +    (put-text-property (point) (+ (point) 1) :notmuch-message-properties props)))\r
297 +\r
298 +(defun notmuch-pick-set-prop (prop val &optional props)\r
299 +  (let ((inhibit-read-only t)\r
300 +       (props (or props\r
301 +                  (notmuch-pick-get-message-properties))))\r
302 +    (plist-put props prop val)\r
303 +    (notmuch-pick-set-message-properties props)))\r
304 +\r
305 +(defun notmuch-pick-get-prop (prop &optional props)\r
306 +  (let ((props (or props\r
307 +                  (notmuch-pick-get-message-properties))))\r
308 +    (plist-get props prop)))\r
309 +\r
310 +(defun notmuch-pick-set-tags (tags)\r
311 +  "Set the tags of the current message."\r
312 +  (notmuch-pick-set-prop :tags tags))\r
313 +\r
314 +(defun notmuch-pick-get-tags ()\r
315 +  "Return the tags of the current message."\r
316 +  (notmuch-pick-get-prop :tags))\r
317 +\r
318 +(defun notmuch-pick-refresh-result ()\r
319 +  (let ((init-point (point))\r
320 +       (end (line-end-position))\r
321 +       (msg (notmuch-pick-get-message-properties))\r
322 +       (inhibit-read-only t))\r
323 +    (beginning-of-line)\r
324 +    (delete-region (point) (1+ (line-end-position)))\r
325 +    (notmuch-pick-insert-msg msg)\r
326 +    (let ((new-end (line-end-position)))\r
327 +      (goto-char (if (= init-point end)\r
328 +                    new-end\r
329 +                  (min init-point (- new-end 1)))))))\r
330 +\r
331 +(defun notmuch-pick-tag-message (&rest tag-changes)\r
332 +  "Change tags for the current message.\r
333 +\r
334 +TAG-CHANGES is a list of tag operations for `notmuch-tag'."\r
335 +  (let* ((current-tags (notmuch-pick-get-tags))\r
336 +        (new-tags (notmuch-update-tags current-tags tag-changes)))\r
337 +    (unless (equal current-tags new-tags)\r
338 +      (funcall 'notmuch-tag (notmuch-pick-get-message-id) tag-changes)\r
339 +      (notmuch-pick-set-tags new-tags)\r
340 +      (notmuch-pick-refresh-result))))\r
341 +\r
342 +(defun notmuch-pick-tag (&optional initial-input)\r
343 +  "Change tags for the current message, read input from the minibuffer."\r
344 +  (interactive)\r
345 +  (let ((tag-changes (notmuch-read-tag-changes\r
346 +                     initial-input (notmuch-pick-get-message-id))))\r
347 +    (apply 'notmuch-pick-tag-message tag-changes)))\r
348 +\r
349 +(defun notmuch-pick-add-tag ()\r
350 +  "Same as `notmuch-pick-tag' but sets initial input to '+'."\r
351 +  (interactive)\r
352 +  (notmuch-pick-tag "+"))\r
353 +\r
354 +(defun notmuch-pick-remove-tag ()\r
355 +  "Same as `notmuch-pick-tag' but sets initial input to '-'."\r
356 +  (interactive)\r
357 +  (notmuch-pick-tag "-"))\r
358 +\r
359 +(defun notmuch-pick-get-message-id ()\r
360 +  "Return the message id of the current message."\r
361 +  (concat "id:\"" (notmuch-pick-get-prop :id) "\""))\r
362 +\r
363 +(defun notmuch-pick-get-match ()\r
364 +  "Return whether the current message is a match."\r
365 +  (interactive)\r
366 +  (notmuch-pick-get-prop :match))\r
367 +\r
368 +;; This function should be in notmuch-hello.el but we are trying to\r
369 +;; minimise impact on the rest of the codebase.\r
370 +(defun notmuch-pick-from-hello (&optional search)\r
371 +  "Run a query and display results in experimental notmuch-pick mode"\r
372 +  (interactive)\r
373 +  (unless (null search)\r
374 +    (setq search (notmuch-hello-trim search))\r
375 +    (let ((history-delete-duplicates t))\r
376 +      (add-to-history 'notmuch-search-history search)))\r
377 +  (notmuch-pick search))\r
378 +\r
379 +;; This function should be in notmuch-show.el but be we trying to\r
380 +;; minimise impact on the rest of the codebase.\r
381 +(defun notmuch-pick-from-show-current-query ()\r
382 +  "Call notmuch pick with the current query"\r
383 +  (interactive)\r
384 +  (notmuch-pick notmuch-show-thread-id notmuch-show-query-context))\r
385 +\r
386 +;; This function should be in notmuch.el but be we trying to minimise\r
387 +;; impact on the rest of the codebase.\r
388 +(defun notmuch-pick-from-search-current-query ()\r
389 +  "Call notmuch pick with the current query"\r
390 +  (interactive)\r
391 +  (notmuch-pick notmuch-search-query-string))\r
392 +\r
393 +;; This function should be in notmuch.el but be we trying to minimise\r
394 +;; impact on the rest of the codebase.\r
395 +(defun notmuch-pick-from-search-thread ()\r
396 +  "Show the selected thread with notmuch-pick"\r
397 +  (interactive)\r
398 +  (notmuch-pick (notmuch-search-find-thread-id)\r
399 +                notmuch-search-query-string\r
400 +                (notmuch-prettify-subject (notmuch-search-find-subject)))\r
401 +  (notmuch-pick-show-match-message-with-wait))\r
402 +\r
403 +(defun notmuch-pick-show-message ()\r
404 +  "Show the current message (in split-pane)."\r
405 +  (interactive)\r
406 +  (let ((id (notmuch-pick-get-message-id))\r
407 +       (inhibit-read-only t)\r
408 +       buffer)\r
409 +    (when id\r
410 +      ;; We close and reopen the window to kill off un-needed buffers\r
411 +      ;; this might cause flickering but seems ok.\r
412 +      (notmuch-pick-close-message-window)\r
413 +      (setq notmuch-pick-message-window\r
414 +           (split-window-vertically (/ (window-height) 4)))\r
415 +      (with-selected-window notmuch-pick-message-window\r
416 +       (setq current-prefix-arg '(4))\r
417 +       (setq buffer (notmuch-show id nil nil nil))))\r
418 +    (setq notmuch-pick-message-buffer buffer)))\r
419 +\r
420 +(defun notmuch-pick-show-message-out ()\r
421 +  "Show the current message (in whole window)."\r
422 +  (interactive)\r
423 +  (let ((id (notmuch-pick-get-message-id))\r
424 +       (inhibit-read-only t)\r
425 +       buffer)\r
426 +    (when id\r
427 +      ;; We close the window to kill off un-needed buffers.\r
428 +      (notmuch-pick-close-message-window)\r
429 +      (notmuch-show id nil nil nil))))\r
430 +\r
431 +(defun notmuch-pick-scroll-message-window ()\r
432 +  "Scroll the message window (if it exists)"\r
433 +  (interactive)\r
434 +  (when (window-live-p notmuch-pick-message-window)\r
435 +    (with-selected-window notmuch-pick-message-window\r
436 +      (if (pos-visible-in-window-p (point-max))\r
437 +         t\r
438 +       (scroll-up)))))\r
439 +\r
440 +(defun notmuch-pick-scroll-message-window-back ()\r
441 +  "Scroll the message window back(if it exists)"\r
442 +  (interactive)\r
443 +  (when (window-live-p notmuch-pick-message-window)\r
444 +    (with-selected-window notmuch-pick-message-window\r
445 +      (if (pos-visible-in-window-p (point-min))\r
446 +         t\r
447 +       (scroll-down)))))\r
448 +\r
449 +(defun notmuch-pick-scroll-or-next ()\r
450 +  "Scroll the message window. If it at end go to next message."\r
451 +  (interactive)\r
452 +  (when (notmuch-pick-scroll-message-window)\r
453 +    (notmuch-pick-next-matching-message)))\r
454 +\r
455 +(defun notmuch-pick-quit ()\r
456 +  "Close the split view or exit pick."\r
457 +  (interactive)\r
458 +  (unless (notmuch-pick-close-message-window)\r
459 +    (kill-buffer (current-buffer))))\r
460 +\r
461 +(defun notmuch-pick-close-message-window ()\r
462 +  "Close the message-window. Return t if close succeeds."\r
463 +  (interactive)\r
464 +  (when (window-live-p notmuch-pick-message-window)\r
465 +    (delete-window notmuch-pick-message-window)\r
466 +    (unless (get-buffer-window-list notmuch-pick-message-buffer)\r
467 +      (kill-buffer notmuch-pick-message-buffer))\r
468 +    t))\r
469 +\r
470 +(defun notmuch-pick-archive-message ()\r
471 +  "Archive the current message and move to next matching message."\r
472 +  (interactive)\r
473 +  (notmuch-pick-tag-message "-inbox")\r
474 +  (notmuch-pick-next-matching-message))\r
475 +\r
476 +(defun notmuch-pick-next-message ()\r
477 +  "Move to next message."\r
478 +  (interactive)\r
479 +  (forward-line)\r
480 +  (when (window-live-p notmuch-pick-message-window)\r
481 +    (notmuch-pick-show-message)))\r
482 +\r
483 +(defun notmuch-pick-prev-message ()\r
484 +  "Move to previous message."\r
485 +  (interactive)\r
486 +  (forward-line -1)\r
487 +  (when (window-live-p notmuch-pick-message-window)\r
488 +    (notmuch-pick-show-message)))\r
489 +\r
490 +(defun notmuch-pick-prev-matching-message ()\r
491 +  "Move to previous matching message."\r
492 +  (interactive)\r
493 +  (forward-line -1)\r
494 +  (while (and (not (bobp)) (not (notmuch-pick-get-match)))\r
495 +    (forward-line -1))\r
496 +  (when (window-live-p notmuch-pick-message-window)\r
497 +    (notmuch-pick-show-message)))\r
498 +\r
499 +(defun notmuch-pick-next-matching-message ()\r
500 +  "Move to next matching message."\r
501 +  (interactive)\r
502 +  (forward-line)\r
503 +  (while (and (not (eobp)) (not (notmuch-pick-get-match)))\r
504 +    (forward-line))\r
505 +  (when (window-live-p notmuch-pick-message-window)\r
506 +    (notmuch-pick-show-message)))\r
507 +\r
508 +(defun notmuch-pick-show-match-message-with-wait ()\r
509 +  "Show the first matching message but wait for it to appear or search to finish."\r
510 +  (interactive)\r
511 +  (unless (notmuch-pick-get-match)\r
512 +    (notmuch-pick-next-matching-message))\r
513 +  (while (and (not (notmuch-pick-get-match))\r
514 +             (not (eq notmuch-pick-process-state 'end)))\r
515 +    (message "waiting for message")\r
516 +    (sit-for 0.1)\r
517 +    (goto-char (point-min))\r
518 +    (unless (notmuch-pick-get-match)\r
519 +      (notmuch-pick-next-matching-message)))\r
520 +  (message nil)\r
521 +  (when (notmuch-pick-get-match)\r
522 +    (notmuch-pick-show-message)))\r
523 +\r
524 +(defun notmuch-pick-refresh-view ()\r
525 +  "Refresh view."\r
526 +  (interactive)\r
527 +  (let ((inhibit-read-only t)\r
528 +       (basic-query notmuch-pick-basic-query)\r
529 +       (query-context notmuch-pick-query-context)\r
530 +       (buffer-name notmuch-pick-buffer-name))\r
531 +    (erase-buffer)\r
532 +    (notmuch-pick-worker basic-query query-context (get-buffer buffer-name))))\r
533 +\r
534 +(defun notmuch-pick-string-width (string width &optional right)\r
535 +  (let ((s (format (format "%%%s%ds" (if right "" "-") width)\r
536 +                  string)))\r
537 +    (if (> (length s) width)\r
538 +       (substring s 0 width)\r
539 +      s)))\r
540 +\r
541 +(defmacro with-current-notmuch-pick-message (&rest body)\r
542 +  "Evaluate body with current buffer set to the text of current message"\r
543 +  `(save-excursion\r
544 +     (let ((id (notmuch-pick-get-message-id)))\r
545 +       (let ((buf (generate-new-buffer (concat "*notmuch-msg-" id "*"))))\r
546 +         (with-current-buffer buf\r
547 +           (call-process notmuch-command nil t nil "show" "--format=raw" id)\r
548 +           ,@body)\r
549 +        (kill-buffer buf)))))\r
550 +\r
551 +(defun notmuch-pick-new-mail (&optional prompt-for-sender)\r
552 +  "Compose new mail."\r
553 +  (interactive "P")\r
554 +  (notmuch-pick-close-message-window)\r
555 +  (notmuch-mua-new-mail prompt-for-sender ))\r
556 +\r
557 +(defun notmuch-pick-forward-message (&optional prompt-for-sender)\r
558 +  "Forward the current message."\r
559 +  (interactive "P")\r
560 +  (notmuch-pick-close-message-window)\r
561 +  (with-current-notmuch-pick-message\r
562 +   (notmuch-mua-new-forward-message prompt-for-sender)))\r
563 +\r
564 +(defun notmuch-pick-reply (&optional prompt-for-sender)\r
565 +  "Reply to the sender and all recipients of the current message."\r
566 +  (interactive "P")\r
567 +  (notmuch-pick-close-message-window)\r
568 +  (notmuch-mua-new-reply (notmuch-pick-get-message-id) prompt-for-sender t))\r
569 +\r
570 +(defun notmuch-pick-reply-sender (&optional prompt-for-sender)\r
571 +  "Reply to the sender of the current message."\r
572 +  (interactive "P")\r
573 +  (notmuch-pick-close-message-window)\r
574 +  (notmuch-mua-new-reply (notmuch-pick-get-message-id) prompt-for-sender nil))\r
575 +\r
576 +;; Shamelessly stolen from notmuch-show.el: maybe should be unified.\r
577 +(defun notmuch-pick-pipe-message (command)\r
578 +  "Pipe the contents of the current message to the given command.\r
579 +\r
580 +The given command will be executed with the raw contents of the\r
581 +current email message as stdin. Anything printed by the command\r
582 +to stdout or stderr will appear in the *notmuch-pipe* buffer.\r
583 +\r
584 +When invoked with a prefix argument, the command will receive all\r
585 +open messages in the current thread (formatted as an mbox) rather\r
586 +than only the current message."\r
587 +  (interactive "sPipe message to command: ")\r
588 +  (let ((shell-command\r
589 +        (concat notmuch-command " show --format=raw "\r
590 +                (shell-quote-argument (notmuch-pick-get-message-id)) " | " command))\r
591 +        (buf (get-buffer-create (concat "*notmuch-pipe*"))))\r
592 +    (with-current-buffer buf\r
593 +      (setq buffer-read-only nil)\r
594 +      (erase-buffer)\r
595 +      (let ((exit-code (call-process-shell-command shell-command nil buf)))\r
596 +       (goto-char (point-max))\r
597 +       (set-buffer-modified-p nil)\r
598 +       (setq buffer-read-only t)\r
599 +       (unless (zerop exit-code)\r
600 +         (switch-to-buffer-other-window buf)\r
601 +         (message (format "Command '%s' exited abnormally with code %d"\r
602 +                          shell-command exit-code)))))))\r
603 +\r
604 +;; Shamelessly stolen from notmuch-show.el: should be unified.\r
605 +(defun notmuch-pick-clean-address (address)\r
606 +  "Try to clean a single email ADDRESS for display.  Return\r
607 +unchanged ADDRESS if parsing fails."\r
608 +  (condition-case nil\r
609 +    (let (p-name p-address)\r
610 +      ;; It would be convenient to use `mail-header-parse-address',\r
611 +      ;; but that expects un-decoded mailbox parts, whereas our\r
612 +      ;; mailbox parts are already decoded (and hence may contain\r
613 +      ;; UTF-8). Given that notmuch should handle most of the awkward\r
614 +      ;; cases, some simple string deconstruction should be sufficient\r
615 +      ;; here.\r
616 +      (cond\r
617 +       ;; "User <user@dom.ain>" style.\r
618 +       ((string-match "\\(.*\\) <\\(.*\\)>" address)\r
619 +       (setq p-name (match-string 1 address)\r
620 +             p-address (match-string 2 address)))\r
621 +\r
622 +       ;; "<user@dom.ain>" style.\r
623 +       ((string-match "<\\(.*\\)>" address)\r
624 +       (setq p-address (match-string 1 address)))\r
625 +\r
626 +       ;; Everything else.\r
627 +       (t\r
628 +       (setq p-address address)))\r
629 +\r
630 +      (when p-name\r
631 +       ;; Remove elements of the mailbox part that are not relevant for\r
632 +       ;; display, even if they are required during transport:\r
633 +       ;;\r
634 +       ;; Backslashes.\r
635 +       (setq p-name (replace-regexp-in-string "\\\\" "" p-name))\r
636 +\r
637 +       ;; Outer single and double quotes, which might be nested.\r
638 +       (loop\r
639 +        with start-of-loop\r
640 +        do (setq start-of-loop p-name)\r
641 +\r
642 +        when (string-match "^\"\\(.*\\)\"$" p-name)\r
643 +        do (setq p-name (match-string 1 p-name))\r
644 +\r
645 +        when (string-match "^'\\(.*\\)'$" p-name)\r
646 +        do (setq p-name (match-string 1 p-name))\r
647 +\r
648 +        until (string= start-of-loop p-name)))\r
649 +\r
650 +      ;; If the address is 'foo@bar.com <foo@bar.com>' then show just\r
651 +      ;; 'foo@bar.com'.\r
652 +      (when (string= p-name p-address)\r
653 +       (setq p-name nil))\r
654 +\r
655 +      ;; If we have a name return that otherwise return the address.\r
656 +      (if (not p-name)\r
657 +         p-address\r
658 +       p-name))\r
659 +    (error address)))\r
660 +\r
661 +(defun notmuch-pick-insert-field (field format-string msg)\r
662 +  (let* ((headers (plist-get msg :headers))\r
663 +       (match (plist-get msg :match)))\r
664 +    (cond\r
665 +     ((string-equal field "date")\r
666 +      (let ((face (if match\r
667 +                     'notmuch-pick-match-date-face\r
668 +                   'notmuch-pick-no-match-date-face)))\r
669 +       (insert (propertize (format format-string (plist-get msg :date_relative))\r
670 +                           'face face))))\r
671 +\r
672 +     ((string-equal field "subject")\r
673 +      (let ((tree-status (plist-get msg :tree-status))\r
674 +           (bare-subject (notmuch-show-strip-re (plist-get headers :Subject)))\r
675 +           (face (if match\r
676 +                     'notmuch-pick-match-subject-face\r
677 +                   'notmuch-pick-no-match-subject-face)))\r
678 +       (insert (propertize (format format-string\r
679 +                                   (concat\r
680 +                                    (mapconcat #'identity (reverse tree-status) "")\r
681 +                                    (if (string= notmuch-pick-previous-subject bare-subject)\r
682 +                                        " ..."\r
683 +                                      bare-subject)))\r
684 +                           'face face))\r
685 +       (setq notmuch-pick-previous-subject bare-subject)))\r
686 +\r
687 +     ((string-equal field "authors")\r
688 +      (let ((author (notmuch-pick-clean-address (plist-get headers :From)))\r
689 +           (len (length (format format-string "")))\r
690 +           (face (if match\r
691 +                     'notmuch-pick-match-author-face\r
692 +                   'notmuch-pick-no-match-author-face)))\r
693 +      (insert (propertize (format format-string\r
694 +                                 (notmuch-pick-string-width author (- len 2)))\r
695 +                         'face face))))\r
696 +\r
697 +     ((string-equal field "tags")\r
698 +      (let ((tags (plist-get msg :tags))\r
699 +           (face (if match\r
700 +                         'notmuch-pick-match-tag-face\r
701 +                       'notmuch-pick-no-match-tag-face)))\r
702 +       (when tags\r
703 +         (insert (propertize (format format-string\r
704 +                                     (mapconcat #'identity tags ", "))\r
705 +                             'face face))))))))\r
706 +\r
707 +\r
708 +(defun notmuch-pick-insert-msg (msg)\r
709 +  "Insert the message MSG according to notmuch-pick-result-format"\r
710 +  (dolist (spec notmuch-pick-result-format)\r
711 +    (notmuch-pick-insert-field (car spec) (cdr spec) msg))\r
712 +  (notmuch-pick-set-message-properties msg)\r
713 +  (insert "\n"))\r
714 +\r
715 +\r
716 +(defun notmuch-pick-insert-tree (tree depth tree-status first last)\r
717 +  "Insert the message tree TREE at depth DEPTH in the current thread."\r
718 +  (let ((msg (car tree))\r
719 +       (replies (cadr tree)))\r
720 +\r
721 +      (cond\r
722 +       ((and (< 0 depth) (not last))\r
723 +       (push "├" tree-status))\r
724 +       ((and (< 0 depth) last)\r
725 +       (push "╰" tree-status))\r
726 +       ((and (eq 0 depth) first last)\r
727 +;;       (push "─" tree-status)) choice between this and next line is matter of taste.\r
728 +       (push " " tree-status))\r
729 +       ((and (eq 0 depth) first (not last))\r
730 +         (push "┬" tree-status))\r
731 +       ((and (eq 0 depth) (not first) last)\r
732 +       (push "╰" tree-status))\r
733 +       ((and (eq 0 depth) (not first) (not last))\r
734 +       (push "├" tree-status)))\r
735 +\r
736 +      (push (concat (if replies "┬" "─") "►") tree-status)\r
737 +      (notmuch-pick-insert-msg (plist-put msg :tree-status tree-status))\r
738 +      (pop tree-status)\r
739 +      (pop tree-status)\r
740 +\r
741 +      (if last\r
742 +         (push " " tree-status)\r
743 +       (push "│" tree-status))\r
744 +\r
745 +    (notmuch-pick-insert-thread replies (1+ depth) tree-status)))\r
746 +\r
747 +(defun notmuch-pick-insert-thread (thread depth tree-status)\r
748 +  "Insert the thread THREAD at depth DEPTH >= 1 in the current forest."\r
749 +  (let ((n (length thread)))\r
750 +    (loop for tree in thread\r
751 +         for count from 1 to n\r
752 +\r
753 +         do (notmuch-pick-insert-tree tree depth tree-status (eq count 1) (eq count n)))))\r
754 +\r
755 +(defun notmuch-pick-insert-forest (forest)\r
756 +  (mapc '(lambda (thread)\r
757 +          (let (tree-status)\r
758 +            ;; Reset at the start of each main thread.\r
759 +            (setq notmuch-pick-previous-subject nil)\r
760 +            (notmuch-pick-insert-thread thread 0 tree-status)))\r
761 +       forest))\r
762 +\r
763 +(defun notmuch-pick-mode ()\r
764 +  "Major mode displaying messages (as opposed to threads) of of a notmuch search.\r
765 +\r
766 +This buffer contains the results of a \"notmuch pick\" of your\r
767 +email archives. Each line in the buffer represents a single\r
768 +message giving the relative date, the author, subject, and any\r
769 +tags.\r
770 +\r
771 +Pressing \\[notmuch-pick-show-message] on any line displays that message.\r
772 +\r
773 +Complete list of currently available key bindings:\r
774 +\r
775 +\\{notmuch-pick-mode-map}"\r
776 +\r
777 +  (interactive)\r
778 +  (kill-all-local-variables)\r
779 +  (use-local-map notmuch-pick-mode-map)\r
780 +  (setq major-mode 'notmuch-pick-mode\r
781 +       mode-name "notmuch-pick")\r
782 +  (hl-line-mode 1)\r
783 +  (setq buffer-read-only t\r
784 +       truncate-lines t))\r
785 +\r
786 +(defun notmuch-pick-process-sentinel (proc msg)\r
787 +  "Add a message to let user know when \"notmuch pick\" exits"\r
788 +  (let ((buffer (process-buffer proc))\r
789 +       (status (process-status proc))\r
790 +       (exit-status (process-exit-status proc))\r
791 +       (never-found-target-thread nil))\r
792 +    (when (memq status '(exit signal))\r
793 +        (kill-buffer (process-get proc 'parse-buf))\r
794 +       (if (buffer-live-p buffer)\r
795 +           (with-current-buffer buffer\r
796 +             (save-excursion\r
797 +               (let ((inhibit-read-only t)\r
798 +                     (atbob (bobp)))\r
799 +                 (goto-char (point-max))\r
800 +                 (if (eq status 'signal)\r
801 +                     (insert "Incomplete search results (pick process was killed).\n"))\r
802 +                 (when (eq status 'exit)\r
803 +                   (insert "End of search results.")\r
804 +                   (message "async parser finished %s"\r
805 +                            (format-time-string "%r"))\r
806 +                   (unless (= exit-status 0)\r
807 +                     (insert (format " (process returned %d)" exit-status)))\r
808 +                   (insert "\n")))))))))\r
809 +\r
810 +\r
811 +(defun notmuch-pick-show-error (string &rest objects)\r
812 +  (save-excursion\r
813 +    (goto-char (point-max))\r
814 +    (insert "Error: Unexpected output from notmuch search:\n")\r
815 +    (insert (apply #'format string objects))\r
816 +    (insert "\n")))\r
817 +\r
818 +\r
819 +(defvar notmuch-pick-json-parser nil\r
820 +  "Incremental JSON parser for the search process filter.")\r
821 +\r
822 +(defun notmuch-pick-process-filter (proc string)\r
823 +  "Process and filter the output of \"notmuch show\" (for pick)"\r
824 +  (let ((results-buf (process-buffer proc))\r
825 +        (parse-buf (process-get proc 'parse-buf))\r
826 +        (inhibit-read-only t)\r
827 +        done)\r
828 +    (if (not (buffer-live-p results-buf))\r
829 +        (delete-process proc)\r
830 +      (with-current-buffer parse-buf\r
831 +        ;; Insert new data\r
832 +        (save-excursion\r
833 +          (goto-char (point-max))\r
834 +          (insert string)))\r
835 +      (with-current-buffer results-buf\r
836 +       (save-excursion\r
837 +         (goto-char (point-max))\r
838 +         (while (not done)\r
839 +           (condition-case nil\r
840 +               (case notmuch-pick-process-state\r
841 +                     ((begin)\r
842 +                      ;; Enter the results list\r
843 +                      (if (eq (notmuch-json-begin-compound\r
844 +                               notmuch-pick-json-parser) 'retry)\r
845 +                          (setq done t)\r
846 +                        (setq notmuch-pick-process-state 'result)))\r
847 +                     ((result)\r
848 +                      ;; Parse a result\r
849 +                      (let ((result (notmuch-json-read notmuch-pick-json-parser)))\r
850 +                        (case result\r
851 +                              ((retry) (setq done t))\r
852 +                              ((end) (setq notmuch-pick-process-state 'end))\r
853 +                              (otherwise (notmuch-pick-insert-forest (list result))))))\r
854 +                     ((end)\r
855 +                      ;; Any trailing data is unexpected\r
856 +                      (with-current-buffer parse-buf\r
857 +                        (skip-chars-forward " \t\r\n")\r
858 +                        (if (eobp)\r
859 +                            (setq done t)\r
860 +                          (signal 'json-error nil)))))\r
861 +             (json-error\r
862 +              ;; Do our best to resynchronize and ensure forward\r
863 +              ;; progress\r
864 +              (notmuch-pick-show-error\r
865 +               "%s"\r
866 +               (with-current-buffer parse-buf\r
867 +                 (let ((bad (buffer-substring (line-beginning-position)\r
868 +                                              (line-end-position))))\r
869 +                   (forward-line)\r
870 +                   bad))))))\r
871 +         ;; Clear out what we've parsed\r
872 +         (with-current-buffer parse-buf\r
873 +           (delete-region (point-min) (point))))))))\r
874 +\r
875 +(defun notmuch-pick-worker (basic-query &optional query-context buffer)\r
876 +  (interactive)\r
877 +  (notmuch-pick-mode)\r
878 +  (setq notmuch-pick-basic-query basic-query)\r
879 +  (setq notmuch-pick-query-context query-context)\r
880 +  (setq notmuch-pick-buffer-name (buffer-name buffer))\r
881 +\r
882 +  (erase-buffer)\r
883 +  (goto-char (point-min))\r
884 +  (let* ((search-args (concat "\'" basic-query\r
885 +                      (if query-context (concat " and (" query-context ")"))\r
886 +                      "\'"))\r
887 +        (message-arg "--entire-thread"))\r
888 +    (if (equal (car (process-lines notmuch-command "count" search-args)) "0")\r
889 +       (setq search-args basic-query))\r
890 +    (message "starting parser %s"\r
891 +            (format-time-string "%r"))\r
892 +    (if notmuch-pick-asynchronous-parser\r
893 +       (let ((proc (start-process\r
894 +                    "notmuch-pick" buffer\r
895 +                    notmuch-command "show" "--body=false" "--format=json"\r
896 +                    message-arg search-args))\r
897 +             ;; Use a scratch buffer to accumulate partial output.\r
898 +              ;; This buffer will be killed by the sentinel, which\r
899 +              ;; should be called no matter how the process dies.\r
900 +              (parse-buf (generate-new-buffer " *notmuch pick parse*")))\r
901 +          (set (make-local-variable 'notmuch-pick-process-state) 'begin)\r
902 +          (set (make-local-variable 'notmuch-pick-json-parser)\r
903 +               (notmuch-json-create-parser parse-buf))\r
904 +          (process-put proc 'parse-buf parse-buf)\r
905 +         (set-process-sentinel proc 'notmuch-pick-process-sentinel)\r
906 +         (set-process-filter proc 'notmuch-pick-process-filter)\r
907 +         (set-process-query-on-exit-flag proc nil))\r
908 +      (progn\r
909 +       (notmuch-pick-insert-forest\r
910 +        (notmuch-query-get-threads\r
911 +         (list "--body=false" message-arg search-args)))\r
912 +       (save-excursion\r
913 +         (goto-char (point-max))\r
914 +         (insert "End of search results.\n"))\r
915 +       (message "sync parser finished %s"\r
916 +                (format-time-string "%r"))))))\r
917 +\r
918 +(defvar notmuch-pick-initialized nil)\r
919 +\r
920 +(defun notmuch-pick-init()\r
921 +  (unless notmuch-pick-initialized\r
922 +    (define-key 'notmuch-search-mode-map "z" 'notmuch-pick)\r
923 +    (define-key 'notmuch-search-mode-map "Z" 'notmuch-pick-from-search-current-query)\r
924 +    (define-key 'notmuch-search-mode-map (kbd "M-RET") 'notmuch-pick-from-search-thread)\r
925 +    (define-key 'notmuch-hello-mode-map "z" 'notmuch-pick-from-hello)\r
926 +    (define-key 'notmuch-show-mode-map "z" 'notmuch-pick)\r
927 +    (define-key 'notmuch-show-mode-map "Z" 'notmuch-pick-from-show-current-query)\r
928 +    (notmuch-pick-setup-show-out)\r
929 +    (setq notmuch-pick-initialized t)\r
930 +    (message "Initialised notmuch-pick")))\r
931 +\r
932 +(defun notmuch-pick (&optional query query-context buffer-name show-first-match)\r
933 +  "Run notmuch pick with the given `query' and display the results"\r
934 +  (interactive "sNotmuch pick: ")\r
935 +  (if (null query)\r
936 +      (setq query (notmuch-read-query "Notmuch pick: ")))\r
937 +  (let ((buffer (get-buffer-create (generate-new-buffer-name\r
938 +                                   (or buffer-name\r
939 +                                       (concat "*notmuch-pick-" query "*")))))\r
940 +       (inhibit-read-only t))\r
941 +\r
942 +    (switch-to-buffer buffer)\r
943 +    ;; Don't track undo information for this buffer\r
944 +    (set 'buffer-undo-list t)\r
945 +\r
946 +    (notmuch-pick-worker query query-context buffer)\r
947 +\r
948 +    (setq truncate-lines t)\r
949 +    (when show-first-match\r
950 +      (notmuch-pick-show-match-message-with-wait))))\r
951 +\r
952 +;;\r
953 +\r
954 +(provide 'notmuch-pick)\r
955 -- \r
956 1.7.9.1\r
957 \r