Re: [PATCH 0/4] Allow specifying alternate names for addresses in other_email
[notmuch-archives.git] / c7 / 4a530353952d02bea075f4642dd7d892927cdd
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 DA24A429E28\r
6         for <notmuch@notmuchmail.org>; Sat, 27 Oct 2012 04:27:00 -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 B9f3j+u1Dlv8 for <notmuch@notmuchmail.org>;\r
18         Sat, 27 Oct 2012 04:26:54 -0700 (PDT)\r
19 Received: from mail-we0-f181.google.com (mail-we0-f181.google.com\r
20         [74.125.82.181]) (using TLSv1 with cipher RC4-SHA (128/128 bits))\r
21         (No client certificate requested)\r
22         by olra.theworths.org (Postfix) with ESMTPS id 3A596429E2E\r
23         for <notmuch@notmuchmail.org>; Sat, 27 Oct 2012 04:26:50 -0700 (PDT)\r
24 Received: by mail-we0-f181.google.com with SMTP id u54so1984348wey.26\r
25         for <notmuch@notmuchmail.org>; Sat, 27 Oct 2012 04:26:49 -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=iODidPHH9A3K7dlmhb5iUN72u3tjkD95DYIscgO2JCs=;\r
30         b=DbovlkUmqGLhHFWYMHmjbJ4R4VBbkLKDMt/4e2mRot2H8IhAy8x2RcOz+KLmD3cSYB\r
31         1e7U4mmc51l9SYlxRVA0drECELMK5xFlUdWKXy4EouffTFiRPDQLhhO3muZUbINkYmlO\r
32         ZhrcIFNV3wyA/moR9YL6IFowqjDfTeZ0IAyZSGDRQYKIAMb8KaDcXO5uBzSgn9y5b7/3\r
33         kUJ154pQulQ3gVZVtgF8iq6rkD6zjqWKBxe5bAivLnPI4obW0hP1YTtSVWuuFLSEVWyr\r
34         QQcukmN2ECDXzW1p9rE/R1IrQQQnF4a7oDRC9t/UgR55jktCcgDI9Bg110No6lta7Sa2\r
35         hVhw==\r
36 Received: by 10.216.197.227 with SMTP id t77mr12299375wen.146.1351337209009;\r
37         Sat, 27 Oct 2012 04:26:49 -0700 (PDT)\r
38 Received: from localhost (93-97-24-31.zone5.bethere.co.uk. [93.97.24.31])\r
39         by mx.google.com with ESMTPS id dt9sm1917770wib.1.2012.10.27.04.26.46\r
40         (version=TLSv1/SSLv3 cipher=OTHER);\r
41         Sat, 27 Oct 2012 04:26:48 -0700 (PDT)\r
42 From: Mark Walters <markwalters1009@gmail.com>\r
43 To: notmuch@notmuchmail.org\r
44 Subject: [PATCH 1/3] contrib: add notmuch-pick.el file itself\r
45 Date: Sat, 27 Oct 2012 12:26:38 +0100\r
46 Message-Id: <1351337200-18050-2-git-send-email-markwalters1009@gmail.com>\r
47 X-Mailer: git-send-email 1.7.9.1\r
48 In-Reply-To: <1351337200-18050-1-git-send-email-markwalters1009@gmail.com>\r
49 References: <1351337200-18050-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: Sat, 27 Oct 2012 11:27:01 -0000\r
66 \r
67 This adds the main notmuch-pick.el file.\r
68 ---\r
69  contrib/notmuch-pick/notmuch-pick.el |  867 ++++++++++++++++++++++++++++++++++\r
70  1 files changed, 867 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..be6a91a\r
76 --- /dev/null\r
77 +++ b/contrib/notmuch-pick/notmuch-pick.el\r
78 @@ -0,0 +1,867 @@\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 +(require 'notmuch) ;; XXX ATM, as notmuch-search-mode-map is defined here\r
109 +\r
110 +(eval-when-compile (require 'cl))\r
111 +\r
112 +(declare-function notmuch-call-notmuch-process "notmuch" (&rest args))\r
113 +(declare-function notmuch-show "notmuch-show" (&rest args))\r
114 +(declare-function notmuch-tag "notmuch" (query &rest tags))\r
115 +(declare-function notmuch-show-strip-re "notmuch-show" (subject))\r
116 +(declare-function notmuch-show-clean-address "notmuch-show" (parsed-address))\r
117 +(declare-function notmuch-show-spaces-n "notmuch-show" (n))\r
118 +(declare-function notmuch-read-query "notmuch" (prompt))\r
119 +(declare-function notmuch-read-tag-changes "notmuch" (&optional initial-input &rest search-terms))\r
120 +(declare-function notmuch-update-tags "notmuch" (current-tags tag-changes))\r
121 +(declare-function notmuch-hello-trim "notmuch-hello" (search))\r
122 +(declare-function notmuch-search-find-thread-id "notmuch" ())\r
123 +(declare-function notmuch-search-find-subject "notmuch" ())\r
124 +\r
125 +;; the following variable is defined in notmuch.el\r
126 +(defvar notmuch-search-query-string)\r
127 +\r
128 +(defgroup notmuch-pick nil\r
129 +  "Showing message and thread structure."\r
130 +  :group 'notmuch)\r
131 +\r
132 +;; This is ugly. We can't run setup-show-out until it has been defined\r
133 +;; which needs the keymap to be defined. So we defer setting up to\r
134 +;; notmuch-pick-init.\r
135 +(defcustom notmuch-pick-show-out nil\r
136 +  "View selected messages in new window rather than split-pane."\r
137 +  :type 'boolean\r
138 +  :group 'notmuch-pick\r
139 +  :set (lambda (symbol value)\r
140 +        (set-default symbol value)\r
141 +        (when (fboundp 'notmuch-pick-setup-show-out)\r
142 +          (notmuch-pick-setup-show-out))))\r
143 +\r
144 +(defcustom notmuch-pick-result-format\r
145 +  `(("date" . "%12s  ")\r
146 +    ("authors" . "%-20s")\r
147 +    ("subject" . " %-54s ")\r
148 +    ("tags" . "(%s)"))\r
149 +  "Result formatting for Pick. Supported fields are: date,\r
150 +        authors, subject, tags Note: subject includes the tree\r
151 +        structure graphics, and the author string should not\r
152 +        contain whitespace (put it in the neighbouring fields\r
153 +        instead).  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 +(defvar notmuch-pick-previous-subject "")\r
229 +(make-variable-buffer-local 'notmuch-pick-previous-subject)\r
230 +\r
231 +;; The basic query i.e. the key part of the search request.\r
232 +(defvar notmuch-pick-basic-query nil)\r
233 +(make-variable-buffer-local 'notmuch-pick-basic-query)\r
234 +;; The context of the search: i.e., useful but can be dropped.\r
235 +(defvar notmuch-pick-query-context nil)\r
236 +(make-variable-buffer-local 'notmuch-pick-query-context)\r
237 +(defvar notmuch-pick-buffer-name nil)\r
238 +(make-variable-buffer-local 'notmuch-pick-buffer-name)\r
239 +(defvar notmuch-pick-message-window nil)\r
240 +(make-variable-buffer-local 'notmuch-pick-message-window)\r
241 +(put 'notmuch-pick-message-window 'permanent-local t)\r
242 +(defvar notmuch-pick-message-buffer nil)\r
243 +(make-variable-buffer-local 'notmuch-pick-message-buffer-name)\r
244 +(put 'notmuch-pick-message-buffer-name 'permanent-local t)\r
245 +(defvar notmuch-pick-process-state nil\r
246 +  "Parsing state of the search process filter.")\r
247 +\r
248 +\r
249 +(defvar notmuch-pick-mode-map\r
250 +  (let ((map (make-sparse-keymap)))\r
251 +    (define-key map [mouse-1] 'notmuch-pick-show-message)\r
252 +    (define-key map "q" 'notmuch-pick-quit)\r
253 +    (define-key map "x" 'notmuch-pick-quit)\r
254 +    (define-key map "?" 'notmuch-help)\r
255 +    (define-key map "a" 'notmuch-pick-archive-message)\r
256 +    (define-key map "=" 'notmuch-pick-refresh-view)\r
257 +    (define-key map "s" 'notmuch-search)\r
258 +    (define-key map "z" 'notmuch-pick)\r
259 +    (define-key map "m" 'notmuch-pick-new-mail)\r
260 +    (define-key map "f" 'notmuch-pick-forward-message)\r
261 +    (define-key map "r" 'notmuch-pick-reply-sender)\r
262 +    (define-key map "R" 'notmuch-pick-reply)\r
263 +    (define-key map "n" 'notmuch-pick-next-matching-message)\r
264 +    (define-key map "p" 'notmuch-pick-prev-matching-message)\r
265 +    (define-key map "N" 'notmuch-pick-next-message)\r
266 +    (define-key map "P" 'notmuch-pick-prev-message)\r
267 +    (define-key map "|" 'notmuch-pick-pipe-message)\r
268 +    (define-key map "-" 'notmuch-pick-remove-tag)\r
269 +    (define-key map "+" 'notmuch-pick-add-tag)\r
270 +    (define-key map " " 'notmuch-pick-scroll-or-next)\r
271 +    (define-key map "b" 'notmuch-pick-scroll-message-window-back)\r
272 +    map))\r
273 +(fset 'notmuch-pick-mode-map notmuch-pick-mode-map)\r
274 +\r
275 +(defun notmuch-pick-setup-show-out ()\r
276 +  (let ((map notmuch-pick-mode-map))\r
277 +    (if notmuch-pick-show-out\r
278 +       (progn\r
279 +         (define-key map (kbd "M-RET") 'notmuch-pick-show-message)\r
280 +         (define-key map (kbd "RET") 'notmuch-pick-show-message-out))\r
281 +      (progn\r
282 +       (define-key map (kbd "RET") 'notmuch-pick-show-message)\r
283 +       (define-key map (kbd "M-RET") 'notmuch-pick-show-message-out)))))\r
284 +\r
285 +(defun notmuch-pick-get-message-properties ()\r
286 +  "Return the properties of the current message as a plist.\r
287 +\r
288 +Some useful entries are:\r
289 +:headers - Property list containing the headers :Date, :Subject, :From, etc.\r
290 +:tags - Tags for this message"\r
291 +  (save-excursion\r
292 +    (beginning-of-line)\r
293 +    (get-text-property (point) :notmuch-message-properties)))\r
294 +\r
295 +(defun notmuch-pick-set-message-properties (props)\r
296 +  (save-excursion\r
297 +    (beginning-of-line)\r
298 +    (put-text-property (point) (+ (point) 1) :notmuch-message-properties props)))\r
299 +\r
300 +(defun notmuch-pick-set-prop (prop val &optional props)\r
301 +  (let ((inhibit-read-only t)\r
302 +       (props (or props\r
303 +                  (notmuch-pick-get-message-properties))))\r
304 +    (plist-put props prop val)\r
305 +    (notmuch-pick-set-message-properties props)))\r
306 +\r
307 +(defun notmuch-pick-get-prop (prop &optional props)\r
308 +  (let ((props (or props\r
309 +                  (notmuch-pick-get-message-properties))))\r
310 +    (plist-get props prop)))\r
311 +\r
312 +(defun notmuch-pick-set-tags (tags)\r
313 +  "Set the tags of the current message."\r
314 +  (notmuch-pick-set-prop :tags tags))\r
315 +\r
316 +(defun notmuch-pick-get-tags ()\r
317 +  "Return the tags of the current message."\r
318 +  (notmuch-pick-get-prop :tags))\r
319 +\r
320 +(defun notmuch-pick-get-message-id ()\r
321 +  "Return the message id of the current message."\r
322 +  (concat "id:\"" (notmuch-pick-get-prop :id) "\""))\r
323 +\r
324 +(defun notmuch-pick-get-match ()\r
325 +  "Return whether the current message is a match."\r
326 +  (interactive)\r
327 +  (notmuch-pick-get-prop :match))\r
328 +\r
329 +(defun notmuch-pick-refresh-result ()\r
330 +  (let ((init-point (point))\r
331 +       (end (line-end-position))\r
332 +       (msg (notmuch-pick-get-message-properties))\r
333 +       (inhibit-read-only t))\r
334 +    (beginning-of-line)\r
335 +    (delete-region (point) (1+ (line-end-position)))\r
336 +    (notmuch-pick-insert-msg msg)\r
337 +    (let ((new-end (line-end-position)))\r
338 +      (goto-char (if (= init-point end)\r
339 +                    new-end\r
340 +                  (min init-point (- new-end 1)))))))\r
341 +\r
342 +(defun notmuch-pick-tag-update-display (&optional tag-changes)\r
343 +  "Update display for TAG-CHANGES to current message.\r
344 +\r
345 +Does NOT change the database."\r
346 +  (let* ((current-tags (notmuch-pick-get-tags))\r
347 +        (new-tags (notmuch-update-tags current-tags tag-changes)))\r
348 +    (unless (equal current-tags new-tags)\r
349 +      (notmuch-pick-set-tags new-tags)\r
350 +      (notmuch-pick-refresh-result))))\r
351 +\r
352 +(defun notmuch-pick-tag (&optional tag-changes)\r
353 +  "Change tags for the current message"\r
354 +  (interactive)\r
355 +  (setq tag-changes (funcall 'notmuch-tag (notmuch-pick-get-message-id) tag-changes))\r
356 +  (notmuch-pick-tag-update-display tag-changes))\r
357 +\r
358 +(defun notmuch-pick-add-tag ()\r
359 +  "Same as `notmuch-pick-tag' but sets initial input to '+'."\r
360 +  (interactive)\r
361 +  (notmuch-pick-tag "+"))\r
362 +\r
363 +(defun notmuch-pick-remove-tag ()\r
364 +  "Same as `notmuch-pick-tag' but sets initial input to '-'."\r
365 +  (interactive)\r
366 +  (notmuch-pick-tag "-"))\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 +      (notmuch-pick-tag-update-display (list "-unread")))\r
419 +    (setq notmuch-pick-message-buffer buffer)))\r
420 +\r
421 +(defun notmuch-pick-show-message-out ()\r
422 +  "Show the current message (in whole window)."\r
423 +  (interactive)\r
424 +  (let ((id (notmuch-pick-get-message-id))\r
425 +       (inhibit-read-only t)\r
426 +       buffer)\r
427 +    (when id\r
428 +      ;; We close the window to kill off un-needed buffers.\r
429 +      (notmuch-pick-close-message-window)\r
430 +      (notmuch-show id nil nil nil))))\r
431 +\r
432 +(defun notmuch-pick-scroll-message-window ()\r
433 +  "Scroll the message window (if it exists)"\r
434 +  (interactive)\r
435 +  (when (window-live-p notmuch-pick-message-window)\r
436 +    (with-selected-window notmuch-pick-message-window\r
437 +      (if (pos-visible-in-window-p (point-max))\r
438 +         t\r
439 +       (scroll-up)))))\r
440 +\r
441 +(defun notmuch-pick-scroll-message-window-back ()\r
442 +  "Scroll the message window back(if it exists)"\r
443 +  (interactive)\r
444 +  (when (window-live-p notmuch-pick-message-window)\r
445 +    (with-selected-window notmuch-pick-message-window\r
446 +      (if (pos-visible-in-window-p (point-min))\r
447 +         t\r
448 +       (scroll-down)))))\r
449 +\r
450 +(defun notmuch-pick-scroll-or-next ()\r
451 +  "Scroll the message window. If it at end go to next message."\r
452 +  (interactive)\r
453 +  (when (notmuch-pick-scroll-message-window)\r
454 +    (notmuch-pick-next-matching-message)))\r
455 +\r
456 +(defun notmuch-pick-quit ()\r
457 +  "Close the split view or exit pick."\r
458 +  (interactive)\r
459 +  (unless (notmuch-pick-close-message-window)\r
460 +    (kill-buffer (current-buffer))))\r
461 +\r
462 +(defun notmuch-pick-close-message-window ()\r
463 +  "Close the message-window. Return t if close succeeds."\r
464 +  (interactive)\r
465 +  (when (and (window-live-p notmuch-pick-message-window)\r
466 +            (eq (window-buffer notmuch-pick-message-window) notmuch-pick-message-buffer))\r
467 +    (delete-window notmuch-pick-message-window)\r
468 +    (unless (get-buffer-window-list notmuch-pick-message-buffer)\r
469 +      (kill-buffer notmuch-pick-message-buffer))\r
470 +    t))\r
471 +\r
472 +(defun notmuch-pick-archive-message ()\r
473 +  "Archive the current message and move to next matching message."\r
474 +  (interactive)\r
475 +  (notmuch-pick-tag "-inbox")\r
476 +  (notmuch-pick-next-matching-message))\r
477 +\r
478 +(defun notmuch-pick-next-message ()\r
479 +  "Move to next message."\r
480 +  (interactive)\r
481 +  (forward-line)\r
482 +  (when (window-live-p notmuch-pick-message-window)\r
483 +    (notmuch-pick-show-message)))\r
484 +\r
485 +(defun notmuch-pick-prev-message ()\r
486 +  "Move to previous message."\r
487 +  (interactive)\r
488 +  (forward-line -1)\r
489 +  (when (window-live-p notmuch-pick-message-window)\r
490 +    (notmuch-pick-show-message)))\r
491 +\r
492 +(defun notmuch-pick-prev-matching-message ()\r
493 +  "Move to previous matching message."\r
494 +  (interactive)\r
495 +  (forward-line -1)\r
496 +  (while (and (not (bobp)) (not (notmuch-pick-get-match)))\r
497 +    (forward-line -1))\r
498 +  (when (window-live-p notmuch-pick-message-window)\r
499 +    (notmuch-pick-show-message)))\r
500 +\r
501 +(defun notmuch-pick-next-matching-message ()\r
502 +  "Move to next matching message."\r
503 +  (interactive)\r
504 +  (forward-line)\r
505 +  (while (and (not (eobp)) (not (notmuch-pick-get-match)))\r
506 +    (forward-line))\r
507 +  (when (window-live-p notmuch-pick-message-window)\r
508 +    (notmuch-pick-show-message)))\r
509 +\r
510 +(defun notmuch-pick-show-match-message-with-wait ()\r
511 +  "Show the first matching message but wait for it to appear or search to finish."\r
512 +  (interactive)\r
513 +  (unless (notmuch-pick-get-match)\r
514 +    (notmuch-pick-next-matching-message))\r
515 +  (while (and (not (notmuch-pick-get-match))\r
516 +             (not (eq notmuch-pick-process-state 'end)))\r
517 +    (message "waiting for message")\r
518 +    (sit-for 0.1)\r
519 +    (goto-char (point-min))\r
520 +    (unless (notmuch-pick-get-match)\r
521 +      (notmuch-pick-next-matching-message)))\r
522 +  (message nil)\r
523 +  (when (notmuch-pick-get-match)\r
524 +    (notmuch-pick-show-message)))\r
525 +\r
526 +(defun notmuch-pick-refresh-view ()\r
527 +  "Refresh view."\r
528 +  (interactive)\r
529 +  (let ((inhibit-read-only t)\r
530 +       (basic-query notmuch-pick-basic-query)\r
531 +       (query-context notmuch-pick-query-context)\r
532 +       (buffer-name notmuch-pick-buffer-name))\r
533 +    (erase-buffer)\r
534 +    (notmuch-pick-worker basic-query query-context (get-buffer buffer-name))))\r
535 +\r
536 +(defmacro with-current-notmuch-pick-message (&rest body)\r
537 +  "Evaluate body with current buffer set to the text of current message"\r
538 +  `(save-excursion\r
539 +     (let ((id (notmuch-pick-get-message-id)))\r
540 +       (let ((buf (generate-new-buffer (concat "*notmuch-msg-" id "*"))))\r
541 +         (with-current-buffer buf\r
542 +           (call-process notmuch-command nil t nil "show" "--format=raw" id)\r
543 +           ,@body)\r
544 +        (kill-buffer buf)))))\r
545 +\r
546 +(defun notmuch-pick-new-mail (&optional prompt-for-sender)\r
547 +  "Compose new mail."\r
548 +  (interactive "P")\r
549 +  (notmuch-pick-close-message-window)\r
550 +  (notmuch-mua-new-mail prompt-for-sender ))\r
551 +\r
552 +(defun notmuch-pick-forward-message (&optional prompt-for-sender)\r
553 +  "Forward the current message."\r
554 +  (interactive "P")\r
555 +  (notmuch-pick-close-message-window)\r
556 +  (with-current-notmuch-pick-message\r
557 +   (notmuch-mua-new-forward-message prompt-for-sender)))\r
558 +\r
559 +(defun notmuch-pick-reply (&optional prompt-for-sender)\r
560 +  "Reply to the sender and all recipients of the current message."\r
561 +  (interactive "P")\r
562 +  (notmuch-pick-close-message-window)\r
563 +  (notmuch-mua-new-reply (notmuch-pick-get-message-id) prompt-for-sender t))\r
564 +\r
565 +(defun notmuch-pick-reply-sender (&optional prompt-for-sender)\r
566 +  "Reply to the sender of the current message."\r
567 +  (interactive "P")\r
568 +  (notmuch-pick-close-message-window)\r
569 +  (notmuch-mua-new-reply (notmuch-pick-get-message-id) prompt-for-sender nil))\r
570 +\r
571 +;; Shamelessly stolen from notmuch-show.el: maybe should be unified.\r
572 +(defun notmuch-pick-pipe-message (command)\r
573 +  "Pipe the contents of the current message to the given command.\r
574 +\r
575 +The given command will be executed with the raw contents of the\r
576 +current email message as stdin. Anything printed by the command\r
577 +to stdout or stderr will appear in the *notmuch-pipe* buffer.\r
578 +\r
579 +When invoked with a prefix argument, the command will receive all\r
580 +open messages in the current thread (formatted as an mbox) rather\r
581 +than only the current message."\r
582 +  (interactive "sPipe message to command: ")\r
583 +  (let ((shell-command\r
584 +        (concat notmuch-command " show --format=raw "\r
585 +                (shell-quote-argument (notmuch-pick-get-message-id)) " | " command))\r
586 +        (buf (get-buffer-create (concat "*notmuch-pipe*"))))\r
587 +    (with-current-buffer buf\r
588 +      (setq buffer-read-only nil)\r
589 +      (erase-buffer)\r
590 +      (let ((exit-code (call-process-shell-command shell-command nil buf)))\r
591 +       (goto-char (point-max))\r
592 +       (set-buffer-modified-p nil)\r
593 +       (setq buffer-read-only t)\r
594 +       (unless (zerop exit-code)\r
595 +         (switch-to-buffer-other-window buf)\r
596 +         (message (format "Command '%s' exited abnormally with code %d"\r
597 +                          shell-command exit-code)))))))\r
598 +\r
599 +;; Shamelessly stolen from notmuch-show.el: should be unified.\r
600 +(defun notmuch-pick-clean-address (address)\r
601 +  "Try to clean a single email ADDRESS for display.  Return\r
602 +unchanged ADDRESS if parsing fails."\r
603 +  (condition-case nil\r
604 +    (let (p-name p-address)\r
605 +      ;; It would be convenient to use `mail-header-parse-address',\r
606 +      ;; but that expects un-decoded mailbox parts, whereas our\r
607 +      ;; mailbox parts are already decoded (and hence may contain\r
608 +      ;; UTF-8). Given that notmuch should handle most of the awkward\r
609 +      ;; cases, some simple string deconstruction should be sufficient\r
610 +      ;; here.\r
611 +      (cond\r
612 +       ;; "User <user@dom.ain>" style.\r
613 +       ((string-match "\\(.*\\) <\\(.*\\)>" address)\r
614 +       (setq p-name (match-string 1 address)\r
615 +             p-address (match-string 2 address)))\r
616 +\r
617 +       ;; "<user@dom.ain>" style.\r
618 +       ((string-match "<\\(.*\\)>" address)\r
619 +       (setq p-address (match-string 1 address)))\r
620 +\r
621 +       ;; Everything else.\r
622 +       (t\r
623 +       (setq p-address address)))\r
624 +\r
625 +      (when p-name\r
626 +       ;; Remove elements of the mailbox part that are not relevant for\r
627 +       ;; display, even if they are required during transport:\r
628 +       ;;\r
629 +       ;; Backslashes.\r
630 +       (setq p-name (replace-regexp-in-string "\\\\" "" p-name))\r
631 +\r
632 +       ;; Outer single and double quotes, which might be nested.\r
633 +       (loop\r
634 +        with start-of-loop\r
635 +        do (setq start-of-loop p-name)\r
636 +\r
637 +        when (string-match "^\"\\(.*\\)\"$" p-name)\r
638 +        do (setq p-name (match-string 1 p-name))\r
639 +\r
640 +        when (string-match "^'\\(.*\\)'$" p-name)\r
641 +        do (setq p-name (match-string 1 p-name))\r
642 +\r
643 +        until (string= start-of-loop p-name)))\r
644 +\r
645 +      ;; If the address is 'foo@bar.com <foo@bar.com>' then show just\r
646 +      ;; 'foo@bar.com'.\r
647 +      (when (string= p-name p-address)\r
648 +       (setq p-name nil))\r
649 +\r
650 +      ;; If we have a name return that otherwise return the address.\r
651 +      (if (not p-name)\r
652 +         p-address\r
653 +       p-name))\r
654 +    (error address)))\r
655 +\r
656 +(defun notmuch-pick-insert-field (field format-string msg)\r
657 +  (let* ((headers (plist-get msg :headers))\r
658 +       (match (plist-get msg :match)))\r
659 +    (cond\r
660 +     ((string-equal field "date")\r
661 +      (let ((face (if match\r
662 +                     'notmuch-pick-match-date-face\r
663 +                   'notmuch-pick-no-match-date-face)))\r
664 +       (insert (propertize (format format-string (plist-get msg :date_relative))\r
665 +                           'face face))))\r
666 +\r
667 +     ((string-equal field "subject")\r
668 +      (let ((tree-status (plist-get msg :tree-status))\r
669 +           (bare-subject (notmuch-show-strip-re (plist-get headers :Subject)))\r
670 +           (face (if match\r
671 +                     'notmuch-pick-match-subject-face\r
672 +                   'notmuch-pick-no-match-subject-face)))\r
673 +       (insert (propertize (format format-string\r
674 +                                   (concat\r
675 +                                    (mapconcat #'identity (reverse tree-status) "")\r
676 +                                    (if (string= notmuch-pick-previous-subject bare-subject)\r
677 +                                        " ..."\r
678 +                                      bare-subject)))\r
679 +                           'face face))\r
680 +       (setq notmuch-pick-previous-subject bare-subject)))\r
681 +\r
682 +     ((string-equal field "authors")\r
683 +      (let ((author (notmuch-pick-clean-address (plist-get headers :From)))\r
684 +           (len (length (format format-string "")))\r
685 +           (face (if match\r
686 +                     'notmuch-pick-match-author-face\r
687 +                   'notmuch-pick-no-match-author-face)))\r
688 +       (when (> (length author) len)\r
689 +         (setq author (substring author 0 len)))\r
690 +       (insert (propertize (format format-string author)\r
691 +                           'face face))))\r
692 +\r
693 +     ((string-equal field "tags")\r
694 +      (let ((tags (plist-get msg :tags))\r
695 +           (face (if match\r
696 +                         'notmuch-pick-match-tag-face\r
697 +                       'notmuch-pick-no-match-tag-face)))\r
698 +       (when tags\r
699 +         (insert (propertize (format format-string\r
700 +                                     (mapconcat #'identity tags ", "))\r
701 +                             'face face))))))))\r
702 +\r
703 +(defun notmuch-pick-insert-msg (msg)\r
704 +  "Insert the message MSG according to notmuch-pick-result-format"\r
705 +  (dolist (spec notmuch-pick-result-format)\r
706 +    (notmuch-pick-insert-field (car spec) (cdr spec) msg))\r
707 +  (notmuch-pick-set-message-properties msg)\r
708 +  (insert "\n"))\r
709 +\r
710 +(defun notmuch-pick-insert-tree (tree depth tree-status first last)\r
711 +  "Insert the message tree TREE at depth DEPTH in the current thread."\r
712 +  (let ((msg (car tree))\r
713 +       (replies (cadr tree)))\r
714 +\r
715 +      (cond\r
716 +       ((and (< 0 depth) (not last))\r
717 +       (push "├" tree-status))\r
718 +       ((and (< 0 depth) last)\r
719 +       (push "╰" tree-status))\r
720 +       ((and (eq 0 depth) first last)\r
721 +;;       (push "─" tree-status)) choice between this and next line is matter of taste.\r
722 +       (push " " tree-status))\r
723 +       ((and (eq 0 depth) first (not last))\r
724 +         (push "┬" tree-status))\r
725 +       ((and (eq 0 depth) (not first) last)\r
726 +       (push "╰" tree-status))\r
727 +       ((and (eq 0 depth) (not first) (not last))\r
728 +       (push "├" tree-status)))\r
729 +\r
730 +      (push (concat (if replies "┬" "─") "►") tree-status)\r
731 +      (notmuch-pick-insert-msg (plist-put msg :tree-status tree-status))\r
732 +      (pop tree-status)\r
733 +      (pop tree-status)\r
734 +\r
735 +      (if last\r
736 +         (push " " tree-status)\r
737 +       (push "│" tree-status))\r
738 +\r
739 +    (notmuch-pick-insert-thread replies (1+ depth) tree-status)))\r
740 +\r
741 +(defun notmuch-pick-insert-thread (thread depth tree-status)\r
742 +  "Insert the thread THREAD at depth DEPTH >= 1 in the current forest."\r
743 +  (let ((n (length thread)))\r
744 +    (loop for tree in thread\r
745 +         for count from 1 to n\r
746 +\r
747 +         do (notmuch-pick-insert-tree tree depth tree-status (eq count 1) (eq count n)))))\r
748 +\r
749 +(defun notmuch-pick-insert-forest-thread (forest-thread)\r
750 +  (save-excursion\r
751 +    (goto-char (point-max))\r
752 +    (let (tree-status)\r
753 +      ;; Reset at the start of each main thread.\r
754 +      (setq notmuch-pick-previous-subject nil)\r
755 +      (notmuch-pick-insert-thread forest-thread 0 tree-status))))\r
756 +\r
757 +(defun notmuch-pick-insert-forest (forest)\r
758 +  (mapc 'notmuch-pick-insert-forest-thread forest))\r
759 +\r
760 +(defun notmuch-pick-mode ()\r
761 +  "Major mode displaying messages (as opposed to threads) of of a notmuch search.\r
762 +\r
763 +This buffer contains the results of a \"notmuch pick\" of your\r
764 +email archives. Each line in the buffer represents a single\r
765 +message giving the relative date, the author, subject, and any\r
766 +tags.\r
767 +\r
768 +Pressing \\[notmuch-pick-show-message] on any line displays that message.\r
769 +\r
770 +Complete list of currently available key bindings:\r
771 +\r
772 +\\{notmuch-pick-mode-map}"\r
773 +\r
774 +  (interactive)\r
775 +  (kill-all-local-variables)\r
776 +  (use-local-map notmuch-pick-mode-map)\r
777 +  (setq major-mode 'notmuch-pick-mode\r
778 +       mode-name "notmuch-pick")\r
779 +  (hl-line-mode 1)\r
780 +  (setq buffer-read-only t\r
781 +       truncate-lines t))\r
782 +\r
783 +(defun notmuch-pick-process-sentinel (proc msg)\r
784 +  "Add a message to let user know when \"notmuch pick\" exits"\r
785 +  (let ((buffer (process-buffer proc))\r
786 +       (status (process-status proc))\r
787 +       (exit-status (process-exit-status proc))\r
788 +       (never-found-target-thread nil))\r
789 +    (when (memq status '(exit signal))\r
790 +        (kill-buffer (process-get proc 'parse-buf))\r
791 +       (if (buffer-live-p buffer)\r
792 +           (with-current-buffer buffer\r
793 +             (save-excursion\r
794 +               (let ((inhibit-read-only t)\r
795 +                     (atbob (bobp)))\r
796 +                 (goto-char (point-max))\r
797 +                 (if (eq status 'signal)\r
798 +                     (insert "Incomplete search results (pick process was killed).\n"))\r
799 +                 (when (eq status 'exit)\r
800 +                   (insert "End of search results.")\r
801 +                   (unless (= exit-status 0)\r
802 +                     (insert (format " (process returned %d)" exit-status)))\r
803 +                   (insert "\n")))))))))\r
804 +\r
805 +\r
806 +(defun notmuch-pick-show-error (string &rest objects)\r
807 +  (save-excursion\r
808 +    (goto-char (point-max))\r
809 +    (insert "Error: Unexpected output from notmuch search:\n")\r
810 +    (insert (apply #'format string objects))\r
811 +    (insert "\n")))\r
812 +\r
813 +\r
814 +(defvar notmuch-pick-json-parser nil\r
815 +  "Incremental JSON parser for the search process filter.")\r
816 +\r
817 +(defun notmuch-pick-process-filter (proc string)\r
818 +  "Process and filter the output of \"notmuch show\" (for pick)"\r
819 +  (let ((results-buf (process-buffer proc))\r
820 +        (parse-buf (process-get proc 'parse-buf))\r
821 +        (inhibit-read-only t)\r
822 +        done)\r
823 +    (if (not (buffer-live-p results-buf))\r
824 +        (delete-process proc)\r
825 +      (with-current-buffer parse-buf\r
826 +        ;; Insert new data\r
827 +        (save-excursion\r
828 +          (goto-char (point-max))\r
829 +          (insert string)))\r
830 +      (with-current-buffer results-buf\r
831 +       (save-excursion\r
832 +         (goto-char (point-max))\r
833 +         (while (not done)\r
834 +           (condition-case nil\r
835 +               (case notmuch-pick-process-state\r
836 +                     ((begin)\r
837 +                      ;; Enter the results list\r
838 +                      (if (eq (notmuch-json-begin-compound\r
839 +                               notmuch-pick-json-parser) 'retry)\r
840 +                          (setq done t)\r
841 +                        (setq notmuch-pick-process-state 'result)))\r
842 +                     ((result)\r
843 +                      ;; Parse a result\r
844 +                      (let ((result (notmuch-json-read notmuch-pick-json-parser)))\r
845 +                        (case result\r
846 +                              ((retry) (setq done t))\r
847 +                              ((end) (setq notmuch-pick-process-state 'end))\r
848 +                              (otherwise (notmuch-pick-insert-forest-thread result)))))\r
849 +                     ((end)\r
850 +                      ;; Any trailing data is unexpected\r
851 +                      (with-current-buffer parse-buf\r
852 +                        (skip-chars-forward " \t\r\n")\r
853 +                        (if (eobp)\r
854 +                            (setq done t)\r
855 +                          (signal 'json-error nil)))))\r
856 +             (json-error\r
857 +              ;; Do our best to resynchronize and ensure forward\r
858 +              ;; progress\r
859 +              (notmuch-pick-show-error\r
860 +               "%s"\r
861 +               (with-current-buffer parse-buf\r
862 +                 (let ((bad (buffer-substring (line-beginning-position)\r
863 +                                              (line-end-position))))\r
864 +                   (forward-line)\r
865 +                   bad))))))\r
866 +         ;; Clear out what we've parsed\r
867 +         (with-current-buffer parse-buf\r
868 +           (delete-region (point-min) (point))))))))\r
869 +\r
870 +(defun notmuch-pick-worker (basic-query &optional query-context buffer)\r
871 +  (interactive)\r
872 +  (notmuch-pick-mode)\r
873 +  (setq notmuch-pick-basic-query basic-query)\r
874 +  (setq notmuch-pick-query-context query-context)\r
875 +  (setq notmuch-pick-buffer-name (buffer-name buffer))\r
876 +\r
877 +  (erase-buffer)\r
878 +  (goto-char (point-min))\r
879 +  (let* ((search-args (concat basic-query\r
880 +                      (if query-context (concat " and (" query-context ")"))\r
881 +                      ))\r
882 +        (message-arg "--entire-thread"))\r
883 +    (if (equal (car (process-lines notmuch-command "count" search-args)) "0")\r
884 +       (setq search-args basic-query))\r
885 +    (message "starting parser %s"\r
886 +            (format-time-string "%r"))\r
887 +    (if notmuch-pick-asynchronous-parser\r
888 +       (let ((proc (start-process\r
889 +                    "notmuch-pick" buffer\r
890 +                    notmuch-command "show" "--body=false" "--format=json"\r
891 +                    message-arg search-args))\r
892 +             ;; Use a scratch buffer to accumulate partial output.\r
893 +              ;; This buffer will be killed by the sentinel, which\r
894 +              ;; should be called no matter how the process dies.\r
895 +              (parse-buf (generate-new-buffer " *notmuch pick parse*")))\r
896 +          (set (make-local-variable 'notmuch-pick-process-state) 'begin)\r
897 +          (set (make-local-variable 'notmuch-pick-json-parser)\r
898 +               (notmuch-json-create-parser parse-buf))\r
899 +          (process-put proc 'parse-buf parse-buf)\r
900 +         (set-process-sentinel proc 'notmuch-pick-process-sentinel)\r
901 +         (set-process-filter proc 'notmuch-pick-process-filter)\r
902 +         (set-process-query-on-exit-flag proc nil))\r
903 +      (progn\r
904 +       (notmuch-pick-insert-forest\r
905 +        (notmuch-query-get-threads\r
906 +         (list "--body=false" message-arg search-args)))\r
907 +       (save-excursion\r
908 +         (goto-char (point-max))\r
909 +         (insert "End of search results.\n"))\r
910 +       (message "sync parser finished %s"\r
911 +                (format-time-string "%r"))))))\r
912 +\r
913 +\r
914 +(defun notmuch-pick (&optional query query-context buffer-name show-first-match)\r
915 +  "Run notmuch pick with the given `query' and display the results"\r
916 +  (interactive "sNotmuch pick: ")\r
917 +  (if (null query)\r
918 +      (setq query (notmuch-read-query "Notmuch pick: ")))\r
919 +  (let ((buffer (get-buffer-create (generate-new-buffer-name\r
920 +                                   (or buffer-name\r
921 +                                       (concat "*notmuch-pick-" query "*")))))\r
922 +       (inhibit-read-only t))\r
923 +\r
924 +    (switch-to-buffer buffer)\r
925 +    ;; Don't track undo information for this buffer\r
926 +    (set 'buffer-undo-list t)\r
927 +\r
928 +    (notmuch-pick-worker query query-context buffer)\r
929 +\r
930 +    (setq truncate-lines t)\r
931 +    (when show-first-match\r
932 +      (notmuch-pick-show-match-message-with-wait))))\r
933 +\r
934 +\r
935 +;; Set up key bindings from the rest of notmuch.\r
936 +(define-key 'notmuch-search-mode-map "z" 'notmuch-pick)\r
937 +(define-key 'notmuch-search-mode-map "Z" 'notmuch-pick-from-search-current-query)\r
938 +(define-key 'notmuch-search-mode-map (kbd "M-RET") 'notmuch-pick-from-search-thread)\r
939 +(define-key 'notmuch-hello-mode-map "z" 'notmuch-pick-from-hello)\r
940 +(define-key 'notmuch-show-mode-map "z" 'notmuch-pick)\r
941 +(define-key 'notmuch-show-mode-map "Z" 'notmuch-pick-from-show-current-query)\r
942 +(notmuch-pick-setup-show-out)\r
943 +(message "Initialised notmuch-pick")\r
944 +\r
945 +(provide 'notmuch-pick)\r
946 -- \r
947 1.7.9.1\r
948 \r