Commit | Line | Data |
---|---|---|
eec82323 | 1 | ;;; gnus-cite.el --- parse citations in articles for Gnus |
6748645f | 2 | ;; Copyright (C) 1995,96,97,98 Free Software Foundation, Inc. |
eec82323 | 3 | |
6748645f | 4 | ;; Author: Per Abhiddenware; you can redistribute it and/or modify |
eec82323 LMI |
5 | ;; it under the terms of the GNU General Public License as published by |
6 | ;; the Free Software Foundation; either version 2, or (at your option) | |
7 | ;; any later version. | |
8 | ||
9 | ;; GNU Emacs is distributed in the hope that it will be useful, | |
10 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of | |
11 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
12 | ;; GNU General Public License for more details. | |
13 | ||
14 | ;; You should have received a copy of the GNU General Public License | |
15 | ;; along with GNU Emacs; see the file COPYING. If not, write to the | |
16 | ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330, | |
17 | ;; Boston, MA 02111-1307, USA. | |
18 | ||
19 | ;;; Commentary: | |
20 | ||
21 | ;;; Code: | |
22 | ||
5ab7173c RS |
23 | (eval-when-compile (require 'cl)) |
24 | ||
6748645f LMI |
25 | (eval-when-compile (require 'cl)) |
26 | ||
eec82323 LMI |
27 | (require 'gnus) |
28 | (require 'gnus-art) | |
29 | (require 'gnus-range) | |
30 | ||
31 | ;;; Customization: | |
32 | ||
33 | (defgroup gnus-cite nil | |
34 | "Citation." | |
35 | :prefix "gnus-cite-" | |
36 | :link '(custom-manual "(gnus)Article Highlighting") | |
37 | :group 'gnus-article) | |
38 | ||
39 | (defcustom gnus-cite-reply-regexp | |
40 | "^\\(Subject: Re\\|In-Reply-To\\|References\\):" | |
6748645f | 41 | "*If headers match this regexp it is reasonable to believe that |
eec82323 LMI |
42 | article has citations." |
43 | :group 'gnus-cite | |
44 | :type 'string) | |
45 | ||
46 | (defcustom gnus-cite-always-check nil | |
47 | "Check article always for citations. Set it t to check all articles." | |
48 | :group 'gnus-cite | |
49 | :type '(choice (const :tag "no" nil) | |
50 | (const :tag "yes" t))) | |
51 | ||
6748645f LMI |
52 | (defcustom gnus-cited-opened-text-button-line-format "%(%{[-]%}%)\n" |
53 | "Format of opened cited text buttons." | |
54 | :group 'gnus-cite | |
55 | :type 'string) | |
56 | ||
57 | (defcustom gnus-cited-closed-text-button-line-format "%(%{[+]%}%)\n" | |
58 | "Format of closed cited text buttons." | |
eec82323 LMI |
59 | :group 'gnus-cite |
60 | :type 'string) | |
61 | ||
62 | (defcustom gnus-cited-lines-visible nil | |
63 | "The number of lines of hidden cited text to remain visible." | |
64 | :group 'gnus-cite | |
65 | :type '(choice (const :tag "none" nil) | |
66 | integer)) | |
67 | ||
68 | (defcustom gnus-cite-parse-max-size 25000 | |
69 | "Maximum article size (in bytes) where parsing citations is allowed. | |
70 | Set it to nil to parse all articles." | |
71 | :group 'gnus-cite | |
72 | :type '(choice (const :tag "all" nil) | |
73 | integer)) | |
74 | ||
75 | (defcustom gnus-cite-prefix-regexp | |
6748645f LMI |
76 | "^[]>|:}+ ]*[]>|:}+]\\(.*>\\)?\\|^.*>" |
77 | "*Regexp matching the longest possible citation prefix on a line." | |
eec82323 LMI |
78 | :group 'gnus-cite |
79 | :type 'regexp) | |
80 | ||
81 | (defcustom gnus-cite-max-prefix 20 | |
82 | "Maximum possible length for a citation prefix." | |
83 | :group 'gnus-cite | |
84 | :type 'integer) | |
85 | ||
86 | (defcustom gnus-supercite-regexp | |
87 | (concat "^\\(" gnus-cite-prefix-regexp "\\)? *" | |
88 | ">>>>> +\"\\([^\"\n]+\\)\" +==") | |
6748645f | 89 | "*Regexp matching normal Supercite attribution lines. |
eec82323 LMI |
90 | The first grouping must match prefixes added by other packages." |
91 | :group 'gnus-cite | |
92 | :type 'regexp) | |
93 | ||
94 | (defcustom gnus-supercite-secondary-regexp "^.*\"\\([^\"\n]+\\)\" +==" | |
95 | "Regexp matching mangled Supercite attribution lines. | |
96 | The first regexp group should match the Supercite attribution." | |
97 | :group 'gnus-cite | |
98 | :type 'regexp) | |
99 | ||
100 | (defcustom gnus-cite-minimum-match-count 2 | |
101 | "Minimum number of identical prefixes before we believe it's a citation." | |
102 | :group 'gnus-cite | |
103 | :type 'integer) | |
104 | ||
6748645f LMI |
105 | (defcustom gnus-cite-attribution-prefix |
106 | "In article\\|in <\\|On \\(Mon\\|Tue\\|Wed\\|Thu\\|Fri\\|Sat\\|Sun\\)," | |
107 | "*Regexp matching the beginning of an attribution line." | |
eec82323 LMI |
108 | :group 'gnus-cite |
109 | :type 'regexp) | |
110 | ||
111 | (defcustom gnus-cite-attribution-suffix | |
6748645f LMI |
112 | "\\(\\(wrote\\|writes\\|said\\|says\\|>\\)\\(:\\|\\.\\.\\.\\)\\)[ \t]*$" |
113 | "*Regexp matching the end of an attribution line. | |
eec82323 LMI |
114 | The text matching the first grouping will be used as a button." |
115 | :group 'gnus-cite | |
116 | :type 'regexp) | |
117 | ||
118 | (defface gnus-cite-attribution-face '((t | |
6748645f | 119 | (:italic t))) |
eec82323 LMI |
120 | "Face used for attribution lines.") |
121 | ||
122 | (defcustom gnus-cite-attribution-face 'gnus-cite-attribution-face | |
123 | "Face used for attribution lines. | |
124 | It is merged with the face for the cited text belonging to the attribution." | |
125 | :group 'gnus-cite | |
126 | :type 'face) | |
127 | ||
128 | (defface gnus-cite-face-1 '((((class color) | |
129 | (background dark)) | |
130 | (:foreground "light blue")) | |
131 | (((class color) | |
132 | (background light)) | |
133 | (:foreground "MidnightBlue")) | |
134 | (t | |
135 | (:italic t))) | |
136 | "Citation face.") | |
137 | ||
138 | (defface gnus-cite-face-2 '((((class color) | |
139 | (background dark)) | |
140 | (:foreground "light cyan")) | |
141 | (((class color) | |
142 | (background light)) | |
143 | (:foreground "firebrick")) | |
144 | (t | |
145 | (:italic t))) | |
146 | "Citation face.") | |
147 | ||
148 | (defface gnus-cite-face-3 '((((class color) | |
149 | (background dark)) | |
150 | (:foreground "light yellow")) | |
151 | (((class color) | |
152 | (background light)) | |
153 | (:foreground "dark green")) | |
154 | (t | |
155 | (:italic t))) | |
156 | "Citation face.") | |
157 | ||
158 | (defface gnus-cite-face-4 '((((class color) | |
159 | (background dark)) | |
160 | (:foreground "light pink")) | |
161 | (((class color) | |
162 | (background light)) | |
163 | (:foreground "OrangeRed")) | |
164 | (t | |
165 | (:italic t))) | |
166 | "Citation face.") | |
167 | ||
168 | (defface gnus-cite-face-5 '((((class color) | |
169 | (background dark)) | |
170 | (:foreground "pale green")) | |
171 | (((class color) | |
172 | (background light)) | |
173 | (:foreground "dark khaki")) | |
174 | (t | |
175 | (:italic t))) | |
176 | "Citation face.") | |
177 | ||
178 | (defface gnus-cite-face-6 '((((class color) | |
179 | (background dark)) | |
180 | (:foreground "beige")) | |
181 | (((class color) | |
182 | (background light)) | |
183 | (:foreground "dark violet")) | |
184 | (t | |
185 | (:italic t))) | |
186 | "Citation face.") | |
187 | ||
188 | (defface gnus-cite-face-7 '((((class color) | |
189 | (background dark)) | |
190 | (:foreground "orange")) | |
191 | (((class color) | |
192 | (background light)) | |
193 | (:foreground "SteelBlue4")) | |
194 | (t | |
195 | (:italic t))) | |
196 | "Citation face.") | |
197 | ||
198 | (defface gnus-cite-face-8 '((((class color) | |
199 | (background dark)) | |
200 | (:foreground "magenta")) | |
201 | (((class color) | |
202 | (background light)) | |
203 | (:foreground "magenta")) | |
204 | (t | |
205 | (:italic t))) | |
206 | "Citation face.") | |
207 | ||
208 | (defface gnus-cite-face-9 '((((class color) | |
209 | (background dark)) | |
210 | (:foreground "violet")) | |
211 | (((class color) | |
212 | (background light)) | |
213 | (:foreground "violet")) | |
214 | (t | |
215 | (:italic t))) | |
216 | "Citation face.") | |
217 | ||
218 | (defface gnus-cite-face-10 '((((class color) | |
219 | (background dark)) | |
220 | (:foreground "medium purple")) | |
221 | (((class color) | |
222 | (background light)) | |
223 | (:foreground "medium purple")) | |
224 | (t | |
225 | (:italic t))) | |
226 | "Citation face.") | |
227 | ||
228 | (defface gnus-cite-face-11 '((((class color) | |
229 | (background dark)) | |
230 | (:foreground "turquoise")) | |
231 | (((class color) | |
232 | (background light)) | |
233 | (:foreground "turquoise")) | |
234 | (t | |
235 | (:italic t))) | |
236 | "Citation face.") | |
237 | ||
238 | (defcustom gnus-cite-face-list | |
239 | '(gnus-cite-face-1 gnus-cite-face-2 gnus-cite-face-3 gnus-cite-face-4 | |
240 | gnus-cite-face-5 gnus-cite-face-6 gnus-cite-face-7 gnus-cite-face-8 | |
241 | gnus-cite-face-9 gnus-cite-face-10 gnus-cite-face-11) | |
6748645f | 242 | "*List of faces used for highlighting citations. |
eec82323 LMI |
243 | |
244 | When there are citations from multiple articles in the same message, | |
245 | Gnus will try to give each citation from each article its own face. | |
246 | This should make it easier to see who wrote what." | |
247 | :group 'gnus-cite | |
248 | :type '(repeat face)) | |
249 | ||
250 | (defcustom gnus-cite-hide-percentage 50 | |
251 | "Only hide excess citation if above this percentage of the body." | |
252 | :group 'gnus-cite | |
253 | :type 'number) | |
254 | ||
255 | (defcustom gnus-cite-hide-absolute 10 | |
256 | "Only hide excess citation if above this number of lines in the body." | |
257 | :group 'gnus-cite | |
258 | :type 'integer) | |
259 | ||
260 | ;;; Internal Variables: | |
261 | ||
262 | (defvar gnus-cite-article nil) | |
6748645f | 263 | (defvar gnus-cite-overlay-list nil) |
eec82323 LMI |
264 | |
265 | (defvar gnus-cite-prefix-alist nil) | |
266 | ;; Alist of citation prefixes. | |
267 | ;; The cdr is a list of lines with that prefix. | |
268 | ||
269 | (defvar gnus-cite-attribution-alist nil) | |
270 | ;; Alist of attribution lines. | |
271 | ;; The car is a line number. | |
272 | ;; The cdr is the prefix for the citation started by that line. | |
273 | ||
274 | (defvar gnus-cite-loose-prefix-alist nil) | |
275 | ;; Alist of citation prefixes that have no matching attribution. | |
276 | ;; The cdr is a list of lines with that prefix. | |
277 | ||
278 | (defvar gnus-cite-loose-attribution-alist nil) | |
279 | ;; Alist of attribution lines that have no matching citation. | |
280 | ;; Each member has the form (WROTE IN PREFIX TAG), where | |
281 | ;; WROTE: is the attribution line number | |
282 | ;; IN: is the line number of the previous line if part of the same attribution, | |
283 | ;; PREFIX: Is the citation prefix of the attribution line(s), and | |
284 | ;; TAG: Is a Supercite tag, if any. | |
285 | ||
6748645f | 286 | (defvar gnus-cited-opened-text-button-line-format-alist |
eec82323 LMI |
287 | `((?b (marker-position beg) ?d) |
288 | (?e (marker-position end) ?d) | |
6748645f | 289 | (?n (count-lines beg end) ?d) |
eec82323 | 290 | (?l (- end beg) ?d))) |
6748645f LMI |
291 | (defvar gnus-cited-opened-text-button-line-format-spec nil) |
292 | (defvar gnus-cited-closed-text-button-line-format-alist | |
293 | gnus-cited-opened-text-button-line-format-alist) | |
294 | (defvar gnus-cited-closed-text-button-line-format-spec nil) | |
295 | ||
eec82323 LMI |
296 | |
297 | ;;; Commands: | |
298 | ||
299 | (defun gnus-article-highlight-citation (&optional force) | |
300 | "Highlight cited text. | |
301 | Each citation in the article will be highlighted with a different face. | |
302 | The faces are taken from `gnus-cite-face-list'. | |
303 | Attribution lines are highlighted with the same face as the | |
304 | corresponding citation merged with `gnus-cite-attribution-face'. | |
305 | ||
306 | Text is considered cited if at least `gnus-cite-minimum-match-count' | |
307 | lines matches `gnus-cite-prefix-regexp' with the same prefix. | |
308 | ||
309 | Lines matching `gnus-cite-attribution-suffix' and perhaps | |
310 | `gnus-cite-attribution-prefix' are considered attribution lines." | |
311 | (interactive (list 'force)) | |
312 | (save-excursion | |
313 | (set-buffer gnus-article-buffer) | |
314 | (gnus-cite-parse-maybe force) | |
315 | (let ((buffer-read-only nil) | |
316 | (alist gnus-cite-prefix-alist) | |
317 | (faces gnus-cite-face-list) | |
318 | (inhibit-point-motion-hooks t) | |
319 | face entry prefix skip numbers number face-alist) | |
320 | ;; Loop through citation prefixes. | |
321 | (while alist | |
322 | (setq entry (car alist) | |
323 | alist (cdr alist) | |
324 | prefix (car entry) | |
325 | numbers (cdr entry) | |
326 | face (car faces) | |
327 | faces (or (cdr faces) gnus-cite-face-list) | |
328 | face-alist (cons (cons prefix face) face-alist)) | |
329 | (while numbers | |
330 | (setq number (car numbers) | |
331 | numbers (cdr numbers)) | |
332 | (and (not (assq number gnus-cite-attribution-alist)) | |
333 | (not (assq number gnus-cite-loose-attribution-alist)) | |
334 | (gnus-cite-add-face number prefix face)))) | |
335 | ;; Loop through attribution lines. | |
336 | (setq alist gnus-cite-attribution-alist) | |
337 | (while alist | |
338 | (setq entry (car alist) | |
339 | alist (cdr alist) | |
340 | number (car entry) | |
341 | prefix (cdr entry) | |
342 | skip (gnus-cite-find-prefix number) | |
343 | face (cdr (assoc prefix face-alist))) | |
344 | ;; Add attribution button. | |
345 | (goto-line number) | |
346 | (when (re-search-forward gnus-cite-attribution-suffix | |
347 | (save-excursion (end-of-line 1) (point)) | |
348 | t) | |
349 | (gnus-article-add-button (match-beginning 1) (match-end 1) | |
350 | 'gnus-cite-toggle prefix)) | |
351 | ;; Highlight attribution line. | |
352 | (gnus-cite-add-face number skip face) | |
353 | (gnus-cite-add-face number skip gnus-cite-attribution-face)) | |
354 | ;; Loop through attribution lines. | |
355 | (setq alist gnus-cite-loose-attribution-alist) | |
356 | (while alist | |
357 | (setq entry (car alist) | |
358 | alist (cdr alist) | |
359 | number (car entry) | |
360 | skip (gnus-cite-find-prefix number)) | |
361 | (gnus-cite-add-face number skip gnus-cite-attribution-face))))) | |
362 | ||
363 | (defun gnus-dissect-cited-text () | |
364 | "Dissect the article buffer looking for cited text." | |
365 | (save-excursion | |
366 | (set-buffer gnus-article-buffer) | |
367 | (gnus-cite-parse-maybe) | |
368 | (let ((alist gnus-cite-prefix-alist) | |
369 | prefix numbers number marks m) | |
370 | ;; Loop through citation prefixes. | |
371 | (while alist | |
372 | (setq numbers (pop alist) | |
373 | prefix (pop numbers)) | |
374 | (while numbers | |
375 | (setq number (pop numbers)) | |
376 | (goto-char (point-min)) | |
377 | (forward-line number) | |
378 | (push (cons (point-marker) "") marks) | |
379 | (while (and numbers | |
380 | (= (1- number) (car numbers))) | |
381 | (setq number (pop numbers))) | |
382 | (goto-char (point-min)) | |
383 | (forward-line (1- number)) | |
384 | (push (cons (point-marker) prefix) marks))) | |
385 | ;; Skip to the beginning of the body. | |
386 | (goto-char (point-min)) | |
387 | (search-forward "\n\n" nil t) | |
388 | (push (cons (point-marker) "") marks) | |
389 | ;; Find the end of the body. | |
390 | (goto-char (point-max)) | |
391 | (gnus-article-search-signature) | |
392 | (push (cons (point-marker) "") marks) | |
393 | ;; Sort the marks. | |
6748645f | 394 | (setq marks (sort marks 'car-less-than-car)) |
eec82323 LMI |
395 | (let ((omarks marks)) |
396 | (setq marks nil) | |
397 | (while (cdr omarks) | |
398 | (if (= (caar omarks) (caadr omarks)) | |
399 | (progn | |
400 | (unless (equal (cdar omarks) "") | |
401 | (push (car omarks) marks)) | |
402 | (unless (equal (cdadr omarks) "") | |
403 | (push (cadr omarks) marks)) | |
404 | (unless (and (equal (cdar omarks) "") | |
405 | (equal (cdadr omarks) "") | |
406 | (not (cddr omarks))) | |
407 | (setq omarks (cdr omarks)))) | |
408 | (push (car omarks) marks)) | |
409 | (setq omarks (cdr omarks))) | |
410 | (when (car omarks) | |
411 | (push (car omarks) marks)) | |
412 | (setq marks (setq m (nreverse marks))) | |
413 | (while (cddr m) | |
414 | (if (and (equal (cdadr m) "") | |
415 | (equal (cdar m) (cdaddr m)) | |
416 | (goto-char (caadr m)) | |
417 | (forward-line 1) | |
418 | (= (point) (caaddr m))) | |
419 | (setcdr m (cdddr m)) | |
420 | (setq m (cdr m)))) | |
421 | marks)))) | |
422 | ||
423 | (defun gnus-article-fill-cited-article (&optional force width) | |
424 | "Do word wrapping in the current article. | |
425 | If WIDTH (the numerical prefix), use that text width when filling." | |
426 | (interactive (list t current-prefix-arg)) | |
427 | (save-excursion | |
428 | (set-buffer gnus-article-buffer) | |
429 | (let ((buffer-read-only nil) | |
430 | (inhibit-point-motion-hooks t) | |
431 | (marks (gnus-dissect-cited-text)) | |
432 | (adaptive-fill-mode nil) | |
433 | (filladapt-mode nil) | |
434 | (fill-column (if width (prefix-numeric-value width) fill-column))) | |
435 | (save-restriction | |
436 | (while (cdr marks) | |
437 | (widen) | |
438 | (narrow-to-region (caar marks) (caadr marks)) | |
439 | (let ((adaptive-fill-regexp | |
440 | (concat "^" (regexp-quote (cdar marks)) " *")) | |
441 | (fill-prefix (cdar marks))) | |
442 | (fill-region (point-min) (point-max))) | |
443 | (set-marker (caar marks) nil) | |
444 | (setq marks (cdr marks))) | |
445 | (when marks | |
446 | (set-marker (caar marks) nil)) | |
447 | ;; All this information is now incorrect. | |
448 | (setq gnus-cite-prefix-alist nil | |
449 | gnus-cite-attribution-alist nil | |
450 | gnus-cite-loose-prefix-alist nil | |
a8151ef7 LMI |
451 | gnus-cite-loose-attribution-alist nil |
452 | gnus-cite-article nil))))) | |
eec82323 LMI |
453 | |
454 | (defun gnus-article-hide-citation (&optional arg force) | |
455 | "Toggle hiding of all cited text except attribution lines. | |
456 | See the documentation for `gnus-article-highlight-citation'. | |
457 | If given a negative prefix, always show; if given a positive prefix, | |
458 | always hide." | |
459 | (interactive (append (gnus-article-hidden-arg) (list 'force))) | |
6748645f LMI |
460 | (gnus-set-format 'cited-opened-text-button t) |
461 | (gnus-set-format 'cited-closed-text-button t) | |
eec82323 LMI |
462 | (save-excursion |
463 | (set-buffer gnus-article-buffer) | |
464 | (cond | |
465 | ((gnus-article-check-hidden-text 'cite arg) | |
466 | t) | |
467 | ((gnus-article-text-type-exists-p 'cite) | |
468 | (let ((buffer-read-only nil)) | |
469 | (gnus-article-hide-text-of-type 'cite))) | |
470 | (t | |
471 | (let ((buffer-read-only nil) | |
472 | (marks (gnus-dissect-cited-text)) | |
473 | (inhibit-point-motion-hooks t) | |
474 | (props (nconc (list 'article-type 'cite) | |
475 | gnus-hidden-properties)) | |
6748645f | 476 | beg end start) |
eec82323 LMI |
477 | (while marks |
478 | (setq beg nil | |
479 | end nil) | |
480 | (while (and marks (string= (cdar marks) "")) | |
481 | (setq marks (cdr marks))) | |
482 | (when marks | |
483 | (setq beg (caar marks))) | |
484 | (while (and marks (not (string= (cdar marks) ""))) | |
485 | (setq marks (cdr marks))) | |
486 | (when marks | |
487 | (setq end (caar marks))) | |
488 | ;; Skip past lines we want to leave visible. | |
489 | (when (and beg end gnus-cited-lines-visible) | |
490 | (goto-char beg) | |
491 | (forward-line gnus-cited-lines-visible) | |
492 | (if (>= (point) end) | |
493 | (setq beg nil) | |
494 | (setq beg (point-marker)))) | |
495 | (when (and beg end) | |
6748645f LMI |
496 | ;; We use markers for the end-points to facilitate later |
497 | ;; wrapping and mangling of text. | |
498 | (setq beg (set-marker (make-marker) beg) | |
499 | end (set-marker (make-marker) end)) | |
eec82323 LMI |
500 | (gnus-add-text-properties beg end props) |
501 | (goto-char beg) | |
502 | (unless (save-excursion (search-backward "\n\n" nil t)) | |
503 | (insert "\n")) | |
504 | (put-text-property | |
6748645f | 505 | (setq start (point-marker)) |
eec82323 LMI |
506 | (progn |
507 | (gnus-article-add-button | |
508 | (point) | |
6748645f LMI |
509 | (progn (eval gnus-cited-closed-text-button-line-format-spec) |
510 | (point)) | |
511 | `gnus-article-toggle-cited-text | |
512 | (list (cons beg end) start)) | |
eec82323 LMI |
513 | (point)) |
514 | 'article-type 'annotation) | |
515 | (set-marker beg (point))))))))) | |
516 | ||
6748645f | 517 | (defun gnus-article-toggle-cited-text (args) |
eec82323 | 518 | "Toggle hiding the text in REGION." |
6748645f LMI |
519 | (let* ((region (car args)) |
520 | (start (cadr args)) | |
521 | (hidden | |
522 | (text-property-any | |
523 | (car region) (1- (cdr region)) | |
524 | (car gnus-hidden-properties) (cadr gnus-hidden-properties))) | |
525 | (inhibit-point-motion-hooks t) | |
526 | buffer-read-only) | |
eec82323 | 527 | (funcall |
6748645f | 528 | (if hidden |
eec82323 | 529 | 'remove-text-properties 'gnus-add-text-properties) |
6748645f LMI |
530 | (car region) (cdr region) gnus-hidden-properties) |
531 | (save-excursion | |
532 | (goto-char start) | |
533 | (gnus-delete-line) | |
534 | (put-text-property | |
535 | (point) | |
536 | (progn | |
537 | (gnus-article-add-button | |
538 | (point) | |
539 | (progn (eval | |
540 | (if hidden | |
541 | gnus-cited-opened-text-button-line-format-spec | |
542 | gnus-cited-closed-text-button-line-format-spec)) | |
543 | (point)) | |
544 | `gnus-article-toggle-cited-text | |
545 | args) | |
546 | (point)) | |
547 | 'article-type 'annotation)))) | |
eec82323 LMI |
548 | |
549 | (defun gnus-article-hide-citation-maybe (&optional arg force) | |
550 | "Toggle hiding of cited text that has an attribution line. | |
551 | If given a negative prefix, always show; if given a positive prefix, | |
552 | always hide. | |
553 | This will do nothing unless at least `gnus-cite-hide-percentage' | |
554 | percent and at least `gnus-cite-hide-absolute' lines of the body is | |
555 | cited text with attributions. When called interactively, these two | |
556 | variables are ignored. | |
557 | See also the documentation for `gnus-article-highlight-citation'." | |
6748645f | 558 | (interactive (append (gnus-article-hidden-arg) '(force))) |
eec82323 LMI |
559 | (unless (gnus-article-check-hidden-text 'cite arg) |
560 | (save-excursion | |
561 | (set-buffer gnus-article-buffer) | |
562 | (gnus-cite-parse-maybe force) | |
563 | (goto-char (point-min)) | |
564 | (search-forward "\n\n" nil t) | |
565 | (let ((start (point)) | |
566 | (atts gnus-cite-attribution-alist) | |
567 | (buffer-read-only nil) | |
568 | (inhibit-point-motion-hooks t) | |
6748645f | 569 | (hidden 0) |
eec82323 LMI |
570 | total) |
571 | (goto-char (point-max)) | |
572 | (gnus-article-search-signature) | |
573 | (setq total (count-lines start (point))) | |
574 | (while atts | |
6748645f LMI |
575 | (setq hidden (+ hidden (length (cdr (assoc (cdar atts) |
576 | gnus-cite-prefix-alist)))) | |
eec82323 LMI |
577 | atts (cdr atts))) |
578 | (when (or force | |
6748645f LMI |
579 | (and (> (* 100 hidden) (* gnus-cite-hide-percentage total)) |
580 | (> hidden gnus-cite-hide-absolute))) | |
eec82323 LMI |
581 | (setq atts gnus-cite-attribution-alist) |
582 | (while atts | |
583 | (setq total (cdr (assoc (cdar atts) gnus-cite-prefix-alist)) | |
584 | atts (cdr atts)) | |
585 | (while total | |
6748645f | 586 | (setq hidden (car total) |
eec82323 | 587 | total (cdr total)) |
6748645f LMI |
588 | (goto-line hidden) |
589 | (unless (assq hidden gnus-cite-attribution-alist) | |
eec82323 LMI |
590 | (gnus-add-text-properties |
591 | (point) (progn (forward-line 1) (point)) | |
592 | (nconc (list 'article-type 'cite) | |
593 | gnus-hidden-properties)))))))))) | |
594 | ||
595 | (defun gnus-article-hide-citation-in-followups () | |
596 | "Hide cited text in non-root articles." | |
597 | (interactive) | |
598 | (save-excursion | |
599 | (set-buffer gnus-article-buffer) | |
600 | (let ((article (cdr gnus-article-current))) | |
601 | (unless (save-excursion | |
602 | (set-buffer gnus-summary-buffer) | |
603 | (gnus-article-displayed-root-p article)) | |
604 | (gnus-article-hide-citation))))) | |
605 | ||
606 | ;;; Internal functions: | |
607 | ||
608 | (defun gnus-cite-parse-maybe (&optional force) | |
609 | ;; Parse if the buffer has changes since last time. | |
6748645f LMI |
610 | (if (and (not force) |
611 | (equal gnus-cite-article gnus-article-current)) | |
eec82323 | 612 | () |
6748645f | 613 | (gnus-cite-localize) |
eec82323 LMI |
614 | ;;Reset parser information. |
615 | (setq gnus-cite-prefix-alist nil | |
616 | gnus-cite-attribution-alist nil | |
617 | gnus-cite-loose-prefix-alist nil | |
618 | gnus-cite-loose-attribution-alist nil) | |
6748645f LMI |
619 | (while gnus-cite-overlay-list |
620 | (gnus-delete-overlay (pop gnus-cite-overlay-list))) | |
eec82323 LMI |
621 | ;; Parse if not too large. |
622 | (if (and (not force) | |
623 | gnus-cite-parse-max-size | |
624 | (> (buffer-size) gnus-cite-parse-max-size)) | |
625 | () | |
626 | (setq gnus-cite-article (cons (car gnus-article-current) | |
627 | (cdr gnus-article-current))) | |
628 | (gnus-cite-parse-wrapper)))) | |
629 | ||
630 | (defun gnus-cite-parse-wrapper () | |
631 | ;; Wrap chopped gnus-cite-parse | |
632 | (goto-char (point-min)) | |
633 | (unless (search-forward "\n\n" nil t) | |
634 | (goto-char (point-max))) | |
635 | (save-excursion | |
636 | (gnus-cite-parse-attributions)) | |
637 | ;; Try to avoid check citation if there is no reason to believe | |
638 | ;; that article has citations | |
639 | (if (or gnus-cite-always-check | |
640 | (save-excursion | |
641 | (re-search-backward gnus-cite-reply-regexp nil t)) | |
642 | gnus-cite-loose-attribution-alist) | |
643 | (progn (save-excursion | |
644 | (gnus-cite-parse)) | |
645 | (save-excursion | |
646 | (gnus-cite-connect-attributions))))) | |
647 | ||
648 | (defun gnus-cite-parse () | |
649 | ;; Parse and connect citation prefixes and attribution lines. | |
650 | ||
651 | ;; Parse current buffer searching for citation prefixes. | |
652 | (let ((line (1+ (count-lines (point-min) (point)))) | |
653 | (case-fold-search t) | |
654 | (max (save-excursion | |
655 | (goto-char (point-max)) | |
656 | (gnus-article-search-signature) | |
657 | (point))) | |
658 | alist entry start begin end numbers prefix) | |
659 | ;; Get all potential prefixes in `alist'. | |
660 | (while (< (point) max) | |
661 | ;; Each line. | |
662 | (setq begin (point) | |
663 | end (progn (beginning-of-line 2) (point)) | |
664 | start end) | |
665 | (goto-char begin) | |
666 | ;; Ignore standard Supercite attribution prefix. | |
667 | (when (looking-at gnus-supercite-regexp) | |
668 | (if (match-end 1) | |
669 | (setq end (1+ (match-end 1))) | |
670 | (setq end (1+ begin)))) | |
671 | ;; Ignore very long prefixes. | |
672 | (when (> end (+ (point) gnus-cite-max-prefix)) | |
673 | (setq end (+ (point) gnus-cite-max-prefix))) | |
674 | (while (re-search-forward gnus-cite-prefix-regexp (1- end) t) | |
675 | ;; Each prefix. | |
676 | (setq end (match-end 0) | |
677 | prefix (buffer-substring begin end)) | |
678 | (gnus-set-text-properties 0 (length prefix) nil prefix) | |
679 | (setq entry (assoc prefix alist)) | |
680 | (if entry | |
681 | (setcdr entry (cons line (cdr entry))) | |
682 | (push (list prefix line) alist)) | |
683 | (goto-char begin)) | |
684 | (goto-char start) | |
685 | (setq line (1+ line))) | |
686 | ;; We got all the potential prefixes. Now create | |
687 | ;; `gnus-cite-prefix-alist' containing the oldest prefix for each | |
688 | ;; line that appears at least gnus-cite-minimum-match-count | |
689 | ;; times. First sort them by length. Longer is older. | |
690 | (setq alist (sort alist (lambda (a b) | |
691 | (> (length (car a)) (length (car b)))))) | |
692 | (while alist | |
693 | (setq entry (car alist) | |
694 | prefix (car entry) | |
695 | numbers (cdr entry) | |
696 | alist (cdr alist)) | |
697 | (cond ((null numbers) | |
698 | ;; No lines with this prefix that wasn't also part of | |
699 | ;; a longer prefix. | |
700 | ) | |
701 | ((< (length numbers) gnus-cite-minimum-match-count) | |
702 | ;; Too few lines with this prefix. We keep it a bit | |
703 | ;; longer in case it is an exact match for an attribution | |
704 | ;; line, but we don't remove the line from other | |
705 | ;; prefixes. | |
706 | (push entry gnus-cite-prefix-alist)) | |
707 | (t | |
708 | (push entry | |
709 | gnus-cite-prefix-alist) | |
710 | ;; Remove articles from other prefixes. | |
711 | (let ((loop alist) | |
712 | current) | |
713 | (while loop | |
714 | (setq current (car loop) | |
715 | loop (cdr loop)) | |
716 | (setcdr current | |
717 | (gnus-set-difference (cdr current) numbers))))))))) | |
718 | ||
719 | (defun gnus-cite-parse-attributions () | |
720 | (let (al-alist) | |
721 | ;; Parse attributions | |
722 | (while (re-search-forward gnus-cite-attribution-suffix (point-max) t) | |
723 | (let* ((start (match-beginning 0)) | |
724 | (end (match-end 0)) | |
725 | (wrote (count-lines (point-min) end)) | |
726 | (prefix (gnus-cite-find-prefix wrote)) | |
727 | ;; Check previous line for an attribution leader. | |
728 | (tag (progn | |
729 | (beginning-of-line 1) | |
730 | (when (looking-at gnus-supercite-secondary-regexp) | |
731 | (buffer-substring (match-beginning 1) | |
732 | (match-end 1))))) | |
733 | (in (progn | |
734 | (goto-char start) | |
735 | (and (re-search-backward gnus-cite-attribution-prefix | |
736 | (save-excursion | |
737 | (beginning-of-line 0) | |
738 | (point)) | |
739 | t) | |
740 | (not (re-search-forward gnus-cite-attribution-suffix | |
741 | start t)) | |
742 | (count-lines (point-min) (1+ (point))))))) | |
743 | (when (eq wrote in) | |
744 | (setq in nil)) | |
745 | (goto-char end) | |
746 | ;; don't add duplicates | |
747 | (let ((al (buffer-substring (save-excursion (beginning-of-line 0) | |
748 | (1+ (point))) | |
749 | end))) | |
750 | (if (not (assoc al al-alist)) | |
751 | (progn | |
752 | (push (list wrote in prefix tag) | |
753 | gnus-cite-loose-attribution-alist) | |
754 | (push (cons al t) al-alist)))))))) | |
755 | ||
756 | (defun gnus-cite-connect-attributions () | |
757 | ;; Connect attributions to citations | |
758 | ||
759 | ;; No citations have been connected to attribution lines yet. | |
760 | (setq gnus-cite-loose-prefix-alist (append gnus-cite-prefix-alist nil)) | |
761 | ||
762 | ;; Parse current buffer searching for attribution lines. | |
763 | ;; Find exact supercite citations. | |
764 | (gnus-cite-match-attributions 'small nil | |
765 | (lambda (prefix tag) | |
766 | (when tag | |
767 | (concat "\\`" | |
768 | (regexp-quote prefix) "[ \t]*" | |
769 | (regexp-quote tag) ">")))) | |
770 | ;; Find loose supercite citations after attributions. | |
771 | (gnus-cite-match-attributions 'small t | |
772 | (lambda (prefix tag) | |
773 | (when tag | |
774 | (concat "\\<" | |
775 | (regexp-quote tag) | |
776 | "\\>")))) | |
777 | ;; Find loose supercite citations anywhere. | |
778 | (gnus-cite-match-attributions 'small nil | |
779 | (lambda (prefix tag) | |
780 | (when tag | |
781 | (concat "\\<" | |
782 | (regexp-quote tag) | |
783 | "\\>")))) | |
784 | ;; Find nested citations after attributions. | |
785 | (gnus-cite-match-attributions 'small-if-unique t | |
786 | (lambda (prefix tag) | |
787 | (concat "\\`" (regexp-quote prefix) ".+"))) | |
788 | ;; Find nested citations anywhere. | |
789 | (gnus-cite-match-attributions 'small nil | |
790 | (lambda (prefix tag) | |
791 | (concat "\\`" (regexp-quote prefix) ".+"))) | |
792 | ;; Remove loose prefixes with too few lines. | |
793 | (let ((alist gnus-cite-loose-prefix-alist) | |
794 | entry) | |
795 | (while alist | |
796 | (setq entry (car alist) | |
797 | alist (cdr alist)) | |
798 | (when (< (length (cdr entry)) gnus-cite-minimum-match-count) | |
799 | (setq gnus-cite-prefix-alist | |
800 | (delq entry gnus-cite-prefix-alist) | |
801 | gnus-cite-loose-prefix-alist | |
802 | (delq entry gnus-cite-loose-prefix-alist))))) | |
803 | ;; Find flat attributions. | |
804 | (gnus-cite-match-attributions 'first t nil) | |
805 | ;; Find any attributions (are we getting desperate yet?). | |
806 | (gnus-cite-match-attributions 'first nil nil)) | |
807 | ||
808 | (defun gnus-cite-match-attributions (sort after fun) | |
809 | ;; Match all loose attributions and citations (SORT AFTER FUN) . | |
810 | ;; | |
811 | ;; If SORT is `small', the citation with the shortest prefix will be | |
812 | ;; used, if it is `first' the first prefix will be used, if it is | |
813 | ;; `small-if-unique' the shortest prefix will be used if the | |
814 | ;; attribution line does not share its own prefix with other | |
815 | ;; loose attribution lines, otherwise the first prefix will be used. | |
816 | ;; | |
817 | ;; If AFTER is non-nil, only citations after the attribution line | |
818 | ;; will be considered. | |
819 | ;; | |
820 | ;; If FUN is non-nil, it will be called with the arguments (WROTE | |
821 | ;; PREFIX TAG) and expected to return a regular expression. Only | |
822 | ;; citations whose prefix matches the regular expression will be | |
823 | ;; considered. | |
824 | ;; | |
825 | ;; WROTE is the attribution line number. | |
826 | ;; PREFIX is the attribution line prefix. | |
827 | ;; TAG is the Supercite tag on the attribution line. | |
828 | (let ((atts gnus-cite-loose-attribution-alist) | |
829 | (case-fold-search t) | |
830 | att wrote in prefix tag regexp limit smallest best size) | |
831 | (while atts | |
832 | (setq att (car atts) | |
833 | atts (cdr atts) | |
834 | wrote (nth 0 att) | |
835 | in (nth 1 att) | |
836 | prefix (nth 2 att) | |
837 | tag (nth 3 att) | |
838 | regexp (if fun (funcall fun prefix tag) "") | |
839 | size (cond ((eq sort 'small) t) | |
840 | ((eq sort 'first) nil) | |
841 | (t (< (length (gnus-cite-find-loose prefix)) 2))) | |
842 | limit (if after wrote -1) | |
843 | smallest 1000000 | |
844 | best nil) | |
845 | (let ((cites gnus-cite-loose-prefix-alist) | |
846 | cite candidate numbers first compare) | |
847 | (while cites | |
848 | (setq cite (car cites) | |
849 | cites (cdr cites) | |
850 | candidate (car cite) | |
851 | numbers (cdr cite) | |
852 | first (apply 'min numbers) | |
853 | compare (if size (length candidate) first)) | |
854 | (and (> first limit) | |
855 | regexp | |
856 | (string-match regexp candidate) | |
857 | (< compare smallest) | |
858 | (setq best cite | |
859 | smallest compare)))) | |
860 | (if (null best) | |
861 | () | |
862 | (setq gnus-cite-loose-attribution-alist | |
863 | (delq att gnus-cite-loose-attribution-alist)) | |
864 | (push (cons wrote (car best)) gnus-cite-attribution-alist) | |
865 | (when in | |
866 | (push (cons in (car best)) gnus-cite-attribution-alist)) | |
867 | (when (memq best gnus-cite-loose-prefix-alist) | |
868 | (let ((loop gnus-cite-prefix-alist) | |
869 | (numbers (cdr best)) | |
870 | current) | |
871 | (setq gnus-cite-loose-prefix-alist | |
872 | (delq best gnus-cite-loose-prefix-alist)) | |
873 | (while loop | |
874 | (setq current (car loop) | |
875 | loop (cdr loop)) | |
876 | (if (eq current best) | |
877 | () | |
878 | (setcdr current (gnus-set-difference (cdr current) numbers)) | |
879 | (when (null (cdr current)) | |
880 | (setq gnus-cite-loose-prefix-alist | |
881 | (delq current gnus-cite-loose-prefix-alist) | |
882 | atts (delq current atts))))))))))) | |
883 | ||
884 | (defun gnus-cite-find-loose (prefix) | |
885 | ;; Return a list of loose attribution lines prefixed by PREFIX. | |
886 | (let* ((atts gnus-cite-loose-attribution-alist) | |
887 | att line lines) | |
888 | (while atts | |
889 | (setq att (car atts) | |
890 | line (car att) | |
891 | atts (cdr atts)) | |
892 | (when (string-equal (gnus-cite-find-prefix line) prefix) | |
893 | (push line lines))) | |
894 | lines)) | |
895 | ||
896 | (defun gnus-cite-add-face (number prefix face) | |
897 | ;; At line NUMBER, ignore PREFIX and add FACE to the rest of the line. | |
898 | (when face | |
899 | (let ((inhibit-point-motion-hooks t) | |
6748645f | 900 | from to overlay) |
eec82323 | 901 | (goto-line number) |
6748645f | 902 | (unless (eobp) ; Sometimes things become confused. |
eec82323 LMI |
903 | (forward-char (length prefix)) |
904 | (skip-chars-forward " \t") | |
905 | (setq from (point)) | |
906 | (end-of-line 1) | |
907 | (skip-chars-backward " \t") | |
908 | (setq to (point)) | |
909 | (when (< from to) | |
6748645f LMI |
910 | (push (setq overlay (gnus-make-overlay from to)) |
911 | gnus-cite-overlay-list) | |
912 | (gnus-overlay-put overlay 'face face)))))) | |
eec82323 LMI |
913 | |
914 | (defun gnus-cite-toggle (prefix) | |
915 | (save-excursion | |
916 | (set-buffer gnus-article-buffer) | |
6748645f | 917 | (gnus-cite-parse-maybe) |
eec82323 LMI |
918 | (let ((buffer-read-only nil) |
919 | (numbers (cdr (assoc prefix gnus-cite-prefix-alist))) | |
920 | (inhibit-point-motion-hooks t) | |
921 | number) | |
922 | (while numbers | |
923 | (setq number (car numbers) | |
924 | numbers (cdr numbers)) | |
925 | (goto-line number) | |
926 | (cond ((get-text-property (point) 'invisible) | |
927 | (remove-text-properties (point) (progn (forward-line 1) (point)) | |
928 | gnus-hidden-properties)) | |
929 | ((assq number gnus-cite-attribution-alist)) | |
930 | (t | |
931 | (gnus-add-text-properties | |
932 | (point) (progn (forward-line 1) (point)) | |
933 | (nconc (list 'article-type 'cite) | |
934 | gnus-hidden-properties)))))))) | |
935 | ||
936 | (defun gnus-cite-find-prefix (line) | |
937 | ;; Return citation prefix for LINE. | |
938 | (let ((alist gnus-cite-prefix-alist) | |
939 | (prefix "") | |
940 | entry) | |
941 | (while alist | |
942 | (setq entry (car alist) | |
943 | alist (cdr alist)) | |
944 | (when (memq line (cdr entry)) | |
945 | (setq prefix (car entry)))) | |
946 | prefix)) | |
947 | ||
6748645f LMI |
948 | (defun gnus-cite-localize () |
949 | "Make the citation variables local to the article buffer." | |
950 | (let ((vars '(gnus-cite-article | |
951 | gnus-cite-overlay-list gnus-cite-prefix-alist | |
952 | gnus-cite-attribution-alist gnus-cite-loose-prefix-alist | |
953 | gnus-cite-loose-attribution-alist))) | |
954 | (while vars | |
955 | (make-local-variable (pop vars))))) | |
eec82323 LMI |
956 | |
957 | (gnus-ems-redefine) | |
958 | ||
959 | (provide 'gnus-cite) | |
960 | ||
961 | ;;; gnus-cite.el ends here |