1 Return-Path: <daniel.schoepe@googlemail.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 B1005429E2F
\r
6 for <notmuch@notmuchmail.org>; Fri, 8 Jul 2011 11:47:13 -0700 (PDT)
\r
7 X-Virus-Scanned: Debian amavisd-new at olra.theworths.org
\r
11 X-Spam-Status: No, score=-0.799 tagged_above=-999 required=5
\r
12 tests=[DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1,
\r
13 FREEMAIL_FROM=0.001, RCVD_IN_DNSWL_LOW=-0.7] autolearn=disabled
\r
14 Received: from olra.theworths.org ([127.0.0.1])
\r
15 by localhost (olra.theworths.org [127.0.0.1]) (amavisd-new, port 10024)
\r
16 with ESMTP id ibvhSZ2h2EEv for <notmuch@notmuchmail.org>;
\r
17 Fri, 8 Jul 2011 11:47:10 -0700 (PDT)
\r
18 Received: from mail-fx0-f46.google.com (mail-fx0-f46.google.com
\r
19 [209.85.161.46]) (using TLSv1 with cipher RC4-SHA (128/128 bits))
\r
20 (No client certificate requested)
\r
21 by olra.theworths.org (Postfix) with ESMTPS id C2A10431FD0
\r
22 for <notmuch@notmuchmail.org>; Fri, 8 Jul 2011 11:47:09 -0700 (PDT)
\r
23 Received: by mail-fx0-f46.google.com with SMTP id 19so2355176fxh.19
\r
24 for <notmuch@notmuchmail.org>; Fri, 08 Jul 2011 11:47:09 -0700 (PDT)
\r
25 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
\r
26 d=googlemail.com; s=gamma;
\r
27 h=from:to:cc:subject:date:message-id:x-mailer:in-reply-to:references;
\r
28 bh=Uljj9ttx0LbbK4B+fw9+x6EYWcPlmz/NQvX5dmNDgJ8=;
\r
29 b=s5hHqk7CjHCTjGrqv1gqG7/HDB/djMvPVfeH+d0hYd4VS5rObULJBb1nuIH6BuZEKK
\r
30 vVEtwNV4YhkXkPEzns4CPKRceigt+THJTMaWSxgj+/gdcEpq3pgxW0Gs5iTREWjDg8fm
\r
31 R0uSwOiVY7LWzha052bDrGgBV7k2iZm0Widts=
\r
32 Received: by 10.223.160.131 with SMTP id n3mr3441011fax.111.1310150829456;
\r
33 Fri, 08 Jul 2011 11:47:09 -0700 (PDT)
\r
34 Received: from localhost (dslb-178-004-020-175.pools.arcor-ip.net
\r
36 by mx.google.com with ESMTPS id g12sm3133101fai.32.2011.07.08.11.47.07
\r
37 (version=TLSv1/SSLv3 cipher=OTHER);
\r
38 Fri, 08 Jul 2011 11:47:08 -0700 (PDT)
\r
39 From: Daniel Schoepe <daniel.schoepe@googlemail.com>
\r
40 To: notmuch@notmuchmail.org
\r
41 Subject: [PATCH v2 2/3] emacs: Add thread-outline functionality
\r
42 Date: Fri, 8 Jul 2011 20:46:56 +0200
\r
43 Message-Id: <1310150817-15213-3-git-send-email-daniel.schoepe@googlemail.com>
\r
44 X-Mailer: git-send-email 1.7.5.4
\r
45 In-Reply-To: <1310150817-15213-1-git-send-email-daniel.schoepe@googlemail.com>
\r
46 References: <1307921480-17130-1-git-send-email-daniel.schoepe@googlemail.com>
\r
47 <1310150817-15213-1-git-send-email-daniel.schoepe@googlemail.com>
\r
48 X-BeenThere: notmuch@notmuchmail.org
\r
49 X-Mailman-Version: 2.1.13
\r
51 List-Id: "Use and development of the notmuch mail system."
\r
52 <notmuch.notmuchmail.org>
\r
53 List-Unsubscribe: <http://notmuchmail.org/mailman/options/notmuch>,
\r
54 <mailto:notmuch-request@notmuchmail.org?subject=unsubscribe>
\r
55 List-Archive: <http://notmuchmail.org/pipermail/notmuch>
\r
56 List-Post: <mailto:notmuch@notmuchmail.org>
\r
57 List-Help: <mailto:notmuch-request@notmuchmail.org?subject=help>
\r
58 List-Subscribe: <http://notmuchmail.org/mailman/listinfo/notmuch>,
\r
59 <mailto:notmuch-request@notmuchmail.org?subject=subscribe>
\r
60 X-List-Received-Date: Fri, 08 Jul 2011 18:47:14 -0000
\r
62 This patch adds some functionality to display the outline for threads
\r
63 displayed by notmuch-show. The entries in the outline buffer are
\r
64 links to the corresponding message in the notmuch-show buffer.
\r
66 emacs/notmuch-lib.el | 12 +++
\r
67 emacs/notmuch-show.el | 195 ++++++++++++++++++++++++++++++++++++++++++++++++-
\r
68 2 files changed, 206 insertions(+), 1 deletions(-)
\r
70 diff --git a/emacs/notmuch-lib.el b/emacs/notmuch-lib.el
\r
71 index f93c957..e346571 100644
\r
72 --- a/emacs/notmuch-lib.el
\r
73 +++ b/emacs/notmuch-lib.el
\r
75 (defvar notmuch-folders nil
\r
76 "Deprecated name for what is now known as `notmuch-saved-searches'.")
\r
78 +(defvar notmuch-show-outline-buffer nil
\r
79 + "Outline buffer associated with a notmuch-show buffer.")
\r
80 +(make-variable-buffer-local 'notmuch-show-outline-buffer)
\r
82 (defun notmuch-saved-searches ()
\r
83 "Common function for querying the notmuch-saved-searches variable.
\r
85 @@ -91,9 +95,17 @@ the user hasn't set this variable with the old or new value."
\r
86 "Return the user.other_email value (as a list) from the notmuch configuration."
\r
87 (split-string (notmuch-config-get "user.other_email") "\n"))
\r
89 +(declare-function notmuch-show-outline-buffer-name "notmuch-show" (&optional buf))
\r
91 (defun notmuch-kill-this-buffer ()
\r
92 "Kill the current buffer."
\r
94 + ;; if we are in a notmuch-show buffer, kill the associated outline buffer, if any
\r
95 + (when (eq major-mode 'notmuch-show-mode)
\r
96 + (let ((outline-buf notmuch-show-outline-buffer))
\r
98 + (mapc #'delete-window (get-buffer-window-list outline-buf))
\r
99 + (kill-buffer outline-buf))))
\r
100 (kill-buffer (current-buffer)))
\r
103 diff --git a/emacs/notmuch-show.el b/emacs/notmuch-show.el
\r
104 index 262addb..cd3eefb 100644
\r
105 --- a/emacs/notmuch-show.el
\r
106 +++ b/emacs/notmuch-show.el
\r
107 @@ -96,6 +96,57 @@ any given message."
\r
111 +(defcustom notmuch-always-show-outline nil
\r
112 + "Always open an outline buffer when viewing a thread?"
\r
116 +(defcustom notmuch-outline-format
\r
118 + "Format used for thread-outline lines.
\r
120 +This is a list supporting the following types of elements:
\r
121 +For a symbol, its value is used if non-nil.
\r
122 +A string is inserted verbatim with the exception
\r
123 + of the following %-constructs:
\r
127 + %r - Relative date
\r
128 +For a list of the form `(:eval FORM)', form is evaluated
\r
129 + and its result displayed.
\r
131 +The variables author, subject, date and reldate will be bound to
\r
132 +their respective values when this is interpreted, and can be
\r
133 +used in (:eval ..)-elements or directly as symbols."
\r
136 + '(repeat (choice (const :tag "Author" author)
\r
137 + (const :tag "Date" date)
\r
138 + (const :tag "Relative date" reldate)
\r
139 + (string :tag "Format string")
\r
140 + (list :tag "Custom expression (will be evaluated when rendering)"
\r
141 + (const :tag "" :eval)
\r
144 +(defface notmuch-outline '((t :inherit default))
\r
145 + "Face used to display (unhighlighted) lines in thread outlines"
\r
148 +(defface notmuch-outline-highlighted
\r
149 + '((((class color) (background light)) (:background "#f0f0f0"))
\r
150 + (((class color) (background dark)) (:background "#303030")))
\r
151 + "Face used to display highlight the current message in the outline buffer"
\r
154 +(defvar notmuch-outline-mode-map
\r
155 + (let ((map (make-sparse-keymap)))
\r
156 + (define-key map "n" 'next-line)
\r
157 + (define-key map "p" 'previous-line)
\r
158 + (define-key map "q" 'kill-buffer-and-window)
\r
159 + (define-key map "x" 'kill-buffer-and-window)
\r
162 (defmacro with-current-notmuch-show-message (&rest body)
\r
163 "Evaluate body with current buffer set to the text of current message"
\r
165 @@ -741,12 +792,27 @@ current buffer, if possible."
\r
167 (put-text-property message-start message-end :notmuch-message-extent (cons message-start message-end))
\r
169 + ;; Save the indentation depth, used by `notmuch-show-outline'
\r
170 + (put-text-property message-start message-end :notmuch-depth depth)
\r
172 (let ((headers-overlay (make-overlay headers-start headers-end))
\r
173 (invis-specs (list headers-invis-spec message-invis-spec)))
\r
174 (overlay-put headers-overlay 'invisible invis-specs)
\r
175 (overlay-put headers-overlay 'priority 10))
\r
176 (overlay-put (make-overlay body-start body-end) 'invisible message-invis-spec)
\r
178 + ;; Add callbacks that update the outline buffer when moving between messages.
\r
179 + ;; Due to the mindbogglingly absurd semantics of point-entered and point-left
\r
180 + ;; this function will will be run up to _four_ times when moving between messages:
\r
181 + (let ((goto-msg-func
\r
182 + `(lambda (before after)
\r
183 + (if (and (>= after (marker-position ,message-start))
\r
184 + (< after (marker-position ,message-end)))
\r
185 + (notmuch-outline-highlight-message ,message-start)))))
\r
186 + (add-text-properties message-start message-end
\r
187 + (list 'point-entered goto-msg-func
\r
188 + 'point-left goto-msg-func)))
\r
190 ;; Save the properties for this message. Currently this saves the
\r
191 ;; entire message (augmented it with other stuff), which seems
\r
192 ;; like overkill. We might save a reduced subset (for example, not
\r
193 @@ -778,6 +844,130 @@ current buffer, if possible."
\r
194 (defvar notmuch-show-parent-buffer nil)
\r
195 (make-variable-buffer-local 'notmuch-show-parent-buffer)
\r
197 +(defun notmuch-show-message-is-visible ()
\r
198 + "Return t if current message is visible."
\r
199 + (plist-get (notmuch-show-get-message-properties) :message-visible))
\r
201 +(defun notmuch-outline-render-format (format)
\r
202 + "Render FORMAT, as described in `notmuch-outline-format'"
\r
203 + (let ((author (notmuch-show-get-from))
\r
204 + (date (notmuch-show-get-date))
\r
205 + (subject (notmuch-show-get-subject))
\r
206 + (reldate (plist-get (notmuch-show-get-message-properties)
\r
207 + :date_relative)))
\r
208 + (mapconcat (lambda (elem)
\r
210 + ((symbolp elem) (or (symbol-value elem) ""))
\r
212 + (let ((str elem))
\r
213 + (mapc (lambda (subst)
\r
215 + (replace-regexp-in-string (car subst)
\r
218 + `(("%a" . ,author)
\r
219 + ("%s" . ,subject)
\r
221 + ("%r" . ,reldate)))
\r
223 + ((and (listp elem) (eq (car elem) :eval))
\r
224 + (eval (second elem)))
\r
225 + (t (error "Unknown element in `notmuch-outline-format': %S" elem))))
\r
229 +(defun notmuch-outline-highlight-message (msg-start)
\r
230 + "Highlight message starting at MSG-START.
\r
232 +The highlighting will take place in the outline buffer, while
\r
233 +MSG-START refers to a position in the corresponding notmuch-show buffer."
\r
234 + (when (buffer-live-p notmuch-show-outline-buffer)
\r
235 + (with-current-buffer notmuch-show-outline-buffer
\r
236 + (remove-overlays nil nil 'current-message t)
\r
238 + (goto-char (point-min))
\r
239 + (while (and (not (equal (get-text-property (point) :message-start)
\r
245 + (make-overlay (line-beginning-position)
\r
246 + (line-end-position))))
\r
247 + (overlay-put ovl 'face 'notmuch-outline-highlighted)
\r
248 + (overlay-put ovl 'current-message t)))))))
\r
250 +(defun notmuch-show-create-outline-buffer (&optional buf)
\r
251 + "Create an outline buffer for show-buffer BUF.
\r
253 +Returns the created buffer."
\r
255 + (generate-new-buffer (concat (buffer-name buf) " - outline")))
\r
257 +(defun notmuch-outline-message ()
\r
258 + "Outline the message under the point.
\r
260 +Expects the point to be on the beginning of the first line of the message."
\r
262 + ((msg-start (car (notmuch-show-message-extent)))
\r
263 + (outline-buf notmuch-show-outline-buffer)
\r
266 + (let ((win (get-buffer-window outline-buf)))
\r
268 + (select-window (get-buffer-window outline-buf))
\r
269 + (when (marker-buffer msg-start)
\r
270 + (switch-to-buffer-other-window (marker-buffer msg-start))
\r
271 + (notmuch-outline-highlight-message msg-start)
\r
272 + (goto-char (marker-position msg-start))
\r
273 + (when (not (notmuch-show-message-is-visible))
\r
274 + (notmuch-show-toggle-message))))))))
\r
275 + (let ((indentation (or (get-text-property (point) :notmuch-depth) 0))
\r
276 + (button-label (notmuch-outline-render-format
\r
277 + notmuch-outline-format)))
\r
278 + (with-current-buffer outline-buf
\r
279 + (indent-to indentation)
\r
280 + (insert button-label)
\r
281 + (make-text-button (line-beginning-position) (line-end-position)
\r
282 + 'action goto-message
\r
284 + 'help-echo "mouse-1, RET: show this message"
\r
285 + 'face 'notmuch-outline)
\r
286 + (put-text-property (line-beginning-position) (line-end-position)
\r
287 + :message-start msg-start)
\r
288 + (insert "\n")))))
\r
290 +(defun notmuch-show-outline ()
\r
291 + "Generate an outline for the current buffer.
\r
293 +This function must only be called in a notmuch-show buffer."
\r
295 + (if (buffer-live-p notmuch-show-outline-buffer)
\r
296 + (switch-to-buffer-other-window notmuch-show-outline-buffer)
\r
297 + (let ((outline-buf (notmuch-show-create-outline-buffer))
\r
298 + (inhibit-point-motion-hooks t))
\r
299 + (setq notmuch-show-outline-buffer outline-buf)
\r
301 + (with-current-buffer outline-buf
\r
302 + (notmuch-outline-mode))
\r
303 + (goto-char (point-min))
\r
304 + (while (not (eobp))
\r
305 + (notmuch-outline-message)
\r
306 + (goto-char (marker-position (cdr (notmuch-show-message-extent)))))
\r
307 + (with-current-buffer outline-buf
\r
308 + (setq buffer-read-only t)))
\r
309 + (notmuch-outline-highlight-message (car (notmuch-show-message-extent)))
\r
310 + (let ((win (selected-window)))
\r
311 + (switch-to-buffer-other-window outline-buf)
\r
312 + (select-window win)))))
\r
314 +(defun notmuch-outline-mode ()
\r
316 + (kill-all-local-variables)
\r
317 + (use-local-map notmuch-outline-mode-map)
\r
318 + (setq major-mode 'notmuch-show-outline-mode
\r
319 + mode-name "notmuch-show-outline"))
\r
322 (defun notmuch-show (thread-id &optional parent-buffer query-context buffer-name crypto-switch)
\r
323 "Run \"notmuch show\" with the given thread ID and display results.
\r
324 @@ -837,7 +1027,9 @@ function is used. "
\r
325 ;; Set the header line to the subject of the first open message.
\r
326 (setq header-line-format (notmuch-show-strip-re (notmuch-show-get-subject)))
\r
328 - (notmuch-show-mark-read)))
\r
329 + (notmuch-show-mark-read)
\r
330 + (when notmuch-always-show-outline
\r
331 + (notmuch-show-outline))))
\r
333 (defvar notmuch-show-stash-map
\r
334 (let ((map (make-sparse-keymap)))
\r
335 @@ -879,6 +1071,7 @@ function is used. "
\r
336 (define-key map "P" 'notmuch-show-previous-message)
\r
337 (define-key map "n" 'notmuch-show-next-open-message)
\r
338 (define-key map "p" 'notmuch-show-previous-open-message)
\r
339 + (define-key map "o" 'notmuch-show-outline)
\r
340 (define-key map (kbd "DEL") 'notmuch-show-rewind)
\r
341 (define-key map " " 'notmuch-show-advance-and-archive)
\r
342 (define-key map (kbd "M-RET") 'notmuch-show-open-or-close-all)
\r