]> git.notmuchmail.org Git - notmuch/blob - emacs/notmuch-wash.el
e8134bf77c5002d16b590ad00590c3d915006232
[notmuch] / emacs / notmuch-wash.el
1 ;; notmuch-wash.el --- cleaning up message bodies
2 ;;
3 ;; Copyright © Carl Worth
4 ;; Copyright © David Edmondson
5 ;;
6 ;; This file is part of Notmuch.
7 ;;
8 ;; Notmuch is free software: you can redistribute it and/or modify it
9 ;; under the terms of the GNU General Public License as published by
10 ;; the Free Software Foundation, either version 3 of the License, or
11 ;; (at your option) any later version.
12 ;;
13 ;; Notmuch is distributed in the hope that it will be useful, but
14 ;; WITHOUT ANY WARRANTY; without even the implied warranty of
15 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16 ;; General Public License for more details.
17 ;;
18 ;; You should have received a copy of the GNU General Public License
19 ;; along with Notmuch.  If not, see <http://www.gnu.org/licenses/>.
20 ;;
21 ;; Authors: Carl Worth <cworth@cworth.org>
22 ;;          David Edmondson <dme@dme.org>
23
24 (require 'coolj)
25
26 (declare-function notmuch-show-insert-bodypart "notmuch-show" (msg part depth))
27
28 ;;
29
30 (defvar notmuch-wash-signature-regexp
31   "^\\(-- ?\\|_+\\)$"
32   "Pattern to match a line that separates content from signature.")
33
34 (defvar notmuch-wash-citation-regexp
35   "\\(^[[:space:]]*>.*\n\\)+"
36   "Pattern to match citation lines.")
37
38 (defvar notmuch-wash-button-signature-hidden-format
39   "[ %d-line signature. Click/Enter to show. ]"
40   "String used to construct button text for hidden signatures.
41 Can use up to one integer format parameter, i.e. %d")
42
43 (defvar notmuch-wash-button-signature-visible-format
44   "[ %d-line signature. Click/Enter to hide. ]"
45   "String used to construct button text for visible signatures.
46 Can use up to one integer format parameter, i.e. %d")
47
48 (defvar notmuch-wash-button-citation-hidden-format
49   "[ %d more citation lines. Click/Enter to show. ]"
50   "String used to construct button text for hidden citations.
51 Can use up to one integer format parameter, i.e. %d")
52
53 (defvar notmuch-wash-button-citation-visible-format
54   "[ %d more citation lines. Click/Enter to hide. ]"
55   "String used to construct button text for visible citations.
56 Can use up to one integer format parameter, i.e. %d")
57
58 (defvar notmuch-wash-signature-lines-max 12
59   "Maximum length of signature that will be hidden by default.")
60
61 (defvar notmuch-wash-citation-lines-prefix 3
62   "Always show at least this many lines from the start of a citation.
63
64 If there is one more line than the sum of
65 `notmuch-wash-citation-lines-prefix' and
66 `notmuch-wash-citation-lines-suffix', show that, otherwise
67 collapse the remaining lines into a button.")
68
69 (defvar notmuch-wash-citation-lines-suffix 3
70   "Always show at least this many lines from the end of a citation.
71
72 If there is one more line than the sum of
73 `notmuch-wash-citation-lines-prefix' and
74 `notmuch-wash-citation-lines-suffix', show that, otherwise
75 collapse the remaining lines into a button.")
76
77 (defun notmuch-wash-toggle-invisible-action (cite-button)
78   (let ((invis-spec (button-get cite-button 'invisibility-spec)))
79     (if (invisible-p invis-spec)
80         (remove-from-invisibility-spec invis-spec)
81       (add-to-invisibility-spec invis-spec)))
82   (let* ((new-start (button-start cite-button))
83          (overlay (button-get cite-button 'overlay))
84          (button-label (notmuch-wash-button-label overlay))
85          (old-point (point))
86          (inhibit-read-only t))
87     (goto-char new-start)
88     (insert button-label)
89     (let ((old-end (button-end cite-button)))
90       (move-overlay cite-button new-start (point))
91       (delete-region (point) old-end))
92     (goto-char (min old-point (1- (button-end cite-button)))))
93   (force-window-update)
94   (redisplay t))
95
96 (define-button-type 'notmuch-wash-button-invisibility-toggle-type
97   'action 'notmuch-wash-toggle-invisible-action
98   'follow-link t
99   'face 'font-lock-comment-face)
100
101 (define-button-type 'notmuch-wash-button-citation-toggle-type
102   'help-echo "mouse-1, RET: Show citation"
103   :supertype 'notmuch-wash-button-invisibility-toggle-type)
104
105 (define-button-type 'notmuch-wash-button-signature-toggle-type
106   'help-echo "mouse-1, RET: Show signature"
107   :supertype 'notmuch-wash-button-invisibility-toggle-type)
108
109 (defun notmuch-wash-region-isearch-show (overlay)
110   (remove-from-invisibility-spec (overlay-get overlay 'invisible)))
111
112 (defun notmuch-wash-button-label (overlay)
113   (let* ((type (overlay-get overlay 'type))
114          (invis-spec (overlay-get overlay 'invisible))
115          (state (if (invisible-p invis-spec) "hidden" "visible"))
116          (label-format (symbol-value (intern-soft (concat "notmuch-wash-button-"
117                                                           type "-" state "-format"))))
118          (lines-count (count-lines (overlay-start overlay) (overlay-end overlay))))
119     (format label-format lines-count)))
120
121 (defun notmuch-wash-region-to-button (beg end type prefix)
122   "Auxilary function to do the actual making of overlays and buttons
123
124 BEG and END are buffer locations. TYPE should a string, either
125 \"citation\" or \"signature\". PREFIX is some arbitrary text to
126 insert before the button, probably for indentation."
127
128   ;; This uses some slightly tricky conversions between strings and
129   ;; symbols because of the way the button code works. Note that
130   ;; replacing intern-soft with make-symbol will cause this to fail,
131   ;; since the newly created symbol has no plist.
132
133   (let ((overlay (make-overlay beg end))
134         (invis-spec (make-symbol (concat "notmuch-" type "-region")))
135         (button-type (intern-soft (concat "notmuch-wash-button-"
136                                           type "-toggle-type"))))
137     (add-to-invisibility-spec invis-spec)
138     (overlay-put overlay 'invisible invis-spec)
139     (overlay-put overlay 'isearch-open-invisible #'notmuch-wash-region-isearch-show)
140     (overlay-put overlay 'type type)
141     (goto-char (1+ end))
142     (save-excursion
143       (goto-char (1- beg))
144       (insert prefix)
145       (insert-button (notmuch-wash-button-label overlay)
146                      'invisibility-spec invis-spec
147                      'overlay overlay
148                      :type button-type))))
149
150 (defun notmuch-wash-excerpt-citations (depth)
151   "Excerpt citations and up to one signature."
152   (goto-char (point-min))
153   (beginning-of-line)
154   (while (and (< (point) (point-max))
155               (re-search-forward notmuch-wash-citation-regexp nil t))
156     (let* ((cite-start (match-beginning 0))
157            (cite-end (match-end 0))
158            (cite-lines (count-lines cite-start cite-end)))
159       (overlay-put (make-overlay cite-start cite-end) 'face 'message-cited-text-face)
160       (when (> cite-lines (+ notmuch-wash-citation-lines-prefix
161                              notmuch-wash-citation-lines-suffix
162                              1))
163         (goto-char cite-start)
164         (forward-line notmuch-wash-citation-lines-prefix)
165         (let ((hidden-start (point-marker)))
166           (goto-char cite-end)
167           (forward-line (- notmuch-wash-citation-lines-suffix))
168           (notmuch-wash-region-to-button
169            hidden-start (point-marker)
170            "citation" "\n")))))
171   (if (and (not (eobp))
172            (re-search-forward notmuch-wash-signature-regexp nil t))
173       (let* ((sig-start (match-beginning 0))
174              (sig-end (match-end 0))
175              (sig-lines (count-lines sig-start (point-max))))
176         (if (<= sig-lines notmuch-wash-signature-lines-max)
177             (let ((sig-start-marker (make-marker))
178                   (sig-end-marker (make-marker)))
179               (set-marker sig-start-marker sig-start)
180               (set-marker sig-end-marker (point-max))
181               (overlay-put (make-overlay sig-start-marker sig-end-marker) 'face 'message-cited-text-face)
182               (notmuch-wash-region-to-button
183                sig-start-marker sig-end-marker
184                "signature" "\n"))))))
185
186 ;;
187
188 (defun notmuch-wash-elide-blank-lines (depth)
189   "Elide leading, trailing and successive blank lines."
190
191   ;; Algorithm derived from `article-strip-multiple-blank-lines' in
192   ;; `gnus-art.el'.
193
194   ;; Make all blank lines empty.
195   (goto-char (point-min))
196   (while (re-search-forward "^[[:space:]\t]+$" nil t)
197     (replace-match "" nil t))
198
199   ;; Replace multiple empty lines with a single empty line.
200   (goto-char (point-min))
201   (while (re-search-forward "^\n\\(\n+\\)" nil t)
202     (delete-region (match-beginning 1) (match-end 1)))
203
204   ;; Remove a leading blank line.
205   (goto-char (point-min))
206   (if (looking-at "\n")
207       (delete-region (match-beginning 0) (match-end 0)))
208
209   ;; Remove a trailing blank line.
210   (goto-char (point-max))
211   (if (looking-at "\n")
212       (delete-region (match-beginning 0) (match-end 0))))
213
214 ;;
215
216 (defun notmuch-wash-tidy-citations (depth)
217   "Improve the display of cited regions of a message.
218
219 Perform several transformations on the message body:
220
221 - Remove lines of repeated citation leaders with no other
222   content,
223 - Remove citation leaders standing alone before a block of cited
224   text,
225 - Remove citation trailers standing alone after a block of cited
226   text."
227
228   ;; Remove lines of repeated citation leaders with no other content.
229   (goto-char (point-min))
230   (while (re-search-forward "\\(^>[> ]*\n\\)\\{2,\\}" nil t)
231     (replace-match "\\1"))
232
233   ;; Remove citation leaders standing alone before a block of cited
234   ;; text.
235   (goto-char (point-min))
236   (while (re-search-forward "\\(\n\\|^[^>].*\\)\n\\(^>[> ]*\n\\)" nil t)
237     (replace-match "\\1\n"))
238
239   ;; Remove citation trailers standing alone after a block of cited
240   ;; text.
241   (goto-char (point-min))
242   (while (re-search-forward "\\(^>[> ]*\n\\)\\(^$\\|^[^>].*\\)" nil t)
243     (replace-match "\\2")))
244
245 ;;
246
247 (defun notmuch-wash-wrap-long-lines (depth)
248   "Wrap any long lines in the message to the width of the window.
249
250 When doing so, maintaining citation leaders in the wrapped text."
251
252   (let ((coolj-wrap-follows-window-size nil)
253         (fill-column (- (window-width)
254                         depth
255                         ;; 2 to avoid poor interaction with
256                         ;; `word-wrap'.
257                         2)))
258     (coolj-wrap-region (point-min) (point-max))))
259
260 ;;
261
262 (require 'diff-mode)
263
264 (defvar diff-file-header-re) ; From `diff-mode.el'.
265
266 (defun notmuch-wash-convert-inline-patch-to-part (depth)
267   "Convert an inline patch into a fake 'text/x-diff' attachment.
268
269 Given that this function guesses whether a buffer includes a
270 patch and then guesses the extent of the patch, there is scope
271 for error."
272
273   (goto-char (point-min))
274   (if (re-search-forward diff-file-header-re nil t)
275       (progn
276         (beginning-of-line -1)
277         (let ((patch-start (point))
278               (patch-end (point-max))
279               part)
280           (goto-char patch-start)
281           (if (or
282                ;; Patch ends with signature.
283                (re-search-forward notmuch-wash-signature-regexp nil t)
284                ;; Patch ends with bugtraq comment.
285                (re-search-forward "^\\*\\*\\* " nil t))
286               (setq patch-end (match-beginning 0)))
287           (save-restriction
288             (narrow-to-region patch-start patch-end)
289             (setq part (plist-put part :content-type "text/x-diff"))
290             (setq part (plist-put part :content (buffer-string)))
291             (setq part (plist-put part :id -1))
292             (setq part (plist-put part :filename "inline patch"))
293             (delete-region (point-min) (point-max))
294             (notmuch-show-insert-bodypart nil part depth))))))
295
296 ;;
297
298 (provide 'notmuch-wash)