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
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
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
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
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
67 This adds the main notmuch-pick.el file.
\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
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
77 +++ b/contrib/notmuch-pick/notmuch-pick.el
\r
79 +;; notmuch-pick.el --- displaying notmuch forests.
\r
81 +;; Copyright © Carl Worth
\r
82 +;; Copyright © David Edmondson
\r
83 +;; Copyright © Mark Walters
\r
85 +;; This file is part of Notmuch.
\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
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
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
100 +;; Authors: David Edmondson <dme@dme.org>
\r
101 +;; Mark Walters <markwalters1009@gmail.com>
\r
103 +(require 'mail-parse)
\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
110 +(eval-when-compile (require 'cl))
\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
125 +;; the following variable is defined in notmuch.el
\r
126 +(defvar notmuch-search-query-string)
\r
128 +(defgroup notmuch-pick nil
\r
129 + "Showing message and thread structure."
\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
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
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
159 +(defcustom notmuch-pick-asynchronous-parser t
\r
160 + "Use the asynchronous parser."
\r
162 + :group 'notmuch-pick)
\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
171 +(defface notmuch-pick-match-author-face
\r
172 + '((((class color)
\r
173 + (background dark))
\r
174 + (:foreground "OliveDrab1"))
\r
176 + (background light))
\r
177 + (:foreground "dark blue"))
\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
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
190 +(defface notmuch-pick-match-tag-face
\r
191 + '((((class color)
\r
192 + (background dark))
\r
193 + (:foreground "OliveDrab1"))
\r
195 + (background light))
\r
196 + (:foreground "navy blue" :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
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
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
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
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
228 +(defvar notmuch-pick-previous-subject "")
\r
229 +(make-variable-buffer-local 'notmuch-pick-previous-subject)
\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
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
273 +(fset 'notmuch-pick-mode-map notmuch-pick-mode-map)
\r
275 +(defun notmuch-pick-setup-show-out ()
\r
276 + (let ((map notmuch-pick-mode-map))
\r
277 + (if notmuch-pick-show-out
\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
282 + (define-key map (kbd "RET") 'notmuch-pick-show-message)
\r
283 + (define-key map (kbd "M-RET") 'notmuch-pick-show-message-out)))))
\r
285 +(defun notmuch-pick-get-message-properties ()
\r
286 + "Return the properties of the current message as a plist.
\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
292 + (beginning-of-line)
\r
293 + (get-text-property (point) :notmuch-message-properties)))
\r
295 +(defun notmuch-pick-set-message-properties (props)
\r
297 + (beginning-of-line)
\r
298 + (put-text-property (point) (+ (point) 1) :notmuch-message-properties props)))
\r
300 +(defun notmuch-pick-set-prop (prop val &optional props)
\r
301 + (let ((inhibit-read-only t)
\r
303 + (notmuch-pick-get-message-properties))))
\r
304 + (plist-put props prop val)
\r
305 + (notmuch-pick-set-message-properties props)))
\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
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
316 +(defun notmuch-pick-get-tags ()
\r
317 + "Return the tags of the current message."
\r
318 + (notmuch-pick-get-prop :tags))
\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
324 +(defun notmuch-pick-get-match ()
\r
325 + "Return whether the current message is a match."
\r
327 + (notmuch-pick-get-prop :match))
\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
340 + (min init-point (- new-end 1)))))))
\r
342 +(defun notmuch-pick-tag-update-display (&optional tag-changes)
\r
343 + "Update display for TAG-CHANGES to current message.
\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
352 +(defun notmuch-pick-tag (&optional tag-changes)
\r
353 + "Change tags for the current message"
\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
358 +(defun notmuch-pick-add-tag ()
\r
359 + "Same as `notmuch-pick-tag' but sets initial input to '+'."
\r
361 + (notmuch-pick-tag "+"))
\r
363 +(defun notmuch-pick-remove-tag ()
\r
364 + "Same as `notmuch-pick-tag' but sets initial input to '-'."
\r
366 + (notmuch-pick-tag "-"))
\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
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
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
384 + (notmuch-pick notmuch-show-thread-id notmuch-show-query-context))
\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
391 + (notmuch-pick notmuch-search-query-string))
\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
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
403 +(defun notmuch-pick-show-message ()
\r
404 + "Show the current message (in split-pane)."
\r
406 + (let ((id (notmuch-pick-get-message-id))
\r
407 + (inhibit-read-only t)
\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
421 +(defun notmuch-pick-show-message-out ()
\r
422 + "Show the current message (in whole window)."
\r
424 + (let ((id (notmuch-pick-get-message-id))
\r
425 + (inhibit-read-only t)
\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
432 +(defun notmuch-pick-scroll-message-window ()
\r
433 + "Scroll the message window (if it exists)"
\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
441 +(defun notmuch-pick-scroll-message-window-back ()
\r
442 + "Scroll the message window back(if it exists)"
\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
448 + (scroll-down)))))
\r
450 +(defun notmuch-pick-scroll-or-next ()
\r
451 + "Scroll the message window. If it at end go to next message."
\r
453 + (when (notmuch-pick-scroll-message-window)
\r
454 + (notmuch-pick-next-matching-message)))
\r
456 +(defun notmuch-pick-quit ()
\r
457 + "Close the split view or exit pick."
\r
459 + (unless (notmuch-pick-close-message-window)
\r
460 + (kill-buffer (current-buffer))))
\r
462 +(defun notmuch-pick-close-message-window ()
\r
463 + "Close the message-window. Return t if close succeeds."
\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
472 +(defun notmuch-pick-archive-message ()
\r
473 + "Archive the current message and move to next matching message."
\r
475 + (notmuch-pick-tag "-inbox")
\r
476 + (notmuch-pick-next-matching-message))
\r
478 +(defun notmuch-pick-next-message ()
\r
479 + "Move to next message."
\r
482 + (when (window-live-p notmuch-pick-message-window)
\r
483 + (notmuch-pick-show-message)))
\r
485 +(defun notmuch-pick-prev-message ()
\r
486 + "Move to previous message."
\r
488 + (forward-line -1)
\r
489 + (when (window-live-p notmuch-pick-message-window)
\r
490 + (notmuch-pick-show-message)))
\r
492 +(defun notmuch-pick-prev-matching-message ()
\r
493 + "Move to previous matching message."
\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
501 +(defun notmuch-pick-next-matching-message ()
\r
502 + "Move to next matching message."
\r
505 + (while (and (not (eobp)) (not (notmuch-pick-get-match)))
\r
507 + (when (window-live-p notmuch-pick-message-window)
\r
508 + (notmuch-pick-show-message)))
\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
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
519 + (goto-char (point-min))
\r
520 + (unless (notmuch-pick-get-match)
\r
521 + (notmuch-pick-next-matching-message)))
\r
523 + (when (notmuch-pick-get-match)
\r
524 + (notmuch-pick-show-message)))
\r
526 +(defun notmuch-pick-refresh-view ()
\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
534 + (notmuch-pick-worker basic-query query-context (get-buffer buffer-name))))
\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
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
544 + (kill-buffer buf)))))
\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
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
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
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
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
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
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
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
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
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
617 + ;; "<user@dom.ain>" style.
\r
618 + ((string-match "<\\(.*\\)>" address)
\r
619 + (setq p-address (match-string 1 address)))
\r
621 + ;; Everything else.
\r
623 + (setq p-address address)))
\r
626 + ;; Remove elements of the mailbox part that are not relevant for
\r
627 + ;; display, even if they are required during transport:
\r
630 + (setq p-name (replace-regexp-in-string "\\\\" "" p-name))
\r
632 + ;; Outer single and double quotes, which might be nested.
\r
634 + with start-of-loop
\r
635 + do (setq start-of-loop p-name)
\r
637 + when (string-match "^\"\\(.*\\)\"$" p-name)
\r
638 + do (setq p-name (match-string 1 p-name))
\r
640 + when (string-match "^'\\(.*\\)'$" p-name)
\r
641 + do (setq p-name (match-string 1 p-name))
\r
643 + until (string= start-of-loop p-name)))
\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
650 + ;; If we have a name return that otherwise return the address.
\r
654 + (error address)))
\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
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
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
671 + 'notmuch-pick-match-subject-face
\r
672 + 'notmuch-pick-no-match-subject-face)))
\r
673 + (insert (propertize (format format-string
\r
675 + (mapconcat #'identity (reverse tree-status) "")
\r
676 + (if (string= notmuch-pick-previous-subject bare-subject)
\r
680 + (setq notmuch-pick-previous-subject bare-subject)))
\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
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
693 + ((string-equal field "tags")
\r
694 + (let ((tags (plist-get msg :tags))
\r
696 + 'notmuch-pick-match-tag-face
\r
697 + 'notmuch-pick-no-match-tag-face)))
\r
699 + (insert (propertize (format format-string
\r
700 + (mapconcat #'identity tags ", "))
\r
701 + 'face face))))))))
\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
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
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
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
736 + (push " " tree-status)
\r
737 + (push "│" tree-status))
\r
739 + (notmuch-pick-insert-thread replies (1+ depth) tree-status)))
\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
747 + do (notmuch-pick-insert-tree tree depth tree-status (eq count 1) (eq count n)))))
\r
749 +(defun notmuch-pick-insert-forest-thread (forest-thread)
\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
757 +(defun notmuch-pick-insert-forest (forest)
\r
758 + (mapc 'notmuch-pick-insert-forest-thread forest))
\r
760 +(defun notmuch-pick-mode ()
\r
761 + "Major mode displaying messages (as opposed to threads) of of a notmuch search.
\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
768 +Pressing \\[notmuch-pick-show-message] on any line displays that message.
\r
770 +Complete list of currently available key bindings:
\r
772 +\\{notmuch-pick-mode-map}"
\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
780 + (setq buffer-read-only t
\r
781 + truncate-lines t))
\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
794 + (let ((inhibit-read-only t)
\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
806 +(defun notmuch-pick-show-error (string &rest objects)
\r
808 + (goto-char (point-max))
\r
809 + (insert "Error: Unexpected output from notmuch search:\n")
\r
810 + (insert (apply #'format string objects))
\r
814 +(defvar notmuch-pick-json-parser nil
\r
815 + "Incremental JSON parser for the search process filter.")
\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
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
828 + (goto-char (point-max))
\r
829 + (insert string)))
\r
830 + (with-current-buffer results-buf
\r
832 + (goto-char (point-max))
\r
833 + (while (not done)
\r
834 + (condition-case nil
\r
835 + (case notmuch-pick-process-state
\r
837 + ;; Enter the results list
\r
838 + (if (eq (notmuch-json-begin-compound
\r
839 + notmuch-pick-json-parser) 'retry)
\r
841 + (setq notmuch-pick-process-state 'result)))
\r
843 + ;; Parse a result
\r
844 + (let ((result (notmuch-json-read notmuch-pick-json-parser)))
\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
850 + ;; Any trailing data is unexpected
\r
851 + (with-current-buffer parse-buf
\r
852 + (skip-chars-forward " \t\r\n")
\r
855 + (signal 'json-error nil)))))
\r
857 + ;; Do our best to resynchronize and ensure forward
\r
859 + (notmuch-pick-show-error
\r
861 + (with-current-buffer parse-buf
\r
862 + (let ((bad (buffer-substring (line-beginning-position)
\r
863 + (line-end-position))))
\r
866 + ;; Clear out what we've parsed
\r
867 + (with-current-buffer parse-buf
\r
868 + (delete-region (point-min) (point))))))))
\r
870 +(defun notmuch-pick-worker (basic-query &optional query-context buffer)
\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
878 + (goto-char (point-min))
\r
879 + (let* ((search-args (concat basic-query
\r
880 + (if query-context (concat " and (" query-context ")"))
\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
904 + (notmuch-pick-insert-forest
\r
905 + (notmuch-query-get-threads
\r
906 + (list "--body=false" message-arg search-args)))
\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
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
918 + (setq query (notmuch-read-query "Notmuch pick: ")))
\r
919 + (let ((buffer (get-buffer-create (generate-new-buffer-name
\r
921 + (concat "*notmuch-pick-" query "*")))))
\r
922 + (inhibit-read-only t))
\r
924 + (switch-to-buffer buffer)
\r
925 + ;; Don't track undo information for this buffer
\r
926 + (set 'buffer-undo-list t)
\r
928 + (notmuch-pick-worker query query-context buffer)
\r
930 + (setq truncate-lines t)
\r
931 + (when show-first-match
\r
932 + (notmuch-pick-show-match-message-with-wait))))
\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
945 +(provide 'notmuch-pick)
\r