1 Return-Path: <m.walters@qmul.ac.uk>
\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 21BA8431FC0
\r
6 for <notmuch@notmuchmail.org>; Sat, 22 Mar 2014 04:44:35 -0700 (PDT)
\r
7 X-Virus-Scanned: Debian amavisd-new at olra.theworths.org
\r
11 X-Spam-Status: No, score=-1.098 tagged_above=-999 required=5
\r
12 tests=[DKIM_ADSP_CUSTOM_MED=0.001, FREEMAIL_FROM=0.001,
\r
13 NML_ADSP_CUSTOM_MED=1.2, RCVD_IN_DNSWL_MED=-2.3] 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 MV3vWZ7vlNgX for <notmuch@notmuchmail.org>;
\r
17 Sat, 22 Mar 2014 04:44:27 -0700 (PDT)
\r
18 Received: from mail2.qmul.ac.uk (mail2.qmul.ac.uk [138.37.6.6])
\r
19 (using TLSv1 with cipher DHE-RSA-AES256-SHA (256/256 bits))
\r
20 (No client certificate requested)
\r
21 by olra.theworths.org (Postfix) with ESMTPS id 09C36431FBC
\r
22 for <notmuch@notmuchmail.org>; Sat, 22 Mar 2014 04:44:27 -0700 (PDT)
\r
23 Received: from smtp.qmul.ac.uk ([138.37.6.40])
\r
24 by mail2.qmul.ac.uk with esmtp (Exim 4.71)
\r
25 (envelope-from <m.walters@qmul.ac.uk>)
\r
26 id 1WRKLc-00060L-Kt; Sat, 22 Mar 2014 11:44:21 +0000
\r
27 Received: from 93-97-24-31.zone5.bethere.co.uk ([93.97.24.31] helo=localhost)
\r
28 by smtp.qmul.ac.uk with esmtpsa (TLSv1:AES128-SHA:128) (Exim 4.71)
\r
29 (envelope-from <m.walters@qmul.ac.uk>)
\r
30 id 1WRKLb-00046f-T7; Sat, 22 Mar 2014 11:44:20 +0000
\r
31 From: Mark Walters <markwalters1009@gmail.com>
\r
32 To: Austin Clements <amdragon@MIT.EDU>
\r
33 Subject: Re: [Patch v3 5/8] emacs: show: mark tags changed since buffer loaded
\r
34 In-Reply-To: <87pplek4v0.fsf@qmul.ac.uk>
\r
35 References: <1394597397-8486-1-git-send-email-markwalters1009@gmail.com>
\r
36 <1394597397-8486-6-git-send-email-markwalters1009@gmail.com>
\r
37 <20140322033933.GC31187@mit.edu> <87pplek4v0.fsf@qmul.ac.uk>
\r
38 User-Agent: Notmuch/0.15.2+615~g78e3a93 (http://notmuchmail.org) Emacs/23.4.1
\r
39 (x86_64-pc-linux-gnu)
\r
40 Date: Sat, 22 Mar 2014 11:44:18 +0000
\r
41 Message-ID: <87mwgih2zx.fsf@qmul.ac.uk>
\r
43 Content-Type: text/plain; charset=us-ascii
\r
44 X-Sender-Host-Address: 93.97.24.31
\r
45 X-QM-Geographic: According to ripencc,
\r
46 this message was delivered by a machine in Britain (UK) (GB).
\r
47 X-QM-SPAM-Info: Sender has good ham record. :)
\r
48 X-QM-Body-MD5: fea0858007c2b9064a64c69cb1ffd51b (of first 20000 bytes)
\r
49 X-SpamAssassin-Score: 0.0
\r
50 X-SpamAssassin-SpamBar: /
\r
51 X-SpamAssassin-Report: The QM spam filters have analysed this message to
\r
53 spam. We require at least 5.0 points to mark a message as spam.
\r
54 This message scored 0.0 points. Summary of the scoring:
\r
55 * 0.0 FREEMAIL_FROM Sender email is commonly abused enduser mail
\r
56 provider * (markwalters1009[at]gmail.com)
\r
57 * -0.0 T_RP_MATCHES_RCVD Envelope sender domain matches handover relay
\r
59 * 0.0 AWL AWL: From: address is in the auto white-list
\r
60 X-QM-Scan-Virus: ClamAV says the message is clean
\r
61 Cc: notmuch@notmuchmail.org
\r
62 X-BeenThere: notmuch@notmuchmail.org
\r
63 X-Mailman-Version: 2.1.13
\r
65 List-Id: "Use and development of the notmuch mail system."
\r
66 <notmuch.notmuchmail.org>
\r
67 List-Unsubscribe: <http://notmuchmail.org/mailman/options/notmuch>,
\r
68 <mailto:notmuch-request@notmuchmail.org?subject=unsubscribe>
\r
69 List-Archive: <http://notmuchmail.org/pipermail/notmuch>
\r
70 List-Post: <mailto:notmuch@notmuchmail.org>
\r
71 List-Help: <mailto:notmuch-request@notmuchmail.org?subject=help>
\r
72 List-Subscribe: <http://notmuchmail.org/mailman/listinfo/notmuch>,
\r
73 <mailto:notmuch-request@notmuchmail.org?subject=subscribe>
\r
74 X-List-Received-Date: Sat, 22 Mar 2014 11:44:35 -0000
\r
77 On Sat, 22 Mar 2014, Mark Walters <markwalters1009@gmail.com> wrote:
\r
80 > Ok I have something working based on your version below. I will post
\r
81 > once I have tested a little more. Since the diff with my version will be
\r
82 > quite big I will just comment on the differences from your version.
\r
84 > On Sat, 22 Mar 2014, Austin Clements <amdragon@MIT.EDU> wrote:
\r
85 >> Quoth Mark Walters on Mar 12 at 4:09 am:
\r
86 >>> This allows (and requires) the original-tags to be passed along with
\r
87 >>> the current-tags to be passed to notmuch-tag-format-tags. This allows
\r
88 >>> the tag formatting to show added and deleted tags.By default a removed
\r
89 >>> tag is displayed with strike-through in red (if strike-through is not
\r
90 >>> available, eg on a terminal, inverse video is used instead) and an
\r
91 >>> added tag is displayed underlined in green.
\r
93 >>> If the caller does not wish to use the new feature it can pass
\r
94 >>> current-tags for both arguments and, at this point, we do exactly that
\r
95 >>> in the three callers of this function.
\r
97 >>> Note, we cannot tidily allow original-tags to be optional because we would
\r
98 >>> need to distinguish nil meaning "we are not specifying original-tags"
\r
99 >>> from nil meaning there were no original-tags (an empty list).
\r
101 >>> We use this in subsequent patches to make it clear when a message was
\r
102 >>> unread when you first loaded a show buffer (previously the unread tag
\r
103 >>> could be removed before a user realised that it had been unread).
\r
105 >>> The code adds into the existing tag formatting code. The user can
\r
106 >>> specify exactly how a tag should be displayed normally, when deleted,
\r
109 >>> Since the formatting code matches regexps a user can match all deleted
\r
110 >>> tags with a ".*" in notmuch-tag-deleted-formats. For example setting
\r
111 >>> notmuch-tag-deleted-formats to '((".*" nil)) tells notmuch not to show
\r
112 >>> deleted tags at all.
\r
114 >>> All the variables are customizable; however, more complicated cases
\r
115 >>> like changing the face depending on the type of display will require
\r
118 >>> Currently this overrides notmuch-tag-deleted-formats for the tests
\r
119 >>> setting it to '((".*" nil)) so that they get removed from the display
\r
120 >>> and, thus, all tests still pass.
\r
122 >>> emacs/notmuch-show.el | 4 +-
\r
123 >>> emacs/notmuch-tag.el | 72 +++++++++++++++++++++++++++++++++++-------------
\r
124 >>> emacs/notmuch-tree.el | 2 +-
\r
125 >>> emacs/notmuch.el | 2 +-
\r
126 >>> test/test-lib.el | 5 +++
\r
127 >>> 5 files changed, 61 insertions(+), 24 deletions(-)
\r
129 >>> diff --git a/emacs/notmuch-show.el b/emacs/notmuch-show.el
\r
130 >>> index 019f51d..5492be4 100644
\r
131 >>> --- a/emacs/notmuch-show.el
\r
132 >>> +++ b/emacs/notmuch-show.el
\r
133 >>> @@ -344,7 +344,7 @@ (defun notmuch-show-update-tags (tags)
\r
134 >>> (if (re-search-forward "(\\([^()]*\\))$" (line-end-position) t)
\r
135 >>> (let ((inhibit-read-only t))
\r
136 >>> (replace-match (concat "("
\r
137 >>> - (notmuch-tag-format-tags tags)
\r
138 >>> + (notmuch-tag-format-tags tags tags)
\r
141 >>> (defun notmuch-clean-address (address)
\r
142 >>> @@ -423,7 +423,7 @@ (defun notmuch-show-insert-headerline (headers date tags depth)
\r
146 >>> - (notmuch-tag-format-tags tags)
\r
147 >>> + (notmuch-tag-format-tags tags tags)
\r
149 >>> (overlay-put (make-overlay start (point)) 'face 'notmuch-message-summary-face)))
\r
151 >>> diff --git a/emacs/notmuch-tag.el b/emacs/notmuch-tag.el
\r
152 >>> index 4698856..cfccb8e 100644
\r
153 >>> --- a/emacs/notmuch-tag.el
\r
154 >>> +++ b/emacs/notmuch-tag.el
\r
155 >>> @@ -184,45 +184,77 @@ (defun notmuch-tag-clear-cache ()
\r
156 >>> "Clear the internal cache of tag formats."
\r
157 >>> (clrhash notmuch-tag--format-cache))
\r
159 >>> -(defun notmuch-tag-format-tag (tag)
\r
160 >>> - "Format TAG by according to `notmuch-tag-formats'.
\r
162 >>> -Callers must ensure that the tag format cache has been recently cleared
\r
163 >>> -via `notmuch-tag-clear-cache' before using this function. For example,
\r
164 >>> -it would be appropriate to clear the cache just prior to filling a
\r
165 >>> -buffer that uses formatted tags."
\r
167 >>> - (let ((formatted (gethash tag notmuch-tag--format-cache 'missing)))
\r
168 >>> +(defun notmuch-tag-format-tag-by-state (tag formatted-tag tag-state)
\r
169 >>> + "Format TAG according to the appropriate `notmuch-tag-formats`.
\r
171 >>> +Applies formats for TAG from the appropriate one of
\r
172 >>> +`notmuch-tag-formats`, `notmuch-tag-deleted-formats` and
\r
173 >>> +`notmuch-tag-added-formats` based on TAG-STATE to the partially
\r
175 >> The second ` should be a ' on all four of the above references.
\r
177 >>> +formatted tag FORMATTED-TAG."
\r
178 >>> + (let ((formatted (gethash (cons tag tag-state) notmuch-tag--format-cache 'missing)))
\r
180 >> Something's strange here. If this hits in the cache, it will ignore
\r
181 >> formatted-tag. I can't actually construct a situation where this does
\r
182 >> the wrong thing, but it always seems to do the right thing for the
\r
185 >> This code would make a lot more sense to me if it were turned
\r
186 >> inside-out with `notmuch-tag-format-tag':
\r
188 >> (defun notmuch-tag-format-tag (tags orig-tags tag)
\r
189 >> "Format TAG according to `notmuch-tag-formats'.
\r
191 >> TAGS and ORIG-TAGS are lists of the current tags and the original
\r
192 >> tags; tags which have been deleted (i.e., are in ORIG-TAGS but
\r
193 >> are not in TAGS) are shown using formats from
\r
194 >> `notmuch-tag-deleted-formats'; tags which have been added (i.e.,
\r
195 >> are in TAGS but are not in ORIG-TAGS) are shown using formats
\r
196 >> from `notmuch-tag-added-formats' and tags which have not been
\r
197 >> changed (the normal case) are shown using formats from
\r
198 >> `notmuch-tag-formats'"
\r
199 >> (let* ((tag-state (cond ((not (member tag tags)) 'deleted)
\r
200 >> ((not (member tag orig-tags)) 'added)))
\r
201 >> (formatted (gethash (cons tag tag-state) notmuch-tag--format-cache
\r
203 >> (when (eq formatted 'missing)
\r
205 > I changed formatted to formatted-tag.
\r
207 >> (let ((base (notmuch-tag--get-formats tag notmuch-tag-formats))
\r
208 >> (over (case tag-state
\r
210 >> (deleted (notmuch-tag--get-formats
\r
211 >> tag notmuch-tag-deleted-formats))
\r
212 >> (added (notmuch-tag--get-formats
\r
213 >> tag notmuch-tag-deleted-formats)))))
\r
215 > I moved the nil case to an otherwise case (and corrected the typo of
\r
216 > deleted-formats to added-formats in the second clause).
\r
218 >> (setq formatted (notmuch-tag--do-format
\r
219 >> (notmuch-tag--do-format tag) base over))
\r
221 > I split this into two steps. I also slightly changed
\r
222 > notmuch-tag--do-format so it gets passed tag and formatted-tag
\r
224 >> (puthash (cons tag tag-state) formatted notmuch-tag--format-cache)))
\r
227 >> (defun notmuch-tag--get-formats (tag format-alist)
\r
228 >> "Find the first item whose car regexp-matches TAG."
\r
229 >> (save-match-data
\r
230 >> ;; Don't use assoc-default since there's no way to distinguish a
\r
231 >> ;; missing key from a present key with a null cdr.
\r
232 >> (assoc* tag format-alist
\r
233 >> :test (lambda (tag key)
\r
234 >> (and (eq (string-match key tag) 0)
\r
235 >> (= (match-end 0) (length tag)))))))
\r
237 > I have not changed this.
\r
239 >> (defun notmuch-tag--do-format (tag formats)
\r
240 >> "Apply a tag-formats entry to TAG."
\r
242 > We need to pass formatted-tag as well as the original unformatted tag
\r
243 > because we want to do the look up to decide what to do based on the
\r
244 > original tag not whatever it has become.
\r
246 Ok I have realised I was wrong and your version was fine (as the choice
\r
247 of formats was already made). However, I do still pass the unformatted
\r
248 tag so that the formatter can access that as `bare-tag'.
\r
250 >> (cond ((null formats) ;; - Tag not in `formats',
\r
251 >> tag) ;; the format is the tag itself.
\r
252 >> ((null (cdr formats)) ;; - Tag was deliberately hidden,
\r
253 >> nil) ;; no format must be returned
\r
255 >> ;; Tag was found and has formats, we must apply all the
\r
256 >> ;; formats. TAG may be null so treat that as a special case.
\r
257 >> (let ((old-tag tag) (tag (or tag "")))
\r
258 >> (dolist (format (cdr formats))
\r
259 >> (setq tag (eval format)))
\r
260 >> (if (and (null old-tag) (equal tag ""))
\r
264 More importantly I was getting (erratic) weird effects because some of
\r
265 these changes changed tag (and even the copy of tag outside this
\r
266 function). I fixed this by using copy-sequence for the second let
\r
274 > This got slightly tweaked because of the formatted/original split above.
\r
277 >> (Completely untested and all indented with spaces and probably
\r
278 >> incorrectly because I wrote it all in my email buffer, but you get the
\r
281 > And I think I fixed all the whitespace
\r
283 > This looks much nicer than my version. Thanks!
\r
292 >>> (when (eq formatted 'missing)
\r
293 >>> - (let* ((formats
\r
294 >>> + (let* ((tag-formats (case tag-state
\r
295 >>> + ((list nil) notmuch-tag-formats)
\r
297 >> While this isn't *technically* wrong, I don't think you meant to
\r
298 >> accept a tag-state of 'list. Should be
\r
301 >> ((nil) notmuch-tag-formats)
\r
304 >>> + (deleted notmuch-tag-deleted-formats)
\r
305 >>> + (added notmuch-tag-added-formats)))
\r
307 >>> (save-match-data
\r
308 >>> ;; Don't use assoc-default since there's no way to
\r
309 >>> ;; distinguish a missing key from a present key with a
\r
311 >>> - (assoc* tag notmuch-tag-formats
\r
312 >>> + (assoc* tag tag-formats
\r
313 >>> :test (lambda (tag key)
\r
314 >>> (and (eq (string-match key tag) 0)
\r
315 >>> (= (match-end 0) (length tag))))))))
\r
316 >>> (setq formatted
\r
318 >>> - ((null formats) ;; - Tag not in `notmuch-tag-formats',
\r
319 >>> - tag) ;; the format is the tag itself.
\r
320 >>> + ((null formats) ;; - Tag not in `tag-formats',
\r
321 >>> + formatted-tag) ;; the format is the tag itself.
\r
322 >>> ((null (cdr formats)) ;; - Tag was deliberately hidden,
\r
323 >>> nil) ;; no format must be returned
\r
324 >>> - (t ;; - Tag was found and has formats,
\r
325 >>> - (let ((tag tag)) ;; we must apply all the formats.
\r
327 >>> + ;; Tag was found and has formats, we must apply all
\r
328 >>> + ;; the formats. FORMATTED-TAG may be null so treat
\r
329 >>> + ;; that as a special case.
\r
330 >>> + (let ((tag (or formatted-tag "")))
\r
331 >>> (dolist (format (cdr formats) tag)
\r
332 >>> - (setq tag (eval format)))))))
\r
333 >>> - (puthash tag formatted notmuch-tag--format-cache)))
\r
334 >>> + (setq tag (eval format)))
\r
335 >>> + (if (and (null formatted-tag)
\r
336 >>> + (equal tag ""))
\r
339 >>> + (puthash (cons tag tag-state) formatted notmuch-tag--format-cache)))
\r
342 >>> -(defun notmuch-tag-format-tags (tags &optional face)
\r
343 >>> +(defun notmuch-tag-format-tag (tags orig-tags tag)
\r
344 >>> + "Format TAG according to `notmuch-tag-formats'.
\r
346 >>> +TAGS and ORIG-TAGS are lists of the current tags and the original
\r
347 >>> +tags; tags which have been deleted (i.e., are in ORIG-TAGS but
\r
348 >>> +are not in TAGS) are shown using formats from
\r
349 >>> +`notmuch-tag-deleted-formats'; tags which have been added (i.e.,
\r
350 >>> +are in TAGS but are not in ORIG-TAGS) are shown using formats
\r
351 >>> +from `notmuch-tag-added-formats' and tags which have not been
\r
352 >>> +changed (the normal case) are shown using formats from
\r
353 >>> +`notmuch-tag-formats'"
\r
354 >>> + (let* ((formatted-tag (notmuch-tag-format-tag-by-state tag tag nil)))
\r
355 >>> + (cond ((not (member tag tags))
\r
356 >>> + (notmuch-tag-format-tag-by-state tag formatted-tag 'deleted))
\r
357 >>> + ((not (member tag orig-tags))
\r
358 >>> + (notmuch-tag-format-tag-by-state tag formatted-tag 'added))
\r
360 >>> + formatted-tag))))
\r
362 >>> +(defun notmuch-tag-format-tags (tags orig-tags &optional face)
\r
363 >>> "Return a string representing formatted TAGS."
\r
364 >>> - (let ((face (or face 'notmuch-tag-face)))
\r
365 >>> + (let ((face (or face 'notmuch-tag-face))
\r
366 >>> + (all-tags (sort (delete-dups (append tags orig-tags nil)) #'string<)))
\r
367 >>> (notmuch-apply-face
\r
368 >>> (mapconcat #'identity
\r
369 >>> ;; nil indicated that the tag was deliberately hidden
\r
370 >>> - (delq nil (mapcar #'notmuch-tag-format-tag tags))
\r
371 >>> + (delq nil (mapcar
\r
372 >>> + (apply-partially #'notmuch-tag-format-tag tags orig-tags)
\r
377 >>> diff --git a/emacs/notmuch-tree.el b/emacs/notmuch-tree.el
\r
378 >>> index c78d9de..8bf2fbc 100644
\r
379 >>> --- a/emacs/notmuch-tree.el
\r
380 >>> +++ b/emacs/notmuch-tree.el
\r
381 >>> @@ -704,7 +704,7 @@ (defun notmuch-tree-format-field (field format-string msg)
\r
382 >>> (face (if match
\r
383 >>> 'notmuch-tree-match-tag-face
\r
384 >>> 'notmuch-tree-no-match-tag-face)))
\r
385 >>> - (format format-string (notmuch-tag-format-tags tags face)))))))
\r
386 >>> + (format format-string (notmuch-tag-format-tags tags tags face)))))))
\r
388 >>> (defun notmuch-tree-format-field-list (field-list msg)
\r
389 >>> "Format fields of MSG according to FIELD-LIST and return string"
\r
390 >>> diff --git a/emacs/notmuch.el b/emacs/notmuch.el
\r
391 >>> index 93a6d8b..609f408 100644
\r
392 >>> --- a/emacs/notmuch.el
\r
393 >>> +++ b/emacs/notmuch.el
\r
394 >>> @@ -754,7 +754,7 @@ (defun notmuch-search-insert-field (field format-string result)
\r
396 >>> ((string-equal field "tags")
\r
397 >>> (let ((tags (plist-get result :tags)))
\r
398 >>> - (insert (format format-string (notmuch-tag-format-tags tags)))))))
\r
399 >>> + (insert (format format-string (notmuch-tag-format-tags tags tags)))))))
\r
401 >>> (defun notmuch-search-show-result (result &optional pos)
\r
402 >>> "Insert RESULT at POS or the end of the buffer if POS is null."
\r
403 >>> diff --git a/test/test-lib.el b/test/test-lib.el
\r
404 >>> index 37fcb3d..437f83f 100644
\r
405 >>> --- a/test/test-lib.el
\r
406 >>> +++ b/test/test-lib.el
\r
407 >>> @@ -165,3 +165,8 @@ (defun notmuch-test-expect-equal (output expected)
\r
410 >>> (notmuch-test-report-unexpected output expected)))))
\r
412 >>> +;; For historical reasons, we hide deleted tags by default in the test
\r
414 >>> +(setq notmuch-tag-deleted-formats
\r
415 >>> + '((".*" nil)))
\r