Commit | Line | Data |
---|---|---|
23f87bed | 1 | ;;; gnus-cite.el --- parse citations in articles for Gnus |
eec82323 | 2 | |
e84b4b86 | 3 | ;; Copyright (C) 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, |
114f9c96 | 4 | ;; 2004, 2005, 2006, 2007, 2008, 2009, 2010 Free Software Foundation, Inc. |
16409b0b GM |
5 | |
6 | ;; Author: Per Abhiddenware | |
7 | ||
8 | ;; This file is part of GNU Emacs. | |
9 | ||
5e809f55 | 10 | ;; GNU Emacs is free software: you can redistribute it and/or modify |
eec82323 | 11 | ;; it under the terms of the GNU General Public License as published by |
5e809f55 GM |
12 | ;; the Free Software Foundation, either version 3 of the License, or |
13 | ;; (at your option) any later version. | |
eec82323 LMI |
14 | |
15 | ;; GNU Emacs is distributed in the hope that it will be useful, | |
16 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of | |
17 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
18 | ;; GNU General Public License for more details. | |
19 | ||
20 | ;; You should have received a copy of the GNU General Public License | |
5e809f55 | 21 | ;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. |
eec82323 LMI |
22 | |
23 | ;;; Commentary: | |
24 | ||
25 | ;;; Code: | |
26 | ||
5ab7173c | 27 | (eval-when-compile (require 'cl)) |
01c52d31 MB |
28 | (eval-when-compile |
29 | (when (featurep 'xemacs) | |
30 | (require 'easy-mmode))) ; for `define-minor-mode' | |
5ab7173c | 31 | |
eec82323 | 32 | (require 'gnus) |
eec82323 | 33 | (require 'gnus-range) |
23f87bed MB |
34 | (require 'gnus-art) |
35 | (require 'message) ; for message-cite-prefix-regexp | |
eec82323 LMI |
36 | |
37 | ;;; Customization: | |
38 | ||
39 | (defgroup gnus-cite nil | |
40 | "Citation." | |
41 | :prefix "gnus-cite-" | |
42 | :link '(custom-manual "(gnus)Article Highlighting") | |
43 | :group 'gnus-article) | |
44 | ||
6748645f LMI |
45 | (defcustom gnus-cited-opened-text-button-line-format "%(%{[-]%}%)\n" |
46 | "Format of opened cited text buttons." | |
47 | :group 'gnus-cite | |
48 | :type 'string) | |
49 | ||
50 | (defcustom gnus-cited-closed-text-button-line-format "%(%{[+]%}%)\n" | |
51 | "Format of closed cited text buttons." | |
eec82323 LMI |
52 | :group 'gnus-cite |
53 | :type 'string) | |
54 | ||
55 | (defcustom gnus-cited-lines-visible nil | |
16409b0b GM |
56 | "The number of lines of hidden cited text to remain visible. |
57 | Or a pair (cons) of numbers which are the number of lines at the top | |
58 | and bottom of the text, respectively, to remain visible." | |
eec82323 LMI |
59 | :group 'gnus-cite |
60 | :type '(choice (const :tag "none" nil) | |
16409b0b GM |
61 | integer |
62 | (cons :tag "Top and Bottom" integer integer))) | |
eec82323 LMI |
63 | |
64 | (defcustom gnus-cite-parse-max-size 25000 | |
65 | "Maximum article size (in bytes) where parsing citations is allowed. | |
66 | Set it to nil to parse all articles." | |
67 | :group 'gnus-cite | |
68 | :type '(choice (const :tag "all" nil) | |
69 | integer)) | |
70 | ||
eec82323 LMI |
71 | (defcustom gnus-cite-max-prefix 20 |
72 | "Maximum possible length for a citation prefix." | |
73 | :group 'gnus-cite | |
74 | :type 'integer) | |
75 | ||
76 | (defcustom gnus-supercite-regexp | |
23f87bed | 77 | (concat "^\\(" message-cite-prefix-regexp "\\)? *" |
eec82323 | 78 | ">>>>> +\"\\([^\"\n]+\\)\" +==") |
6748645f | 79 | "*Regexp matching normal Supercite attribution lines. |
eec82323 LMI |
80 | The first grouping must match prefixes added by other packages." |
81 | :group 'gnus-cite | |
82 | :type 'regexp) | |
83 | ||
84 | (defcustom gnus-supercite-secondary-regexp "^.*\"\\([^\"\n]+\\)\" +==" | |
85 | "Regexp matching mangled Supercite attribution lines. | |
86 | The first regexp group should match the Supercite attribution." | |
87 | :group 'gnus-cite | |
88 | :type 'regexp) | |
89 | ||
90 | (defcustom gnus-cite-minimum-match-count 2 | |
91 | "Minimum number of identical prefixes before we believe it's a citation." | |
92 | :group 'gnus-cite | |
93 | :type 'integer) | |
94 | ||
23f87bed MB |
95 | ;; Some Microsoft products put in a citation that extends to the |
96 | ;; remainder of the message: | |
97 | ;; | |
98 | ;; -----Original Message----- | |
99 | ;; From: ... | |
100 | ;; To: ... | |
101 | ;; Sent: ... [date, in non-RFC-2822 format] | |
102 | ;; Subject: ... | |
103 | ;; | |
104 | ;; Cited message, with no prefixes | |
105 | ;; | |
106 | ;; The four headers are always the same. But note they are prone to | |
107 | ;; folding without additional indentation. | |
108 | ;; | |
109 | ;; Others use "----- Original Message -----" instead, and properly quote | |
110 | ;; the body using "> ". This style is handled without special cases. | |
111 | ||
6748645f | 112 | (defcustom gnus-cite-attribution-prefix |
23f87bed | 113 | "In article\\|in <\\|On \\(Mon\\|Tue\\|Wed\\|Thu\\|Fri\\|Sat\\|Sun\\),\\|----- ?Original Message ?-----" |
6748645f | 114 | "*Regexp matching the beginning of an attribution line." |
eec82323 LMI |
115 | :group 'gnus-cite |
116 | :type 'regexp) | |
117 | ||
118 | (defcustom gnus-cite-attribution-suffix | |
23f87bed | 119 | "\\(\\(wrote\\|writes\\|said\\|says\\|>\\)\\(:\\|\\.\\.\\.\\)\\|----- ?Original Message ?-----\\)[ \t]*$" |
6748645f | 120 | "*Regexp matching the end of an attribution line. |
eec82323 LMI |
121 | The text matching the first grouping will be used as a button." |
122 | :group 'gnus-cite | |
123 | :type 'regexp) | |
124 | ||
23f87bed MB |
125 | (defcustom gnus-cite-unsightly-citation-regexp |
126 | "^-----Original Message-----\nFrom: \\(.+\n\\)+\n" | |
127 | "Regexp matching Microsoft-type rest-of-message citations." | |
bf247b6e | 128 | :version "22.1" |
23f87bed MB |
129 | :group 'gnus-cite |
130 | :type 'regexp) | |
131 | ||
132 | (defcustom gnus-cite-ignore-quoted-from t | |
133 | "Non-nil means don't regard lines beginning with \">From \" as cited text. | |
134 | Those lines may have been quoted by MTAs in order not to mix up with | |
135 | the envelope From line." | |
bf247b6e | 136 | :version "22.1" |
23f87bed MB |
137 | :group 'gnus-cite |
138 | :type 'boolean) | |
139 | ||
0f49874b | 140 | (defface gnus-cite-attribution '((t (:italic t))) |
d0859c9a MB |
141 | "Face used for attribution lines." |
142 | :group 'gnus-cite) | |
0f49874b MB |
143 | ;; backward-compatibility alias |
144 | (put 'gnus-cite-attribution-face 'face-alias 'gnus-cite-attribution) | |
3d493bef | 145 | (put 'gnus-cite-attribution-face 'obsolete-face "22.1") |
eec82323 | 146 | |
0f49874b | 147 | (defcustom gnus-cite-attribution-face 'gnus-cite-attribution |
eec82323 LMI |
148 | "Face used for attribution lines. |
149 | It is merged with the face for the cited text belonging to the attribution." | |
bf247b6e | 150 | :version "22.1" |
eec82323 LMI |
151 | :group 'gnus-cite |
152 | :type 'face) | |
153 | ||
0f49874b MB |
154 | (defface gnus-cite-1 '((((class color) |
155 | (background dark)) | |
156 | (:foreground "light blue")) | |
157 | (((class color) | |
158 | (background light)) | |
159 | (:foreground "MidnightBlue")) | |
160 | (t | |
161 | (:italic t))) | |
d0859c9a MB |
162 | "Citation face." |
163 | :group 'gnus-cite) | |
0f49874b MB |
164 | ;; backward-compatibility alias |
165 | (put 'gnus-cite-face-1 'face-alias 'gnus-cite-1) | |
3d493bef | 166 | (put 'gnus-cite-face-1 'obsolete-face "22.1") |
0f49874b MB |
167 | |
168 | (defface gnus-cite-2 '((((class color) | |
169 | (background dark)) | |
170 | (:foreground "light cyan")) | |
171 | (((class color) | |
172 | (background light)) | |
173 | (:foreground "firebrick")) | |
174 | (t | |
175 | (:italic t))) | |
d0859c9a MB |
176 | "Citation face." |
177 | :group 'gnus-cite) | |
0f49874b MB |
178 | ;; backward-compatibility alias |
179 | (put 'gnus-cite-face-2 'face-alias 'gnus-cite-2) | |
3d493bef | 180 | (put 'gnus-cite-face-2 'obsolete-face "22.1") |
0f49874b MB |
181 | |
182 | (defface gnus-cite-3 '((((class color) | |
183 | (background dark)) | |
184 | (:foreground "light yellow")) | |
185 | (((class color) | |
186 | (background light)) | |
187 | (:foreground "dark green")) | |
188 | (t | |
189 | (:italic t))) | |
d0859c9a MB |
190 | "Citation face." |
191 | :group 'gnus-cite) | |
0f49874b MB |
192 | ;; backward-compatibility alias |
193 | (put 'gnus-cite-face-3 'face-alias 'gnus-cite-3) | |
3d493bef | 194 | (put 'gnus-cite-face-3 'obsolete-face "22.1") |
0f49874b MB |
195 | |
196 | (defface gnus-cite-4 '((((class color) | |
197 | (background dark)) | |
198 | (:foreground "light pink")) | |
199 | (((class color) | |
200 | (background light)) | |
201 | (:foreground "OrangeRed")) | |
202 | (t | |
203 | (:italic t))) | |
d0859c9a MB |
204 | "Citation face." |
205 | :group 'gnus-cite) | |
0f49874b MB |
206 | ;; backward-compatibility alias |
207 | (put 'gnus-cite-face-4 'face-alias 'gnus-cite-4) | |
3d493bef | 208 | (put 'gnus-cite-face-4 'obsolete-face "22.1") |
0f49874b MB |
209 | |
210 | (defface gnus-cite-5 '((((class color) | |
211 | (background dark)) | |
212 | (:foreground "pale green")) | |
213 | (((class color) | |
214 | (background light)) | |
215 | (:foreground "dark khaki")) | |
216 | (t | |
217 | (:italic t))) | |
d0859c9a MB |
218 | "Citation face." |
219 | :group 'gnus-cite) | |
0f49874b MB |
220 | ;; backward-compatibility alias |
221 | (put 'gnus-cite-face-5 'face-alias 'gnus-cite-5) | |
3d493bef | 222 | (put 'gnus-cite-face-5 'obsolete-face "22.1") |
0f49874b MB |
223 | |
224 | (defface gnus-cite-6 '((((class color) | |
225 | (background dark)) | |
226 | (:foreground "beige")) | |
227 | (((class color) | |
228 | (background light)) | |
229 | (:foreground "dark violet")) | |
230 | (t | |
231 | (:italic t))) | |
d0859c9a MB |
232 | "Citation face." |
233 | :group 'gnus-cite) | |
0f49874b MB |
234 | ;; backward-compatibility alias |
235 | (put 'gnus-cite-face-6 'face-alias 'gnus-cite-6) | |
3d493bef | 236 | (put 'gnus-cite-face-6 'obsolete-face "22.1") |
0f49874b MB |
237 | |
238 | (defface gnus-cite-7 '((((class color) | |
239 | (background dark)) | |
240 | (:foreground "orange")) | |
241 | (((class color) | |
242 | (background light)) | |
243 | (:foreground "SteelBlue4")) | |
244 | (t | |
245 | (:italic t))) | |
d0859c9a MB |
246 | "Citation face." |
247 | :group 'gnus-cite) | |
0f49874b MB |
248 | ;; backward-compatibility alias |
249 | (put 'gnus-cite-face-7 'face-alias 'gnus-cite-7) | |
3d493bef | 250 | (put 'gnus-cite-face-7 'obsolete-face "22.1") |
0f49874b MB |
251 | |
252 | (defface gnus-cite-8 '((((class color) | |
253 | (background dark)) | |
254 | (:foreground "magenta")) | |
255 | (((class color) | |
256 | (background light)) | |
257 | (:foreground "magenta")) | |
258 | (t | |
259 | (:italic t))) | |
d0859c9a MB |
260 | "Citation face." |
261 | :group 'gnus-cite) | |
0f49874b MB |
262 | ;; backward-compatibility alias |
263 | (put 'gnus-cite-face-8 'face-alias 'gnus-cite-8) | |
3d493bef | 264 | (put 'gnus-cite-face-8 'obsolete-face "22.1") |
0f49874b MB |
265 | |
266 | (defface gnus-cite-9 '((((class color) | |
267 | (background dark)) | |
268 | (:foreground "violet")) | |
269 | (((class color) | |
270 | (background light)) | |
271 | (:foreground "violet")) | |
272 | (t | |
273 | (:italic t))) | |
d0859c9a MB |
274 | "Citation face." |
275 | :group 'gnus-cite) | |
0f49874b MB |
276 | ;; backward-compatibility alias |
277 | (put 'gnus-cite-face-9 'face-alias 'gnus-cite-9) | |
3d493bef | 278 | (put 'gnus-cite-face-9 'obsolete-face "22.1") |
0f49874b MB |
279 | |
280 | (defface gnus-cite-10 '((((class color) | |
281 | (background dark)) | |
01c52d31 | 282 | (:foreground "plum1")) |
0f49874b MB |
283 | (((class color) |
284 | (background light)) | |
285 | (:foreground "medium purple")) | |
286 | (t | |
287 | (:italic t))) | |
d0859c9a MB |
288 | "Citation face." |
289 | :group 'gnus-cite) | |
0f49874b MB |
290 | ;; backward-compatibility alias |
291 | (put 'gnus-cite-face-10 'face-alias 'gnus-cite-10) | |
3d493bef | 292 | (put 'gnus-cite-face-10 'obsolete-face "22.1") |
0f49874b MB |
293 | |
294 | (defface gnus-cite-11 '((((class color) | |
295 | (background dark)) | |
296 | (:foreground "turquoise")) | |
297 | (((class color) | |
298 | (background light)) | |
299 | (:foreground "turquoise")) | |
300 | (t | |
301 | (:italic t))) | |
d0859c9a MB |
302 | "Citation face." |
303 | :group 'gnus-cite) | |
0f49874b MB |
304 | ;; backward-compatibility alias |
305 | (put 'gnus-cite-face-11 'face-alias 'gnus-cite-11) | |
3d493bef | 306 | (put 'gnus-cite-face-11 'obsolete-face "22.1") |
eec82323 LMI |
307 | |
308 | (defcustom gnus-cite-face-list | |
0f49874b | 309 | '(gnus-cite-1 gnus-cite-2 gnus-cite-3 gnus-cite-4 gnus-cite-5 gnus-cite-6 |
01c52d31 | 310 | gnus-cite-7 gnus-cite-8 gnus-cite-9 gnus-cite-10 gnus-cite-11) |
6748645f | 311 | "*List of faces used for highlighting citations. |
eec82323 LMI |
312 | |
313 | When there are citations from multiple articles in the same message, | |
314 | Gnus will try to give each citation from each article its own face. | |
315 | This should make it easier to see who wrote what." | |
316 | :group 'gnus-cite | |
01c52d31 MB |
317 | :type '(repeat face) |
318 | :set (lambda (symbol value) | |
319 | (prog1 | |
320 | (custom-set-default symbol value) | |
321 | (if (boundp 'gnus-message-max-citation-depth) | |
322 | (setq gnus-message-max-citation-depth (length value))) | |
323 | (if (boundp 'gnus-message-citation-keywords) | |
324 | (setq gnus-message-citation-keywords | |
325 | `((gnus-message-search-citation-line | |
326 | ,@(let ((list nil) | |
327 | (count 1)) | |
328 | (dolist (face value (nreverse list)) | |
329 | (push (list count (list 'quote face) 'prepend t) | |
330 | list) | |
331 | (setq count (1+ count))))))))))) | |
eec82323 LMI |
332 | |
333 | (defcustom gnus-cite-hide-percentage 50 | |
334 | "Only hide excess citation if above this percentage of the body." | |
335 | :group 'gnus-cite | |
336 | :type 'number) | |
337 | ||
338 | (defcustom gnus-cite-hide-absolute 10 | |
339 | "Only hide excess citation if above this number of lines in the body." | |
340 | :group 'gnus-cite | |
341 | :type 'integer) | |
342 | ||
eb1666e2 MB |
343 | (defcustom gnus-cite-blank-line-after-header t |
344 | "If non-nil, put a blank line between the citation header and the button." | |
345 | :group 'gnus-cite | |
346 | :type 'boolean) | |
347 | ||
23f87bed MB |
348 | ;; This has to go here because its default value depends on |
349 | ;; gnus-cite-face-list. | |
0f49874b | 350 | (defcustom gnus-article-boring-faces (cons 'gnus-signature gnus-cite-face-list) |
23f87bed MB |
351 | "List of faces that are not worth reading. |
352 | If an article has more pages below the one you are looking at, but | |
353 | nothing on those pages is a word of at least three letters that is not | |
354 | in a boring face, then the pages will be skipped." | |
355 | :type '(repeat face) | |
356 | :group 'gnus-article-hiding) | |
357 | ||
eec82323 LMI |
358 | ;;; Internal Variables: |
359 | ||
360 | (defvar gnus-cite-article nil) | |
6748645f | 361 | (defvar gnus-cite-overlay-list nil) |
eec82323 LMI |
362 | |
363 | (defvar gnus-cite-prefix-alist nil) | |
364 | ;; Alist of citation prefixes. | |
365 | ;; The cdr is a list of lines with that prefix. | |
366 | ||
367 | (defvar gnus-cite-attribution-alist nil) | |
368 | ;; Alist of attribution lines. | |
369 | ;; The car is a line number. | |
370 | ;; The cdr is the prefix for the citation started by that line. | |
371 | ||
372 | (defvar gnus-cite-loose-prefix-alist nil) | |
373 | ;; Alist of citation prefixes that have no matching attribution. | |
374 | ;; The cdr is a list of lines with that prefix. | |
375 | ||
376 | (defvar gnus-cite-loose-attribution-alist nil) | |
377 | ;; Alist of attribution lines that have no matching citation. | |
378 | ;; Each member has the form (WROTE IN PREFIX TAG), where | |
379 | ;; WROTE: is the attribution line number | |
380 | ;; IN: is the line number of the previous line if part of the same attribution, | |
381 | ;; PREFIX: Is the citation prefix of the attribution line(s), and | |
382 | ;; TAG: Is a Supercite tag, if any. | |
383 | ||
6748645f | 384 | (defvar gnus-cited-opened-text-button-line-format-alist |
eec82323 LMI |
385 | `((?b (marker-position beg) ?d) |
386 | (?e (marker-position end) ?d) | |
6748645f | 387 | (?n (count-lines beg end) ?d) |
eec82323 | 388 | (?l (- end beg) ?d))) |
6748645f LMI |
389 | (defvar gnus-cited-opened-text-button-line-format-spec nil) |
390 | (defvar gnus-cited-closed-text-button-line-format-alist | |
391 | gnus-cited-opened-text-button-line-format-alist) | |
392 | (defvar gnus-cited-closed-text-button-line-format-spec nil) | |
393 | ||
eec82323 LMI |
394 | |
395 | ;;; Commands: | |
396 | ||
01c52d31 | 397 | (defun gnus-article-highlight-citation (&optional force same-buffer) |
eec82323 LMI |
398 | "Highlight cited text. |
399 | Each citation in the article will be highlighted with a different face. | |
400 | The faces are taken from `gnus-cite-face-list'. | |
401 | Attribution lines are highlighted with the same face as the | |
0f49874b | 402 | corresponding citation merged with the face `gnus-cite-attribution'. |
eec82323 LMI |
403 | |
404 | Text is considered cited if at least `gnus-cite-minimum-match-count' | |
23f87bed | 405 | lines matches `message-cite-prefix-regexp' with the same prefix. |
eec82323 LMI |
406 | |
407 | Lines matching `gnus-cite-attribution-suffix' and perhaps | |
408 | `gnus-cite-attribution-prefix' are considered attribution lines." | |
409 | (interactive (list 'force)) | |
765abcce | 410 | (with-current-buffer (if same-buffer (current-buffer) gnus-article-buffer) |
eec82323 LMI |
411 | (gnus-cite-parse-maybe force) |
412 | (let ((buffer-read-only nil) | |
413 | (alist gnus-cite-prefix-alist) | |
414 | (faces gnus-cite-face-list) | |
415 | (inhibit-point-motion-hooks t) | |
416 | face entry prefix skip numbers number face-alist) | |
417 | ;; Loop through citation prefixes. | |
418 | (while alist | |
419 | (setq entry (car alist) | |
420 | alist (cdr alist) | |
421 | prefix (car entry) | |
422 | numbers (cdr entry) | |
423 | face (car faces) | |
424 | faces (or (cdr faces) gnus-cite-face-list) | |
425 | face-alist (cons (cons prefix face) face-alist)) | |
426 | (while numbers | |
427 | (setq number (car numbers) | |
428 | numbers (cdr numbers)) | |
429 | (and (not (assq number gnus-cite-attribution-alist)) | |
430 | (not (assq number gnus-cite-loose-attribution-alist)) | |
431 | (gnus-cite-add-face number prefix face)))) | |
432 | ;; Loop through attribution lines. | |
433 | (setq alist gnus-cite-attribution-alist) | |
434 | (while alist | |
435 | (setq entry (car alist) | |
436 | alist (cdr alist) | |
437 | number (car entry) | |
438 | prefix (cdr entry) | |
439 | skip (gnus-cite-find-prefix number) | |
440 | face (cdr (assoc prefix face-alist))) | |
441 | ;; Add attribution button. | |
16409b0b GM |
442 | (goto-char (point-min)) |
443 | (forward-line (1- number)) | |
eec82323 | 444 | (when (re-search-forward gnus-cite-attribution-suffix |
01c52d31 | 445 | (point-at-eol) |
eec82323 LMI |
446 | t) |
447 | (gnus-article-add-button (match-beginning 1) (match-end 1) | |
448 | 'gnus-cite-toggle prefix)) | |
449 | ;; Highlight attribution line. | |
450 | (gnus-cite-add-face number skip face) | |
451 | (gnus-cite-add-face number skip gnus-cite-attribution-face)) | |
452 | ;; Loop through attribution lines. | |
453 | (setq alist gnus-cite-loose-attribution-alist) | |
454 | (while alist | |
455 | (setq entry (car alist) | |
456 | alist (cdr alist) | |
457 | number (car entry) | |
458 | skip (gnus-cite-find-prefix number)) | |
459 | (gnus-cite-add-face number skip gnus-cite-attribution-face))))) | |
460 | ||
461 | (defun gnus-dissect-cited-text () | |
462 | "Dissect the article buffer looking for cited text." | |
765abcce | 463 | (with-current-buffer gnus-article-buffer |
16409b0b | 464 | (gnus-cite-parse-maybe nil t) |
eec82323 LMI |
465 | (let ((alist gnus-cite-prefix-alist) |
466 | prefix numbers number marks m) | |
467 | ;; Loop through citation prefixes. | |
468 | (while alist | |
469 | (setq numbers (pop alist) | |
470 | prefix (pop numbers)) | |
471 | (while numbers | |
472 | (setq number (pop numbers)) | |
473 | (goto-char (point-min)) | |
474 | (forward-line number) | |
475 | (push (cons (point-marker) "") marks) | |
476 | (while (and numbers | |
477 | (= (1- number) (car numbers))) | |
478 | (setq number (pop numbers))) | |
479 | (goto-char (point-min)) | |
480 | (forward-line (1- number)) | |
481 | (push (cons (point-marker) prefix) marks))) | |
482 | ;; Skip to the beginning of the body. | |
16409b0b | 483 | (article-goto-body) |
eec82323 LMI |
484 | (push (cons (point-marker) "") marks) |
485 | ;; Find the end of the body. | |
486 | (goto-char (point-max)) | |
487 | (gnus-article-search-signature) | |
488 | (push (cons (point-marker) "") marks) | |
489 | ;; Sort the marks. | |
6748645f | 490 | (setq marks (sort marks 'car-less-than-car)) |
eec82323 LMI |
491 | (let ((omarks marks)) |
492 | (setq marks nil) | |
493 | (while (cdr omarks) | |
494 | (if (= (caar omarks) (caadr omarks)) | |
495 | (progn | |
496 | (unless (equal (cdar omarks) "") | |
497 | (push (car omarks) marks)) | |
498 | (unless (equal (cdadr omarks) "") | |
499 | (push (cadr omarks) marks)) | |
500 | (unless (and (equal (cdar omarks) "") | |
501 | (equal (cdadr omarks) "") | |
502 | (not (cddr omarks))) | |
503 | (setq omarks (cdr omarks)))) | |
504 | (push (car omarks) marks)) | |
505 | (setq omarks (cdr omarks))) | |
506 | (when (car omarks) | |
507 | (push (car omarks) marks)) | |
508 | (setq marks (setq m (nreverse marks))) | |
509 | (while (cddr m) | |
510 | (if (and (equal (cdadr m) "") | |
511 | (equal (cdar m) (cdaddr m)) | |
512 | (goto-char (caadr m)) | |
513 | (forward-line 1) | |
514 | (= (point) (caaddr m))) | |
515 | (setcdr m (cdddr m)) | |
516 | (setq m (cdr m)))) | |
517 | marks)))) | |
518 | ||
389b76fa G |
519 | (defun gnus-article-fill-cited-long-lines () |
520 | (gnus-article-fill-cited-article nil t)) | |
521 | ||
522 | (defun gnus-article-fill-cited-article (&optional width long-lines) | |
eec82323 | 523 | "Do word wrapping in the current article. |
389b76fa G |
524 | If WIDTH (the numerical prefix), use that text width when |
525 | filling. If LONG-LINES, only fill sections that have lines | |
526 | longer than the frame width." | |
527 | (interactive "P") | |
765abcce | 528 | (with-current-buffer gnus-article-buffer |
eec82323 LMI |
529 | (let ((buffer-read-only nil) |
530 | (inhibit-point-motion-hooks t) | |
531 | (marks (gnus-dissect-cited-text)) | |
532 | (adaptive-fill-mode nil) | |
533 | (filladapt-mode nil) | |
534 | (fill-column (if width (prefix-numeric-value width) fill-column))) | |
535 | (save-restriction | |
536 | (while (cdr marks) | |
eec82323 LMI |
537 | (narrow-to-region (caar marks) (caadr marks)) |
538 | (let ((adaptive-fill-regexp | |
539 | (concat "^" (regexp-quote (cdar marks)) " *")) | |
23f87bed MB |
540 | (fill-prefix |
541 | (if (string= (cdar marks) "") "" | |
542 | (concat (cdar marks) " "))) | |
389b76fa | 543 | (do-fill (not long-lines)) |
23f87bed | 544 | use-hard-newlines) |
389b76fa G |
545 | (unless do-fill |
546 | (setq do-fill (gnus-article-foldable-buffer (cdar marks)))) | |
f939acf1 KY |
547 | ;; Note: the XEmacs version of `fill-region' inserts a newline |
548 | ;; unless the region ends with a newline. | |
389b76fa | 549 | (when do-fill |
9310f19d LMI |
550 | (if (not long-lines) |
551 | (fill-region (point-min) (point-max)) | |
552 | (goto-char (point-min)) | |
553 | (while (not (eobp)) | |
554 | (end-of-line) | |
f939acf1 KY |
555 | (when (prog1 |
556 | (> (current-column) (window-width)) | |
557 | (forward-line 1)) | |
9310f19d | 558 | (save-restriction |
f939acf1 KY |
559 | (narrow-to-region (line-beginning-position 0) (point)) |
560 | (fill-region (point-min) (point-max)))))))) | |
eec82323 LMI |
561 | (set-marker (caar marks) nil) |
562 | (setq marks (cdr marks))) | |
563 | (when marks | |
564 | (set-marker (caar marks) nil)) | |
565 | ;; All this information is now incorrect. | |
566 | (setq gnus-cite-prefix-alist nil | |
567 | gnus-cite-attribution-alist nil | |
568 | gnus-cite-loose-prefix-alist nil | |
a8151ef7 LMI |
569 | gnus-cite-loose-attribution-alist nil |
570 | gnus-cite-article nil))))) | |
eec82323 | 571 | |
389b76fa G |
572 | (defun gnus-article-foldable-buffer (prefix) |
573 | (let ((do-fill nil) | |
574 | columns) | |
575 | (goto-char (point-min)) | |
576 | (while (not (eobp)) | |
285cf7c8 LMI |
577 | (unless (> (length prefix) (- (point-max) (point))) |
578 | (forward-char (length prefix))) | |
389b76fa G |
579 | (skip-chars-forward " \t") |
580 | (unless (eolp) | |
581 | (let ((elem (assq (current-column) columns))) | |
582 | (unless elem | |
583 | (setq elem (cons (current-column) 0)) | |
584 | (push elem columns)) | |
585 | (setcdr elem (1+ (cdr elem))))) | |
586 | (end-of-line) | |
f939acf1 | 587 | (when (> (current-column) (window-width)) |
389b76fa G |
588 | (setq do-fill t)) |
589 | (forward-line 1)) | |
590 | (and do-fill | |
591 | ;; We know know that there are long lines here, but does this look | |
592 | ;; like code? Check for ragged edges on the left. | |
593 | (< (length columns) 3)))) | |
594 | ||
2cdd366f KY |
595 | (defun gnus-article-natural-long-line-p () |
596 | "Return true if the current line is long, and it's natural text." | |
597 | (save-excursion | |
598 | (beginning-of-line) | |
599 | (and | |
600 | ;; The line is long. | |
601 | (> (- (line-end-position) (line-beginning-position)) | |
f939acf1 | 602 | (window-width)) |
2cdd366f KY |
603 | ;; It doesn't start with spaces. |
604 | (not (looking-at " ")) | |
605 | ;; Not cited text. | |
606 | (let ((line-number (1+ (count-lines (point-min) (point)))) | |
607 | citep) | |
608 | (dolist (elem gnus-cite-prefix-alist) | |
609 | (when (member line-number (cdr elem)) | |
610 | (setq citep t))) | |
611 | (not citep))))) | |
612 | ||
eec82323 LMI |
613 | (defun gnus-article-hide-citation (&optional arg force) |
614 | "Toggle hiding of all cited text except attribution lines. | |
615 | See the documentation for `gnus-article-highlight-citation'. | |
616 | If given a negative prefix, always show; if given a positive prefix, | |
617 | always hide." | |
618 | (interactive (append (gnus-article-hidden-arg) (list 'force))) | |
6748645f LMI |
619 | (gnus-set-format 'cited-opened-text-button t) |
620 | (gnus-set-format 'cited-closed-text-button t) | |
765abcce SM |
621 | (with-current-buffer gnus-article-buffer |
622 | (let ((buffer-read-only nil) | |
623 | marks | |
624 | (inhibit-point-motion-hooks t) | |
625 | (props (nconc (list 'article-type 'cite) | |
626 | gnus-hidden-properties)) | |
627 | (point (point-min)) | |
628 | found beg end start) | |
629 | (while (setq point | |
630 | (text-property-any point (point-max) | |
631 | 'gnus-callback | |
632 | 'gnus-article-toggle-cited-text)) | |
633 | (setq found t) | |
634 | (goto-char point) | |
635 | (gnus-article-toggle-cited-text | |
636 | (get-text-property point 'gnus-data) arg) | |
637 | (forward-line 1) | |
638 | (setq point (point))) | |
639 | (unless found | |
640 | (setq marks (gnus-dissect-cited-text)) | |
641 | (while marks | |
642 | (setq beg nil | |
643 | end nil) | |
644 | (while (and marks (string= (cdar marks) "")) | |
645 | (setq marks (cdr marks))) | |
646 | (when marks | |
647 | (setq beg (caar marks))) | |
648 | (while (and marks (not (string= (cdar marks) ""))) | |
649 | (setq marks (cdr marks))) | |
650 | (when marks | |
eec82323 | 651 | (setq end (caar marks))) |
765abcce SM |
652 | ;; Skip past lines we want to leave visible. |
653 | (when (and beg end gnus-cited-lines-visible) | |
654 | (goto-char beg) | |
655 | (forward-line (if (consp gnus-cited-lines-visible) | |
656 | (car gnus-cited-lines-visible) | |
657 | gnus-cited-lines-visible)) | |
658 | (if (>= (point) end) | |
659 | (setq beg nil) | |
660 | (setq beg (point-marker)) | |
661 | (when (consp gnus-cited-lines-visible) | |
662 | (goto-char end) | |
663 | (forward-line (- (cdr gnus-cited-lines-visible))) | |
664 | (if (<= (point) beg) | |
665 | (setq beg nil) | |
16409b0b | 666 | (setq end (point-marker)))))) |
765abcce SM |
667 | (when (and beg end) |
668 | (gnus-add-wash-type 'cite) | |
669 | ;; We use markers for the end-points to facilitate later | |
670 | ;; wrapping and mangling of text. | |
671 | (setq beg (set-marker (make-marker) beg) | |
672 | end (set-marker (make-marker) end)) | |
673 | (gnus-add-text-properties-when 'article-type nil beg end props) | |
674 | (goto-char beg) | |
675 | (when (and gnus-cite-blank-line-after-header | |
676 | (not (save-excursion (search-backward "\n\n" nil t)))) | |
677 | (insert "\n")) | |
678 | (put-text-property | |
679 | (setq start (point-marker)) | |
680 | (progn | |
eec82323 LMI |
681 | (gnus-article-add-button |
682 | (point) | |
6748645f LMI |
683 | (progn (eval gnus-cited-closed-text-button-line-format-spec) |
684 | (point)) | |
685 | `gnus-article-toggle-cited-text | |
686 | (list (cons beg end) start)) | |
eec82323 | 687 | (point)) |
765abcce SM |
688 | 'article-type 'annotation) |
689 | (set-marker beg (point)))))))) | |
eec82323 | 690 | |
520aa572 SZ |
691 | (defun gnus-article-toggle-cited-text (args &optional arg) |
692 | "Toggle hiding the text in REGION. | |
693 | ARG can be nil or a number. Positive means hide, negative | |
694 | means show, nil means toggle." | |
6748645f | 695 | (let* ((region (car args)) |
16409b0b GM |
696 | (beg (car region)) |
697 | (end (cdr region)) | |
6748645f LMI |
698 | (start (cadr args)) |
699 | (hidden | |
520aa572 | 700 | (text-property-any beg (1- end) 'article-type 'cite)) |
6748645f LMI |
701 | (inhibit-point-motion-hooks t) |
702 | buffer-read-only) | |
520aa572 SZ |
703 | (when (or (null arg) |
704 | (zerop arg) | |
705 | (and (> arg 0) (not hidden)) | |
706 | (and (< arg 0) hidden)) | |
707 | (if hidden | |
23f87bed MB |
708 | (progn |
709 | ;; Can't remove 'cite from g-a-wash-types here because | |
710 | ;; multiple citations may be hidden -jas | |
711 | (gnus-remove-text-properties-when | |
712 | 'article-type 'cite beg end | |
713 | (cons 'article-type (cons 'cite | |
714 | gnus-hidden-properties)))) | |
715 | (gnus-add-wash-type 'cite) | |
520aa572 | 716 | (gnus-add-text-properties-when |
a1506d29 | 717 | 'article-type nil beg end |
520aa572 SZ |
718 | (cons 'article-type (cons 'cite |
719 | gnus-hidden-properties)))) | |
23f87bed MB |
720 | (let ((gnus-article-mime-handle-alist-1 gnus-article-mime-handle-alist)) |
721 | (gnus-set-mode-line 'article)) | |
520aa572 SZ |
722 | (save-excursion |
723 | (goto-char start) | |
724 | (gnus-delete-line) | |
725 | (put-text-property | |
726 | (point) | |
727 | (progn | |
728 | (gnus-article-add-button | |
729 | (point) | |
730 | (progn (eval | |
731 | (if hidden | |
732 | gnus-cited-opened-text-button-line-format-spec | |
733 | gnus-cited-closed-text-button-line-format-spec)) | |
734 | (point)) | |
735 | `gnus-article-toggle-cited-text | |
736 | args) | |
737 | (point)) | |
738 | 'article-type 'annotation))))) | |
eec82323 LMI |
739 | |
740 | (defun gnus-article-hide-citation-maybe (&optional arg force) | |
741 | "Toggle hiding of cited text that has an attribution line. | |
742 | If given a negative prefix, always show; if given a positive prefix, | |
743 | always hide. | |
744 | This will do nothing unless at least `gnus-cite-hide-percentage' | |
745 | percent and at least `gnus-cite-hide-absolute' lines of the body is | |
746 | cited text with attributions. When called interactively, these two | |
747 | variables are ignored. | |
748 | See also the documentation for `gnus-article-highlight-citation'." | |
6748645f | 749 | (interactive (append (gnus-article-hidden-arg) '(force))) |
23f87bed MB |
750 | (with-current-buffer gnus-article-buffer |
751 | (gnus-delete-wash-type 'cite) | |
752 | (unless (gnus-article-check-hidden-text 'cite arg) | |
753 | (save-excursion | |
754 | (gnus-cite-parse-maybe force) | |
755 | (article-goto-body) | |
756 | (let ((start (point)) | |
757 | (atts gnus-cite-attribution-alist) | |
758 | (buffer-read-only nil) | |
759 | (inhibit-point-motion-hooks t) | |
760 | (hidden 0) | |
761 | total) | |
762 | (goto-char (point-max)) | |
763 | (gnus-article-search-signature) | |
764 | (setq total (count-lines start (point))) | |
eec82323 | 765 | (while atts |
23f87bed MB |
766 | (setq hidden (+ hidden (length (cdr (assoc (cdar atts) |
767 | gnus-cite-prefix-alist)))) | |
768 | atts (cdr atts))) | |
769 | (when (or force | |
770 | (and (> (* 100 hidden) (* gnus-cite-hide-percentage total)) | |
771 | (> hidden gnus-cite-hide-absolute))) | |
772 | (gnus-add-wash-type 'cite) | |
773 | (setq atts gnus-cite-attribution-alist) | |
774 | (while atts | |
775 | (setq total (cdr (assoc (cdar atts) gnus-cite-prefix-alist)) | |
776 | atts (cdr atts)) | |
777 | (while total | |
778 | (setq hidden (car total) | |
779 | total (cdr total)) | |
780 | (goto-char (point-min)) | |
781 | (forward-line (1- hidden)) | |
782 | (unless (assq hidden gnus-cite-attribution-alist) | |
783 | (gnus-add-text-properties | |
784 | (point) (progn (forward-line 1) (point)) | |
785 | (nconc (list 'article-type 'cite) | |
786 | gnus-hidden-properties))))))))) | |
787 | (gnus-set-mode-line 'article))) | |
eec82323 LMI |
788 | |
789 | (defun gnus-article-hide-citation-in-followups () | |
790 | "Hide cited text in non-root articles." | |
791 | (interactive) | |
765abcce | 792 | (with-current-buffer gnus-article-buffer |
eec82323 | 793 | (let ((article (cdr gnus-article-current))) |
765abcce | 794 | (unless (with-current-buffer gnus-summary-buffer |
eec82323 LMI |
795 | (gnus-article-displayed-root-p article)) |
796 | (gnus-article-hide-citation))))) | |
797 | ||
798 | ;;; Internal functions: | |
799 | ||
16409b0b GM |
800 | (defun gnus-cite-parse-maybe (&optional force no-overlay) |
801 | "Always parse the buffer." | |
802 | (gnus-cite-localize) | |
803 | ;;Reset parser information. | |
804 | (setq gnus-cite-prefix-alist nil | |
805 | gnus-cite-attribution-alist nil | |
806 | gnus-cite-loose-prefix-alist nil | |
807 | gnus-cite-loose-attribution-alist nil) | |
808 | (unless no-overlay | |
809 | (gnus-cite-delete-overlays)) | |
810 | ;; Parse if not too large. | |
811 | (if (and gnus-cite-parse-max-size | |
812 | (> (buffer-size) gnus-cite-parse-max-size)) | |
eec82323 | 813 | () |
16409b0b GM |
814 | (setq gnus-cite-article (cons (car gnus-article-current) |
815 | (cdr gnus-article-current))) | |
816 | (gnus-cite-parse-wrapper))) | |
817 | ||
818 | (defun gnus-cite-delete-overlays () | |
819 | (dolist (overlay gnus-cite-overlay-list) | |
23f87bed MB |
820 | (ignore-errors |
821 | (when (or (not (gnus-overlay-end overlay)) | |
822 | (and (>= (gnus-overlay-end overlay) (point-min)) | |
823 | (<= (gnus-overlay-end overlay) (point-max)))) | |
824 | (setq gnus-cite-overlay-list (delete overlay gnus-cite-overlay-list)) | |
825 | (ignore-errors | |
826 | (gnus-delete-overlay overlay)))))) | |
eec82323 LMI |
827 | |
828 | (defun gnus-cite-parse-wrapper () | |
16409b0b GM |
829 | ;; Wrap chopped gnus-cite-parse. |
830 | (article-goto-body) | |
831 | (let ((inhibit-point-motion-hooks t)) | |
832 | (save-excursion | |
833 | (gnus-cite-parse-attributions)) | |
834 | (save-excursion | |
835 | (gnus-cite-parse)) | |
836 | (save-excursion | |
837 | (gnus-cite-connect-attributions)))) | |
eec82323 LMI |
838 | |
839 | (defun gnus-cite-parse () | |
840 | ;; Parse and connect citation prefixes and attribution lines. | |
841 | ||
842 | ;; Parse current buffer searching for citation prefixes. | |
843 | (let ((line (1+ (count-lines (point-min) (point)))) | |
844 | (case-fold-search t) | |
845 | (max (save-excursion | |
846 | (goto-char (point-max)) | |
847 | (gnus-article-search-signature) | |
848 | (point))) | |
23f87bed MB |
849 | (prefix-regexp (concat "^\\(" message-cite-prefix-regexp "\\)")) |
850 | alist entry start begin end numbers prefix guess-limit) | |
eec82323 LMI |
851 | ;; Get all potential prefixes in `alist'. |
852 | (while (< (point) max) | |
853 | ;; Each line. | |
854 | (setq begin (point) | |
23f87bed | 855 | guess-limit (progn (skip-chars-forward "^> \t\r\n") (point)) |
01c52d31 | 856 | end (point-at-bol 2) |
eec82323 LMI |
857 | start end) |
858 | (goto-char begin) | |
859 | ;; Ignore standard Supercite attribution prefix. | |
23f87bed MB |
860 | (when (and (< guess-limit (+ begin gnus-cite-max-prefix)) |
861 | (looking-at gnus-supercite-regexp)) | |
eec82323 LMI |
862 | (if (match-end 1) |
863 | (setq end (1+ (match-end 1))) | |
864 | (setq end (1+ begin)))) | |
865 | ;; Ignore very long prefixes. | |
23f87bed MB |
866 | (when (> end (+ begin gnus-cite-max-prefix)) |
867 | (setq end (+ begin gnus-cite-max-prefix))) | |
868 | ;; Ignore quoted envelope From_. | |
869 | (when (and gnus-cite-ignore-quoted-from | |
870 | (prog2 | |
871 | (setq case-fold-search nil) | |
872 | (looking-at ">From ") | |
873 | (setq case-fold-search t))) | |
874 | (setq end (1+ begin))) | |
875 | (while (re-search-forward prefix-regexp (1- end) t) | |
eec82323 LMI |
876 | ;; Each prefix. |
877 | (setq end (match-end 0) | |
878 | prefix (buffer-substring begin end)) | |
01c52d31 | 879 | (set-text-properties 0 (length prefix) nil prefix) |
eec82323 LMI |
880 | (setq entry (assoc prefix alist)) |
881 | (if entry | |
882 | (setcdr entry (cons line (cdr entry))) | |
883 | (push (list prefix line) alist)) | |
884 | (goto-char begin)) | |
885 | (goto-char start) | |
886 | (setq line (1+ line))) | |
23f87bed MB |
887 | ;; Horrible special case for some Microsoft mailers. |
888 | (goto-char (point-min)) | |
01c52d31 MB |
889 | (setq start t begin nil entry nil) |
890 | (while start | |
891 | ;; Assume this search ends up at the beginning of a line. | |
892 | (if (re-search-forward gnus-cite-unsightly-citation-regexp max t) | |
893 | (progn | |
894 | (when (number-or-marker-p start) | |
895 | (setq begin (count-lines (point-min) start) | |
896 | end (count-lines (point-min) (match-beginning 0)))) | |
897 | (setq start (match-end 0))) | |
898 | (when (number-or-marker-p start) | |
899 | (setq begin (count-lines (point-min) start) | |
900 | end (count-lines (point-min) max))) | |
901 | (setq start nil)) | |
902 | (when begin | |
903 | (while (< begin end) | |
904 | ;; Need to do 1+ because we're in the bol. | |
905 | (push (setq begin (1+ begin)) entry)))) | |
906 | (when entry | |
23f87bed | 907 | (push (cons "" entry) alist)) |
eec82323 LMI |
908 | ;; We got all the potential prefixes. Now create |
909 | ;; `gnus-cite-prefix-alist' containing the oldest prefix for each | |
23f87bed | 910 | ;; line that appears at least `gnus-cite-minimum-match-count' |
eec82323 LMI |
911 | ;; times. First sort them by length. Longer is older. |
912 | (setq alist (sort alist (lambda (a b) | |
913 | (> (length (car a)) (length (car b)))))) | |
914 | (while alist | |
915 | (setq entry (car alist) | |
916 | prefix (car entry) | |
917 | numbers (cdr entry) | |
918 | alist (cdr alist)) | |
919 | (cond ((null numbers) | |
920 | ;; No lines with this prefix that wasn't also part of | |
921 | ;; a longer prefix. | |
922 | ) | |
923 | ((< (length numbers) gnus-cite-minimum-match-count) | |
924 | ;; Too few lines with this prefix. We keep it a bit | |
925 | ;; longer in case it is an exact match for an attribution | |
926 | ;; line, but we don't remove the line from other | |
927 | ;; prefixes. | |
928 | (push entry gnus-cite-prefix-alist)) | |
929 | (t | |
930 | (push entry | |
931 | gnus-cite-prefix-alist) | |
932 | ;; Remove articles from other prefixes. | |
933 | (let ((loop alist) | |
934 | current) | |
935 | (while loop | |
936 | (setq current (car loop) | |
937 | loop (cdr loop)) | |
938 | (setcdr current | |
939 | (gnus-set-difference (cdr current) numbers))))))))) | |
940 | ||
941 | (defun gnus-cite-parse-attributions () | |
942 | (let (al-alist) | |
943 | ;; Parse attributions | |
944 | (while (re-search-forward gnus-cite-attribution-suffix (point-max) t) | |
945 | (let* ((start (match-beginning 0)) | |
946 | (end (match-end 0)) | |
947 | (wrote (count-lines (point-min) end)) | |
948 | (prefix (gnus-cite-find-prefix wrote)) | |
949 | ;; Check previous line for an attribution leader. | |
950 | (tag (progn | |
951 | (beginning-of-line 1) | |
952 | (when (looking-at gnus-supercite-secondary-regexp) | |
953 | (buffer-substring (match-beginning 1) | |
954 | (match-end 1))))) | |
955 | (in (progn | |
956 | (goto-char start) | |
957 | (and (re-search-backward gnus-cite-attribution-prefix | |
958 | (save-excursion | |
959 | (beginning-of-line 0) | |
960 | (point)) | |
961 | t) | |
962 | (not (re-search-forward gnus-cite-attribution-suffix | |
963 | start t)) | |
964 | (count-lines (point-min) (1+ (point))))))) | |
965 | (when (eq wrote in) | |
966 | (setq in nil)) | |
967 | (goto-char end) | |
968 | ;; don't add duplicates | |
969 | (let ((al (buffer-substring (save-excursion (beginning-of-line 0) | |
970 | (1+ (point))) | |
971 | end))) | |
01c52d31 MB |
972 | (when (not (assoc al al-alist)) |
973 | (push (list wrote in prefix tag) | |
974 | gnus-cite-loose-attribution-alist) | |
975 | (push (cons al t) al-alist))))))) | |
eec82323 LMI |
976 | |
977 | (defun gnus-cite-connect-attributions () | |
978 | ;; Connect attributions to citations | |
979 | ||
980 | ;; No citations have been connected to attribution lines yet. | |
981 | (setq gnus-cite-loose-prefix-alist (append gnus-cite-prefix-alist nil)) | |
982 | ||
983 | ;; Parse current buffer searching for attribution lines. | |
984 | ;; Find exact supercite citations. | |
985 | (gnus-cite-match-attributions 'small nil | |
986 | (lambda (prefix tag) | |
987 | (when tag | |
988 | (concat "\\`" | |
989 | (regexp-quote prefix) "[ \t]*" | |
990 | (regexp-quote tag) ">")))) | |
991 | ;; Find loose supercite citations after attributions. | |
992 | (gnus-cite-match-attributions 'small t | |
993 | (lambda (prefix tag) | |
994 | (when tag | |
995 | (concat "\\<" | |
996 | (regexp-quote tag) | |
997 | "\\>")))) | |
998 | ;; Find loose supercite citations anywhere. | |
999 | (gnus-cite-match-attributions 'small nil | |
1000 | (lambda (prefix tag) | |
1001 | (when tag | |
1002 | (concat "\\<" | |
1003 | (regexp-quote tag) | |
1004 | "\\>")))) | |
1005 | ;; Find nested citations after attributions. | |
1006 | (gnus-cite-match-attributions 'small-if-unique t | |
1007 | (lambda (prefix tag) | |
1008 | (concat "\\`" (regexp-quote prefix) ".+"))) | |
1009 | ;; Find nested citations anywhere. | |
1010 | (gnus-cite-match-attributions 'small nil | |
1011 | (lambda (prefix tag) | |
1012 | (concat "\\`" (regexp-quote prefix) ".+"))) | |
1013 | ;; Remove loose prefixes with too few lines. | |
1014 | (let ((alist gnus-cite-loose-prefix-alist) | |
1015 | entry) | |
1016 | (while alist | |
1017 | (setq entry (car alist) | |
1018 | alist (cdr alist)) | |
1019 | (when (< (length (cdr entry)) gnus-cite-minimum-match-count) | |
1020 | (setq gnus-cite-prefix-alist | |
1021 | (delq entry gnus-cite-prefix-alist) | |
1022 | gnus-cite-loose-prefix-alist | |
1023 | (delq entry gnus-cite-loose-prefix-alist))))) | |
1024 | ;; Find flat attributions. | |
1025 | (gnus-cite-match-attributions 'first t nil) | |
1026 | ;; Find any attributions (are we getting desperate yet?). | |
1027 | (gnus-cite-match-attributions 'first nil nil)) | |
1028 | ||
1029 | (defun gnus-cite-match-attributions (sort after fun) | |
1030 | ;; Match all loose attributions and citations (SORT AFTER FUN) . | |
1031 | ;; | |
1032 | ;; If SORT is `small', the citation with the shortest prefix will be | |
1033 | ;; used, if it is `first' the first prefix will be used, if it is | |
1034 | ;; `small-if-unique' the shortest prefix will be used if the | |
1035 | ;; attribution line does not share its own prefix with other | |
1036 | ;; loose attribution lines, otherwise the first prefix will be used. | |
1037 | ;; | |
1038 | ;; If AFTER is non-nil, only citations after the attribution line | |
1039 | ;; will be considered. | |
1040 | ;; | |
1041 | ;; If FUN is non-nil, it will be called with the arguments (WROTE | |
1042 | ;; PREFIX TAG) and expected to return a regular expression. Only | |
1043 | ;; citations whose prefix matches the regular expression will be | |
1044 | ;; considered. | |
1045 | ;; | |
1046 | ;; WROTE is the attribution line number. | |
1047 | ;; PREFIX is the attribution line prefix. | |
1048 | ;; TAG is the Supercite tag on the attribution line. | |
1049 | (let ((atts gnus-cite-loose-attribution-alist) | |
1050 | (case-fold-search t) | |
1051 | att wrote in prefix tag regexp limit smallest best size) | |
1052 | (while atts | |
1053 | (setq att (car atts) | |
1054 | atts (cdr atts) | |
1055 | wrote (nth 0 att) | |
1056 | in (nth 1 att) | |
1057 | prefix (nth 2 att) | |
1058 | tag (nth 3 att) | |
1059 | regexp (if fun (funcall fun prefix tag) "") | |
1060 | size (cond ((eq sort 'small) t) | |
1061 | ((eq sort 'first) nil) | |
1062 | (t (< (length (gnus-cite-find-loose prefix)) 2))) | |
1063 | limit (if after wrote -1) | |
1064 | smallest 1000000 | |
1065 | best nil) | |
1066 | (let ((cites gnus-cite-loose-prefix-alist) | |
1067 | cite candidate numbers first compare) | |
1068 | (while cites | |
1069 | (setq cite (car cites) | |
1070 | cites (cdr cites) | |
1071 | candidate (car cite) | |
1072 | numbers (cdr cite) | |
1073 | first (apply 'min numbers) | |
1074 | compare (if size (length candidate) first)) | |
1075 | (and (> first limit) | |
1076 | regexp | |
1077 | (string-match regexp candidate) | |
1078 | (< compare smallest) | |
1079 | (setq best cite | |
1080 | smallest compare)))) | |
1081 | (if (null best) | |
1082 | () | |
1083 | (setq gnus-cite-loose-attribution-alist | |
1084 | (delq att gnus-cite-loose-attribution-alist)) | |
1085 | (push (cons wrote (car best)) gnus-cite-attribution-alist) | |
1086 | (when in | |
1087 | (push (cons in (car best)) gnus-cite-attribution-alist)) | |
1088 | (when (memq best gnus-cite-loose-prefix-alist) | |
1089 | (let ((loop gnus-cite-prefix-alist) | |
1090 | (numbers (cdr best)) | |
1091 | current) | |
1092 | (setq gnus-cite-loose-prefix-alist | |
1093 | (delq best gnus-cite-loose-prefix-alist)) | |
1094 | (while loop | |
1095 | (setq current (car loop) | |
1096 | loop (cdr loop)) | |
1097 | (if (eq current best) | |
1098 | () | |
1099 | (setcdr current (gnus-set-difference (cdr current) numbers)) | |
1100 | (when (null (cdr current)) | |
1101 | (setq gnus-cite-loose-prefix-alist | |
1102 | (delq current gnus-cite-loose-prefix-alist) | |
1103 | atts (delq current atts))))))))))) | |
1104 | ||
1105 | (defun gnus-cite-find-loose (prefix) | |
1106 | ;; Return a list of loose attribution lines prefixed by PREFIX. | |
1107 | (let* ((atts gnus-cite-loose-attribution-alist) | |
1108 | att line lines) | |
1109 | (while atts | |
1110 | (setq att (car atts) | |
1111 | line (car att) | |
1112 | atts (cdr atts)) | |
1113 | (when (string-equal (gnus-cite-find-prefix line) prefix) | |
1114 | (push line lines))) | |
1115 | lines)) | |
1116 | ||
1117 | (defun gnus-cite-add-face (number prefix face) | |
1118 | ;; At line NUMBER, ignore PREFIX and add FACE to the rest of the line. | |
1119 | (when face | |
1120 | (let ((inhibit-point-motion-hooks t) | |
6748645f | 1121 | from to overlay) |
16409b0b GM |
1122 | (goto-char (point-min)) |
1123 | (when (zerop (forward-line (1- number))) | |
eec82323 LMI |
1124 | (forward-char (length prefix)) |
1125 | (skip-chars-forward " \t") | |
1126 | (setq from (point)) | |
1127 | (end-of-line 1) | |
1128 | (skip-chars-backward " \t") | |
1129 | (setq to (point)) | |
1130 | (when (< from to) | |
6748645f LMI |
1131 | (push (setq overlay (gnus-make-overlay from to)) |
1132 | gnus-cite-overlay-list) | |
c1b1a4f3 | 1133 | (gnus-overlay-put overlay 'evaporate t) |
6748645f | 1134 | (gnus-overlay-put overlay 'face face)))))) |
eec82323 LMI |
1135 | |
1136 | (defun gnus-cite-toggle (prefix) | |
765abcce | 1137 | (with-current-buffer gnus-article-buffer |
16409b0b | 1138 | (gnus-cite-parse-maybe nil t) |
eec82323 LMI |
1139 | (let ((buffer-read-only nil) |
1140 | (numbers (cdr (assoc prefix gnus-cite-prefix-alist))) | |
1141 | (inhibit-point-motion-hooks t) | |
1142 | number) | |
1143 | (while numbers | |
1144 | (setq number (car numbers) | |
1145 | numbers (cdr numbers)) | |
16409b0b GM |
1146 | (goto-char (point-min)) |
1147 | (forward-line (1- number)) | |
eec82323 | 1148 | (cond ((get-text-property (point) 'invisible) |
23f87bed MB |
1149 | ;; Can't remove 'cite from g-a-wash-types here because |
1150 | ;; multiple citations may be hidden -jas | |
eec82323 LMI |
1151 | (remove-text-properties (point) (progn (forward-line 1) (point)) |
1152 | gnus-hidden-properties)) | |
1153 | ((assq number gnus-cite-attribution-alist)) | |
1154 | (t | |
23f87bed | 1155 | (gnus-add-wash-type 'cite) |
eec82323 LMI |
1156 | (gnus-add-text-properties |
1157 | (point) (progn (forward-line 1) (point)) | |
1158 | (nconc (list 'article-type 'cite) | |
23f87bed MB |
1159 | gnus-hidden-properties)))) |
1160 | (let ((gnus-article-mime-handle-alist-1 | |
1161 | gnus-article-mime-handle-alist)) | |
1162 | (gnus-set-mode-line 'article)))))) | |
eec82323 LMI |
1163 | |
1164 | (defun gnus-cite-find-prefix (line) | |
1165 | ;; Return citation prefix for LINE. | |
1166 | (let ((alist gnus-cite-prefix-alist) | |
1167 | (prefix "") | |
1168 | entry) | |
1169 | (while alist | |
1170 | (setq entry (car alist) | |
1171 | alist (cdr alist)) | |
1172 | (when (memq line (cdr entry)) | |
1173 | (setq prefix (car entry)))) | |
1174 | prefix)) | |
1175 | ||
6748645f LMI |
1176 | (defun gnus-cite-localize () |
1177 | "Make the citation variables local to the article buffer." | |
1178 | (let ((vars '(gnus-cite-article | |
1179 | gnus-cite-overlay-list gnus-cite-prefix-alist | |
1180 | gnus-cite-attribution-alist gnus-cite-loose-prefix-alist | |
1181 | gnus-cite-loose-attribution-alist))) | |
1182 | (while vars | |
1183 | (make-local-variable (pop vars))))) | |
eec82323 | 1184 | |
23f87bed MB |
1185 | (defun gnus-cited-line-p () |
1186 | "Say whether the current line is a cited line." | |
1187 | (save-excursion | |
1188 | (beginning-of-line) | |
1189 | (let ((found nil)) | |
1190 | (dolist (prefix (mapcar 'car gnus-cite-prefix-alist)) | |
1191 | (when (string= (buffer-substring (point) (+ (length prefix) (point))) | |
1192 | prefix) | |
1193 | (setq found t))) | |
1194 | found))) | |
1195 | ||
01c52d31 MB |
1196 | |
1197 | ;; Highlighting of different citation levels in message-mode. | |
1198 | ;; - message-cite-prefix will be overridden if this is enabled. | |
1199 | ||
1200 | (defvar gnus-message-max-citation-depth | |
1201 | (length gnus-cite-face-list) | |
1202 | "Maximum supported level of citation.") | |
1203 | ||
1204 | (defvar gnus-message-cite-prefix-regexp | |
1205 | (concat "^\\(?:" message-cite-prefix-regexp "\\)")) | |
1206 | ||
1207 | (defun gnus-message-search-citation-line (limit) | |
1208 | "Search for a cited line and set match data accordingly. | |
1209 | Returns nil if there is no such line before LIMIT, t otherwise." | |
1210 | (when (re-search-forward gnus-message-cite-prefix-regexp limit t) | |
1211 | (let ((cdepth (min (length (apply 'concat | |
1212 | (split-string | |
1213 | (match-string-no-properties 0) | |
1214 | "[ \t [:alnum:]]+"))) | |
1215 | gnus-message-max-citation-depth)) | |
1216 | (mlist (make-list (* (1+ gnus-message-max-citation-depth) 2) nil)) | |
1217 | (start (point-at-bol)) | |
1218 | (end (point-at-eol))) | |
1219 | (setcar mlist start) | |
1220 | (setcar (cdr mlist) end) | |
1221 | (setcar (nthcdr (* cdepth 2) mlist) start) | |
1222 | (setcar (nthcdr (1+ (* cdepth 2)) mlist) end) | |
1223 | (set-match-data mlist)) | |
1224 | t)) | |
1225 | ||
1226 | (defvar gnus-message-citation-keywords | |
1227 | ;; eval-when-compile ;; This breaks in XEmacs | |
1228 | `((gnus-message-search-citation-line | |
1229 | ,@(let ((list nil) | |
1230 | (count 1)) | |
1231 | ;; (require 'gnus-cite) | |
1232 | (dolist (face gnus-cite-face-list (nreverse list)) | |
1233 | (push (list count (list 'quote face) 'prepend t) list) | |
1234 | (setq count (1+ count)))))) ;; | |
1235 | "Keywords for highlighting different levels of message citations.") | |
1236 | ||
9efa445f DN |
1237 | (defvar font-lock-defaults-computed) |
1238 | (defvar font-lock-keywords) | |
1239 | (defvar font-lock-set-defaults) | |
01c52d31 MB |
1240 | |
1241 | (eval-and-compile | |
1242 | (unless (featurep 'xemacs) | |
1243 | (autoload 'font-lock-set-defaults "font-lock"))) | |
1244 | ||
1245 | (define-minor-mode gnus-message-citation-mode | |
1246 | "Toggle `gnus-message-citation-mode' in current buffer. | |
1247 | This buffer local minor mode provides additional font-lock support for | |
1248 | nested citations. | |
1249 | With prefix ARG, turn `gnus-message-citation-mode' on if and only if ARG | |
1250 | is positive. | |
1251 | Automatically turn `font-lock-mode' on when `gnus-message-citation-mode' | |
1252 | is turned on." | |
1253 | nil ;; init-value | |
1254 | "" ;; lighter | |
1255 | nil ;; keymap | |
1256 | (when (eq major-mode 'message-mode) | |
1257 | (let ((defaults (car (if (featurep 'xemacs) | |
1258 | (get 'message-mode 'font-lock-defaults) | |
1259 | font-lock-defaults))) | |
1260 | default keywords) | |
1261 | (while defaults | |
1262 | (setq default (if (consp defaults) | |
1263 | (pop defaults) | |
1264 | (prog1 | |
1265 | defaults | |
1266 | (setq defaults nil)))) | |
1267 | (if gnus-message-citation-mode | |
1268 | ;; `gnus-message-citation-keywords' should be the last | |
1269 | ;; elements of the keywords because the others are unlikely | |
1270 | ;; to have the OVERRIDE flags -- XEmacs applies a keyword | |
1271 | ;; having no OVERRIDE flag to matched text even if it has | |
1272 | ;; already other faces, while Emacs doesn't. | |
1273 | (set (make-local-variable default) | |
1274 | (append (default-value default) | |
1275 | gnus-message-citation-keywords)) | |
1276 | (kill-local-variable default)))) | |
1277 | ;; Force `font-lock-set-defaults' to update `font-lock-keywords'. | |
1278 | (if (featurep 'xemacs) | |
1279 | (progn | |
1280 | (require 'font-lock) | |
1281 | (setq font-lock-defaults-computed nil | |
1282 | font-lock-keywords nil)) | |
1283 | (setq font-lock-set-defaults nil)) | |
1284 | (font-lock-set-defaults) | |
1285 | (cond ((symbol-value 'font-lock-mode) | |
1286 | (font-lock-fontify-buffer)) | |
1287 | (gnus-message-citation-mode | |
1288 | (font-lock-mode 1))))) | |
1289 | ||
1290 | (defun turn-on-gnus-message-citation-mode () | |
1291 | "Turn on `gnus-message-citation-mode'." | |
1292 | (gnus-message-citation-mode 1)) | |
1293 | (defun turn-off-gnus-message-citation-mode () | |
1294 | "Turn off `gnus-message-citation-mode'." | |
1295 | (gnus-message-citation-mode -1)) | |
1296 | ||
eec82323 LMI |
1297 | (gnus-ems-redefine) |
1298 | ||
1299 | (provide 'gnus-cite) | |
1300 | ||
16409b0b GM |
1301 | ;; Local Variables: |
1302 | ;; coding: iso-8859-1 | |
1303 | ;; End: | |
1304 | ||
eec82323 | 1305 | ;;; gnus-cite.el ends here |