fc6b49bdca256a5f2d76361675cac9cd2cc2c2ab
[gnus] / lisp / gnus-art.el
1 ;;; gnus-art.el --- article mode commands for Gnus
2 ;; Copyright (C) 1996,97,98,99 Free Software Foundation, Inc.
3
4 ;; Author: Lars Magne Ingebrigtsen <larsi@gnus.org>
5 ;; Keywords: news
6
7 ;; This file is part of GNU Emacs.
8
9 ;; GNU Emacs is free software; you can redistribute it and/or modify
10 ;; it under the terms of the GNU General Public License as published by
11 ;; the Free Software Foundation; either version 2, or (at your option)
12 ;; any later version.
13
14 ;; GNU Emacs is distributed in the hope that it will be useful,
15 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
16 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 ;; GNU General Public License for more details.
18
19 ;; You should have received a copy of the GNU General Public License
20 ;; along with GNU Emacs; see the file COPYING.  If not, write to the
21 ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
22 ;; Boston, MA 02111-1307, USA.
23
24 ;;; Commentary:
25
26 ;;; Code:
27
28 (eval-when-compile (require 'cl))
29
30 (require 'custom)
31 (require 'gnus)
32 (require 'gnus-sum)
33 (require 'gnus-spec)
34 (require 'gnus-int)
35 (require 'browse-url)
36 (require 'mm-bodies)
37 (require 'mail-parse)
38 (require 'mm-decode)
39 (require 'mm-view)
40 (require 'wid-edit)
41 (require 'mm-uu)
42
43 (defgroup gnus-article nil
44   "Article display."
45   :link '(custom-manual "(gnus)The Article Buffer")
46   :group 'gnus)
47
48 (defgroup gnus-article-treat nil
49   "Treating article parts."
50   :link '(custom-manual "(gnus)Article Hiding")
51   :group 'gnus-article)
52
53 (defgroup gnus-article-hiding nil
54   "Hiding article parts."
55   :link '(custom-manual "(gnus)Article Hiding")
56   :group 'gnus-article)
57
58 (defgroup gnus-article-highlight nil
59   "Article highlighting."
60   :link '(custom-manual "(gnus)Article Highlighting")
61   :group 'gnus-article
62   :group 'gnus-visual)
63
64 (defgroup gnus-article-signature nil
65   "Article signatures."
66   :link '(custom-manual "(gnus)Article Signature")
67   :group 'gnus-article)
68
69 (defgroup gnus-article-headers nil
70   "Article headers."
71   :link '(custom-manual "(gnus)Hiding Headers")
72   :group 'gnus-article)
73
74 (defgroup gnus-article-washing nil
75   "Special commands on articles."
76   :link '(custom-manual "(gnus)Article Washing")
77   :group 'gnus-article)
78
79 (defgroup gnus-article-emphasis nil
80   "Fontisizing articles."
81   :link '(custom-manual "(gnus)Article Fontisizing")
82   :group 'gnus-article)
83
84 (defgroup gnus-article-saving nil
85   "Saving articles."
86   :link '(custom-manual "(gnus)Saving Articles")
87   :group 'gnus-article)
88
89 (defgroup gnus-article-mime nil
90   "Worshiping the MIME wonder."
91   :link '(custom-manual "(gnus)Using MIME")
92   :group 'gnus-article)
93
94 (defgroup gnus-article-buttons nil
95   "Pushable buttons in the article buffer."
96   :link '(custom-manual "(gnus)Article Buttons")
97   :group 'gnus-article)
98
99 (defgroup gnus-article-various nil
100   "Other article options."
101   :link '(custom-manual "(gnus)Misc Article")
102   :group 'gnus-article)
103
104 (defcustom gnus-ignored-headers
105   '("^Path:" "^Expires:" "^Date-Received:" "^References:" "^Xref:" "^Lines:"
106     "^Relay-Version:" "^Message-ID:" "^Approved:" "^Sender:" "^Received:"
107     "^X-UIDL:" "^MIME-Version:" "^Return-Path:" "^In-Reply-To:"
108     "^Content-Type:" "^Content-Transfer-Encoding:" "^X-WebTV-Signature:"
109     "^X-MimeOLE:" "^X-MSMail-Priority:" "^X-Priority:" "^X-Loop:"
110     "^X-Authentication-Warning:" "^X-MIME-Autoconverted:" "^X-Face:"
111     "^X-Attribution:" "^X-Originating-IP:" "^Delivered-To:"
112     "^NNTP-[-A-Za-z]+:" "^Distribution:" "^X-no-archive:" "^X-Trace:"
113     "^X-Complaints-To:" "^X-NNTP-Posting-Host:" "^X-Orig.*:"
114     "^Abuse-Reports-To:" "^Cache-Post-Path:" "^X-Article-Creation-Date:"
115     "^X-Poster:" "^X-Mail2News-Path:" "^X-Server-Date:" "^X-Cache:"
116     "^Originator:" "^X-Problems-To:" "^X-Auth-User:" "^X-Post-Time:"
117     "^X-Admin:" "^X-UID:" "^Resent-[-A-Za-z]+:" "^X-Mailing-List:"
118     "^Precedence:" "^Original-[-A-Za-z]+:" "^X-filename:" "^X-Orcpt:"
119     "^Old-Received:" "^X-Pgp-Fingerprint:" "^X-Pgp-Key-Id:"
120     "^X-Pgp-Public-Key-Url:" "^X-Auth:" "^X-From-Line:"
121     "^X-Gnus-Article-Number:" "^X-Majordomo:" "^X-Url:" "^X-Sender:"
122     "^X-Mailing-List:" "^MBOX-Line" "^Priority:" "^X-Pgp" "^X400-[-A-Za-z]+:"
123     "^Status:")
124   "*All headers that start with this regexp will be hidden.
125 This variable can also be a list of regexps of headers to be ignored.
126 If `gnus-visible-headers' is non-nil, this variable will be ignored."
127   :type '(choice :custom-show nil
128                  regexp
129                  (repeat regexp))
130   :group 'gnus-article-hiding)
131
132 (defcustom gnus-visible-headers
133   "^From:\\|^Newsgroups:\\|^Subject:\\|^Date:\\|^Followup-To:\\|^Reply-To:\\|^Organization:\\|^Summary:\\|^Keywords:\\|^To:\\|^[BGF]?Cc:\\|^Posted-To:\\|^Mail-Copies-To:\\|^Apparently-To:\\|^Gnus-Warning:\\|^Resent-From:\\|^X-Sent:"
134   "*All headers that do not match this regexp will be hidden.
135 This variable can also be a list of regexp of headers to remain visible.
136 If this variable is non-nil, `gnus-ignored-headers' will be ignored."
137   :type '(repeat :value-to-internal (lambda (widget value)
138                                       (custom-split-regexp-maybe value))
139                  :match (lambda (widget value)
140                           (or (stringp value)
141                               (widget-editable-list-match widget value)))
142                  regexp)
143   :group 'gnus-article-hiding)
144
145 (defcustom gnus-sorted-header-list
146   '("^From:" "^Subject:" "^Summary:" "^Keywords:" "^Newsgroups:"
147     "^Followup-To:" "^To:" "^Cc:" "^Date:" "^Organization:")
148   "*This variable is a list of regular expressions.
149 If it is non-nil, headers that match the regular expressions will
150 be placed first in the article buffer in the sequence specified by
151 this list."
152   :type '(repeat regexp)
153   :group 'gnus-article-hiding)
154
155 (defcustom gnus-boring-article-headers '(empty followup-to reply-to)
156   "Headers that are only to be displayed if they have interesting data.
157 Possible values in this list are `empty', `newsgroups', `followup-to',
158 `reply-to', `date', `long-to', and `many-to'."
159   :type '(set (const :tag "Headers with no content." empty)
160               (const :tag "Newsgroups with only one group." newsgroups)
161               (const :tag "Followup-to identical to newsgroups." followup-to)
162               (const :tag "Reply-to identical to from." reply-to)
163               (const :tag "Date less than four days old." date)
164               (const :tag "Very long To header." long-to)
165               (const :tag "Multiple To headers." many-to))
166   :group 'gnus-article-hiding)
167
168 (defcustom gnus-signature-separator '("^-- $" "^-- *$")
169   "Regexp matching signature separator.
170 This can also be a list of regexps.  In that case, it will be checked
171 from head to tail looking for a separator.  Searches will be done from
172 the end of the buffer."
173   :type '(repeat string)
174   :group 'gnus-article-signature)
175
176 (defcustom gnus-signature-limit nil
177    "Provide a limit to what is considered a signature.
178 If it is a number, no signature may not be longer (in characters) than
179 that number.  If it is a floating point number, no signature may be
180 longer (in lines) than that number.  If it is a function, the function
181 will be called without any parameters, and if it returns nil, there is
182 no signature in the buffer.  If it is a string, it will be used as a
183 regexp.  If it matches, the text in question is not a signature."
184   :type '(choice (integer :value 200)
185                  (number :value 4.0)
186                  (function :value fun)
187                  (regexp :value ".*"))
188   :group 'gnus-article-signature)
189
190 (defcustom gnus-hidden-properties '(invisible t intangible t)
191   "Property list to use for hiding text."
192   :type 'sexp
193   :group 'gnus-article-hiding)
194
195 (defcustom gnus-article-x-face-command
196   "{ echo '/* Width=48, Height=48 */'; uncompface; } | icontopbm | xv -quit -"
197   "*String or function to be executed to display an X-Face header.
198 If it is a string, the command will be executed in a sub-shell
199 asynchronously.  The compressed face will be piped to this command."
200   :type 'string                         ;Leave function case to Lisp.
201   :group 'gnus-article-washing)
202
203 (defcustom gnus-article-x-face-too-ugly nil
204   "Regexp matching posters whose face shouldn't be shown automatically."
205   :type '(choice regexp (const nil))
206   :group 'gnus-article-washing)
207
208 (defcustom gnus-emphasis-alist
209   (let ((format
210          "\\(\\s-\\|^\\|[-\"]\\|\\s(\\|\\s)\\)\\(%s\\(\\w+\\(\\s-+\\w+\\)*[.,]?\\)%s\\)\\(\\s-\\|[-?!.,;:\"]\\|\\s(\\|\\s)\\)")
211         (types
212          '(("_" "_" underline)
213            ("/" "/" italic)
214            ("\\*" "\\*" bold)
215            ("_/" "/_" underline-italic)
216            ("_\\*" "\\*_" underline-bold)
217            ("\\*/" "/\\*" bold-italic)
218            ("_\\*/" "/\\*_" underline-bold-italic))))
219     `(("\\(\\s-\\|^\\)\\(_\\(\\(\\w\\|_[^_]\\)+\\)_\\)\\(\\s-\\|[?!.,;]\\)"
220        2 3 gnus-emphasis-underline)
221       ,@(mapcar
222          (lambda (spec)
223            (list
224             (format format (car spec) (cadr spec))
225             2 3 (intern (format "gnus-emphasis-%s" (nth 2 spec)))))
226          types)))
227   "*Alist that says how to fontify certain phrases.
228 Each item looks like this:
229
230   (\"_\\\\(\\\\w+\\\\)_\" 0 1 'underline)
231
232 The first element is a regular expression to be matched.  The second
233 is a number that says what regular expression grouping used to find
234 the entire emphasized word.  The third is a number that says what
235 regexp grouping should be displayed and highlighted.  The fourth
236 is the face used for highlighting."
237   :type '(repeat (list :value ("" 0 0 default)
238                        regexp
239                        (integer :tag "Match group")
240                        (integer :tag "Emphasize group")
241                        face))
242   :group 'gnus-article-emphasis)
243
244 (defface gnus-emphasis-bold '((t (:bold t)))
245   "Face used for displaying strong emphasized text (*word*)."
246   :group 'gnus-article-emphasis)
247
248 (defface gnus-emphasis-italic '((t (:italic t)))
249   "Face used for displaying italic emphasized text (/word/)."
250   :group 'gnus-article-emphasis)
251
252 (defface gnus-emphasis-underline '((t (:underline t)))
253   "Face used for displaying underlined emphasized text (_word_)."
254   :group 'gnus-article-emphasis)
255
256 (defface gnus-emphasis-underline-bold '((t (:bold t :underline t)))
257   "Face used for displaying underlined bold emphasized text (_*word*_)."
258   :group 'gnus-article-emphasis)
259
260 (defface gnus-emphasis-underline-italic '((t (:italic t :underline t)))
261   "Face used for displaying underlined italic emphasized text (_*word*_)."
262   :group 'gnus-article-emphasis)
263
264 (defface gnus-emphasis-bold-italic '((t (:bold t :italic t)))
265   "Face used for displaying bold italic emphasized text (/*word*/)."
266   :group 'gnus-article-emphasis)
267
268 (defface gnus-emphasis-underline-bold-italic
269   '((t (:bold t :italic t :underline t)))
270   "Face used for displaying underlined bold italic emphasized text.
271 Esample: (_/*word*/_)."
272   :group 'gnus-article-emphasis)
273
274 (defcustom gnus-article-time-format "%a, %b %d %Y %T %Z"
275   "Format for display of Date headers in article bodies.
276 See `format-time-string' for the possible values.
277
278 The variable can also be function, which should return a complete Date
279 header.  The function is called with one argument, the time, which can
280 be fed to `format-time-string'."
281   :type '(choice string symbol)
282   :link '(custom-manual "(gnus)Article Date")
283   :group 'gnus-article-washing)
284
285 (eval-and-compile
286   (autoload 'mail-extract-address-components "mail-extr"))
287
288 (defcustom gnus-save-all-headers t
289   "*If non-nil, don't remove any headers before saving."
290   :group 'gnus-article-saving
291   :type 'boolean)
292
293 (defcustom gnus-prompt-before-saving 'always
294   "*This variable says how much prompting is to be done when saving articles.
295 If it is nil, no prompting will be done, and the articles will be
296 saved to the default files.  If this variable is `always', each and
297 every article that is saved will be preceded by a prompt, even when
298 saving large batches of articles.  If this variable is neither nil not
299 `always', there the user will be prompted once for a file name for
300 each invocation of the saving commands."
301   :group 'gnus-article-saving
302   :type '(choice (item always)
303                  (item :tag "never" nil)
304                  (sexp :tag "once" :format "%t\n" :value t)))
305
306 (defcustom gnus-saved-headers gnus-visible-headers
307   "Headers to keep if `gnus-save-all-headers' is nil.
308 If `gnus-save-all-headers' is non-nil, this variable will be ignored.
309 If that variable is nil, however, all headers that match this regexp
310 will be kept while the rest will be deleted before saving."
311   :group 'gnus-article-saving
312   :type 'regexp)
313
314 (defcustom gnus-default-article-saver 'gnus-summary-save-in-rmail
315   "A function to save articles in your favourite format.
316 The function must be interactively callable (in other words, it must
317 be an Emacs command).
318
319 Gnus provides the following functions:
320
321 * gnus-summary-save-in-rmail (Rmail format)
322 * gnus-summary-save-in-mail (Unix mail format)
323 * gnus-summary-save-in-folder (MH folder)
324 * gnus-summary-save-in-file (article format)
325 * gnus-summary-save-in-vm (use VM's folder format)
326 * gnus-summary-write-to-file (article format -- overwrite)."
327   :group 'gnus-article-saving
328   :type '(radio (function-item gnus-summary-save-in-rmail)
329                 (function-item gnus-summary-save-in-mail)
330                 (function-item gnus-summary-save-in-folder)
331                 (function-item gnus-summary-save-in-file)
332                 (function-item gnus-summary-save-in-vm)
333                 (function-item gnus-summary-write-to-file)))
334
335 (defcustom gnus-rmail-save-name 'gnus-plain-save-name
336   "A function generating a file name to save articles in Rmail format.
337 The function is called with NEWSGROUP, HEADERS, and optional LAST-FILE."
338   :group 'gnus-article-saving
339   :type 'function)
340
341 (defcustom gnus-mail-save-name 'gnus-plain-save-name
342   "A function generating a file name to save articles in Unix mail format.
343 The function is called with NEWSGROUP, HEADERS, and optional LAST-FILE."
344   :group 'gnus-article-saving
345   :type 'function)
346
347 (defcustom gnus-folder-save-name 'gnus-folder-save-name
348   "A function generating a file name to save articles in MH folder.
349 The function is called with NEWSGROUP, HEADERS, and optional LAST-FOLDER."
350   :group 'gnus-article-saving
351   :type 'function)
352
353 (defcustom gnus-file-save-name 'gnus-numeric-save-name
354   "A function generating a file name to save articles in article format.
355 The function is called with NEWSGROUP, HEADERS, and optional
356 LAST-FILE."
357   :group 'gnus-article-saving
358   :type 'function)
359
360 (defcustom gnus-split-methods
361   '((gnus-article-archive-name)
362     (gnus-article-nndoc-name))
363   "*Variable used to suggest where articles are to be saved.
364 For instance, if you would like to save articles related to Gnus in
365 the file \"gnus-stuff\", and articles related to VM in \"vm-stuff\",
366 you could set this variable to something like:
367
368  '((\"^Subject:.*gnus\\|^Newsgroups:.*gnus\" \"gnus-stuff\")
369    (\"^Subject:.*vm\\|^Xref:.*vm\" \"vm-stuff\"))
370
371 This variable is an alist where the where the key is the match and the
372 value is a list of possible files to save in if the match is non-nil.
373
374 If the match is a string, it is used as a regexp match on the
375 article.  If the match is a symbol, that symbol will be funcalled
376 from the buffer of the article to be saved with the newsgroup as the
377 parameter.  If it is a list, it will be evaled in the same buffer.
378
379 If this form or function returns a string, this string will be used as
380 a possible file name; and if it returns a non-nil list, that list will
381 be used as possible file names."
382   :group 'gnus-article-saving
383   :type '(repeat (choice (list :value (fun) function)
384                          (cons :value ("" "") regexp (repeat string))
385                          (sexp :value nil))))
386
387 (defcustom gnus-page-delimiter "^\^L"
388   "*Regexp describing what to use as article page delimiters.
389 The default value is \"^\^L\", which is a form linefeed at the
390 beginning of a line."
391   :type 'regexp
392   :group 'gnus-article-various)
393
394 (defcustom gnus-article-mode-line-format "Gnus: %g %S%m"
395   "*The format specification for the article mode line.
396 See `gnus-summary-mode-line-format' for a closer description.
397
398 The following additional specs are available:
399
400 %w  The article washing status.
401 %m  The number of MIME parts in the article."
402   :type 'string
403   :group 'gnus-article-various)
404
405 (defcustom gnus-article-mode-hook nil
406   "*A hook for Gnus article mode."
407   :type 'hook
408   :group 'gnus-article-various)
409
410 (defcustom gnus-article-menu-hook nil
411   "*Hook run after the creation of the article mode menu."
412   :type 'hook
413   :group 'gnus-article-various)
414
415 (defcustom gnus-article-prepare-hook nil
416   "*A hook called after an article has been prepared in the article buffer."
417   :type 'hook
418   :group 'gnus-article-various)
419
420 (defcustom gnus-article-hide-pgp-hook nil
421   "*A hook called after successfully hiding a PGP signature."
422   :type 'hook
423   :group 'gnus-article-various)
424
425 (defcustom gnus-article-button-face 'bold
426   "Face used for highlighting buttons in the article buffer.
427
428 An article button is a piece of text that you can activate by pressing
429 `RET' or `mouse-2' above it."
430   :type 'face
431   :group 'gnus-article-buttons)
432
433 (defcustom gnus-article-mouse-face 'highlight
434   "Face used for mouse highlighting in the article buffer.
435
436 Article buttons will be displayed in this face when the cursor is
437 above them."
438   :type 'face
439   :group 'gnus-article-buttons)
440
441 (defcustom gnus-signature-face 'gnus-signature-face
442   "Face used for highlighting a signature in the article buffer.
443 Obsolete; use the face `gnus-signature-face' for customizations instead."
444   :type 'face
445   :group 'gnus-article-highlight
446   :group 'gnus-article-signature)
447
448 (defface gnus-signature-face
449   '((((type x))
450      (:italic t)))
451   "Face used for highlighting a signature in the article buffer."
452   :group 'gnus-article-highlight
453   :group 'gnus-article-signature)
454
455 (defface gnus-header-from-face
456   '((((class color)
457       (background dark))
458      (:foreground "spring green"))
459     (((class color)
460       (background light))
461      (:foreground "red3"))
462     (t
463      (:italic t)))
464   "Face used for displaying from headers."
465   :group 'gnus-article-headers
466   :group 'gnus-article-highlight)
467
468 (defface gnus-header-subject-face
469   '((((class color)
470       (background dark))
471      (:foreground "SeaGreen3"))
472     (((class color)
473       (background light))
474      (:foreground "red4"))
475     (t
476      (:bold t :italic t)))
477   "Face used for displaying subject headers."
478   :group 'gnus-article-headers
479   :group 'gnus-article-highlight)
480
481 (defface gnus-header-newsgroups-face
482   '((((class color)
483       (background dark))
484      (:foreground "yellow" :italic t))
485     (((class color)
486       (background light))
487      (:foreground "MidnightBlue" :italic t))
488     (t
489      (:italic t)))
490   "Face used for displaying newsgroups headers."
491   :group 'gnus-article-headers
492   :group 'gnus-article-highlight)
493
494 (defface gnus-header-name-face
495   '((((class color)
496       (background dark))
497      (:foreground "SeaGreen"))
498     (((class color)
499       (background light))
500      (:foreground "maroon"))
501     (t
502      (:bold t)))
503   "Face used for displaying header names."
504   :group 'gnus-article-headers
505   :group 'gnus-article-highlight)
506
507 (defface gnus-header-content-face
508   '((((class color)
509       (background dark))
510      (:foreground "forest green" :italic t))
511     (((class color)
512       (background light))
513      (:foreground "indianred4" :italic t))
514     (t
515      (:italic t)))  "Face used for displaying header content."
516   :group 'gnus-article-headers
517   :group 'gnus-article-highlight)
518
519 (defcustom gnus-header-face-alist
520   '(("From" nil gnus-header-from-face)
521     ("Subject" nil gnus-header-subject-face)
522     ("Newsgroups:.*," nil gnus-header-newsgroups-face)
523     ("" gnus-header-name-face gnus-header-content-face))
524   "*Controls highlighting of article header.
525
526 An alist of the form (HEADER NAME CONTENT).
527
528 HEADER is a regular expression which should match the name of an
529 header header and NAME and CONTENT are either face names or nil.
530
531 The name of each header field will be displayed using the face
532 specified by the first element in the list where HEADER match the
533 header name and NAME is non-nil.  Similarly, the content will be
534 displayed by the first non-nil matching CONTENT face."
535   :group 'gnus-article-headers
536   :group 'gnus-article-highlight
537   :type '(repeat (list (regexp :tag "Header")
538                        (choice :tag "Name"
539                                (item :tag "skip" nil)
540                                (face :value default))
541                        (choice :tag "Content"
542                                (item :tag "skip" nil)
543                                (face :value default)))))
544
545 (defcustom gnus-article-decode-hook
546   '(article-decode-charset article-decode-encoded-words)
547   "*Hook run to decode charsets in articles."
548   :group 'gnus-article-headers
549   :type 'hook)
550
551 (defcustom gnus-display-mime-function 'gnus-display-mime
552   "Function to display MIME articles."
553   :group 'gnus-article-mime
554   :type 'function)
555
556 (defvar gnus-decode-header-function 'mail-decode-encoded-word-region
557   "Function used to decode headers.")
558
559 (defvar gnus-article-dumbquotes-map
560   '(("\202" ",")
561     ("\203" "f")
562     ("\204" ",,")
563     ("\205" "...")
564     ("\213" "<")
565     ("\214" "OE")
566     ("\221" "`")
567     ("\222" "'")
568     ("\223" "``")
569     ("\224" "''")
570     ("\225" "*")
571     ("\226" "-")
572     ("\227" "-")
573     ("\231" "(TM)")
574     ("\233" ">")
575     ("\234" "oe")
576     ("\264" "'"))
577   "Table for MS-to-Latin1 translation.")
578
579 (defcustom gnus-ignored-mime-types nil
580   "List of MIME types that should be ignored by Gnus."
581   :group 'gnus-article-mime
582   :type '(repeat regexp))
583
584 (defcustom gnus-unbuttonized-mime-types '(".*/.*")
585   "List of MIME types that should not be given buttons when rendered."
586   :group 'gnus-article-mime
587   :type '(repeat regexp))
588
589 (defcustom gnus-article-mime-part-function nil
590   "Function called with a MIME handle as the argument.
591 This is meant for people who want to do something automatic based
592 on parts -- for instance, adding Vcard info to a database."
593   :group 'gnus-article-mime
594   :type 'function)
595
596 ;;;
597 ;;; The treatment variables
598 ;;;
599
600 (defvar gnus-part-display-hook nil
601   "Hook called on parts that are to receive treatment.")
602
603 (defvar gnus-article-treat-custom
604   '(choice (const :tag "Off" nil)
605            (const :tag "On" t)
606            (const :tag "Header" head)
607            (const :tag "Last" last)
608            (integer :tag "Less")
609            (sexp :tag "Predicate")))
610
611 (defvar gnus-article-treat-types '("text/plain")
612   "Parts to treat.")
613
614 (defvar gnus-inhibit-treatment nil
615   "Whether to inhibit treatment.")
616
617 (defcustom gnus-treat-highlight-signature '(or last (typep "text/x-vcard"))
618   "Highlight the signature."
619   :group 'gnus-article-treat
620   :type gnus-article-treat-custom)
621
622 (defcustom gnus-treat-buttonize t
623   "Add buttons."
624   :group 'gnus-article-treat
625   :type gnus-article-treat-custom)
626
627 (defcustom gnus-treat-buttonize-head 'head
628   "Add buttons to the head."
629   :group 'gnus-article-treat
630   :type gnus-article-treat-custom)
631
632 (defcustom gnus-treat-emphasize t
633   "Emphasize text."
634   :group 'gnus-article-treat
635   :type gnus-article-treat-custom)
636
637 (defcustom gnus-treat-strip-cr nil
638   "Remove carriage returns."
639   :group 'gnus-article-treat
640   :type gnus-article-treat-custom)
641
642 (defcustom gnus-treat-hide-headers 'head
643   "Hide headers."
644   :group 'gnus-article-treat
645   :type gnus-article-treat-custom)
646
647 (defcustom gnus-treat-hide-boring-headers nil
648   "Hide boring headers."
649   :group 'gnus-article-treat
650   :type gnus-article-treat-custom)
651
652 (defcustom gnus-treat-hide-signature nil
653   "Hide the signature."
654   :group 'gnus-article-treat
655   :type gnus-article-treat-custom)
656
657 (defcustom gnus-treat-fill-article nil
658   "Fill the article."
659   :group 'gnus-article-treat
660   :type gnus-article-treat-custom)
661
662 (defcustom gnus-treat-hide-citation nil
663   "Hide cited text."
664   :group 'gnus-article-treat
665   :type gnus-article-treat-custom)
666
667 (defcustom gnus-treat-strip-pgp t
668   "Strip PGP signatures."
669   :group 'gnus-article-treat
670   :type gnus-article-treat-custom)
671
672 (defcustom gnus-treat-strip-pem nil
673   "Strip PEM signatures."
674   :group 'gnus-article-treat
675   :type gnus-article-treat-custom)
676
677 (defcustom gnus-treat-highlight-headers 'head
678   "Highlight the headers."
679   :group 'gnus-article-treat
680   :type gnus-article-treat-custom)
681
682 (defcustom gnus-treat-highlight-citation t
683   "Highlight cited text."
684   :group 'gnus-article-treat
685   :type gnus-article-treat-custom)
686
687 (defcustom gnus-treat-date-ut nil
688   "Display the Date in UT (GMT)."
689   :group 'gnus-article-treat
690   :type gnus-article-treat-custom)
691
692 (defcustom gnus-treat-date-local nil
693   "Display the Date in the local timezone."
694   :group 'gnus-article-treat
695   :type gnus-article-treat-custom)
696
697 (defcustom gnus-treat-date-lapsed nil
698   "Display the Date header in a way that says how much time has elapsed."
699   :group 'gnus-article-treat
700   :type gnus-article-treat-custom)
701
702 (defcustom gnus-treat-date-original nil
703   "Display the date in the original timezone."
704   :group 'gnus-article-treat
705   :type gnus-article-treat-custom)
706
707 (defcustom gnus-treat-strip-trailing-blank-lines nil
708   "Strip trailing blank lines."
709   :group 'gnus-article-treat
710   :type gnus-article-treat-custom)
711
712 (defcustom gnus-treat-strip-leading-blank-lines nil
713   "Strip leading blank lines."
714   :group 'gnus-article-treat
715   :type gnus-article-treat-custom)
716
717 (defcustom gnus-treat-strip-multiple-blank-lines nil
718   "Strip multiple blank lines."
719   :group 'gnus-article-treat
720   :type gnus-article-treat-custom)
721
722 (defcustom gnus-treat-strip-blank-lines nil
723   "Strip all blank lines."
724   :group 'gnus-article-treat
725   :type gnus-article-treat-custom)
726
727 (defcustom gnus-treat-overstrike t
728   "Treat overstrike highlighting."
729   :group 'gnus-article-treat
730   :type gnus-article-treat-custom)
731
732 (defcustom gnus-treat-display-xface (if (and gnus-xemacs (featurep 'xface))
733                                         'head nil)
734   "Display X-Face headers."
735   :group 'gnus-article-treat
736   :type gnus-article-treat-custom)
737
738 (defcustom gnus-treat-display-smileys (if (and gnus-xemacs
739                                                (featurep 'xpm))
740                                           t nil)
741   "Display smileys."
742   :group 'gnus-article-treat
743   :type gnus-article-treat-custom)
744
745 (defcustom gnus-treat-display-picons (if gnus-xemacs 'head nil)
746   "Display picons."
747   :group 'gnus-article-treat
748   :type gnus-article-treat-custom)
749
750 ;;; Internal variables
751
752 (defvar article-goto-body-goes-to-point-min-p nil)
753
754 (defvar gnus-article-mime-handle-alist-1 nil)
755 (defvar gnus-treatment-function-alist
756   '((gnus-treat-highlight-signature gnus-article-highlight-signature)
757     (gnus-treat-buttonize gnus-article-add-buttons)
758     (gnus-treat-buttonize-head gnus-article-add-buttons-to-head)
759     (gnus-treat-fill-article gnus-article-fill-cited-article)
760     (gnus-treat-strip-cr gnus-article-remove-cr)
761     (gnus-treat-hide-headers gnus-article-hide-headers)
762     (gnus-treat-hide-boring-headers gnus-article-hide-boring-headers)
763     (gnus-treat-hide-signature gnus-article-hide-signature)
764     (gnus-treat-hide-citation gnus-article-hide-citation)
765     (gnus-treat-strip-pgp gnus-article-hide-pgp)
766     (gnus-treat-strip-pem gnus-article-hide-pem)
767     (gnus-treat-highlight-headers gnus-article-highlight-headers)
768     (gnus-treat-highlight-citation gnus-article-highlight-citation)
769     (gnus-treat-highlight-signature gnus-article-highlight-signature)
770     (gnus-treat-emphasize gnus-article-emphasize)
771     (gnus-treat-date-ut gnus-article-date-ut)
772     (gnus-treat-date-local gnus-article-date-local)
773     (gnus-treat-date-lapsed gnus-article-date-lapsed)
774     (gnus-treat-date-original gnus-article-date-original)
775     (gnus-treat-strip-trailing-blank-lines
776      gnus-article-remove-trailing-blank-lines)
777     (gnus-treat-strip-leading-blank-lines
778      gnus-article-strip-leading-blank-lines)
779     (gnus-treat-strip-multiple-blank-lines
780      gnus-article-strip-multiple-blank-lines)
781     (gnus-treat-strip-blank-lines gnus-article-strip-blank-lines)
782     (gnus-treat-overstrike gnus-article-treat-overstrike)
783     (gnus-treat-display-xface gnus-article-display-x-face)
784     (gnus-treat-display-smileys gnus-smiley-display)
785     (gnus-treat-display-picons gnus-article-display-picons)))
786
787 (defvar gnus-article-mime-handle-alist nil)
788 (defvar article-lapsed-timer nil)
789 (defvar gnus-article-current-summary nil)
790
791 (defvar gnus-article-mode-syntax-table
792   (let ((table (copy-syntax-table text-mode-syntax-table)))
793     (modify-syntax-entry ?- "w" table)
794     (modify-syntax-entry ?> ")" table)
795     (modify-syntax-entry ?< "(" table)
796     table)
797   "Syntax table used in article mode buffers.
798 Initialized from `text-mode-syntax-table.")
799
800 (defvar gnus-save-article-buffer nil)
801
802 (defvar gnus-article-mode-line-format-alist
803   (nconc '((?w (gnus-article-wash-status) ?s)
804            (?m (gnus-article-mime-part-status) ?s))
805          gnus-summary-mode-line-format-alist))
806
807 (defvar gnus-number-of-articles-to-be-saved nil)
808
809 (defvar gnus-inhibit-hiding nil)
810
811 (defsubst gnus-article-hide-text (b e props)
812   "Set text PROPS on the B to E region, extending `intangible' 1 past B."
813   (add-text-properties b e props)
814   (when (memq 'intangible props)
815     (put-text-property
816      (max (1- b) (point-min))
817      b 'intangible (cddr (memq 'intangible props)))))
818
819 (defmacro gnus-with-article (article &rest forms)
820   "Select ARTICLE and perform FORMS in the original article buffer.
821 Then replace the article with the result."
822   `(progn
823      ;; We don't want the article to be marked as read.
824      (let (gnus-mark-article-hook)
825        (gnus-summary-select-article t t nil ,article))
826      (set-buffer gnus-original-article-buffer)
827      ,@forms
828      (if (not (gnus-check-backend-function
829                'request-replace-article (car gnus-article-current)))
830          (gnus-message 5 "Read-only group; not replacing")
831        (unless (gnus-request-replace-article
832                 ,article (car gnus-article-current)
833                 (current-buffer) t)
834          (error "Couldn't replace article")))
835      ;; The cache and backlog have to be flushed somewhat.
836      (when gnus-keep-backlog
837        (gnus-backlog-remove-article
838         (car gnus-article-current) (cdr gnus-article-current)))
839      (when gnus-use-cache
840        (gnus-cache-update-article
841         (car gnus-article-current) (cdr gnus-article-current)))))
842
843 (put 'gnus-with-article 'lisp-indent-function 1)
844 (put 'gnus-with-article 'edebug-form-spec '(form body))
845
846 (defsubst gnus-article-unhide-text (b e)
847   "Remove hidden text properties from region between B and E."
848   (remove-text-properties b e gnus-hidden-properties)
849   (when (memq 'intangible gnus-hidden-properties)
850     (put-text-property (max (1- b) (point-min))
851                        b 'intangible nil)))
852
853 (defun gnus-article-hide-text-type (b e type)
854   "Hide text of TYPE between B and E."
855   (gnus-article-hide-text
856    b e (cons 'article-type (cons type gnus-hidden-properties))))
857
858 (defun gnus-article-unhide-text-type (b e type)
859   "Unhide text of TYPE between B and E."
860   (remove-text-properties
861    b e (cons 'article-type (cons type gnus-hidden-properties)))
862   (when (memq 'intangible gnus-hidden-properties)
863     (put-text-property (max (1- b) (point-min))
864                        b 'intangible nil)))
865
866 (defun gnus-article-hide-text-of-type (type)
867   "Hide text of TYPE in the current buffer."
868   (save-excursion
869     (let ((b (point-min))
870           (e (point-max)))
871       (while (setq b (text-property-any b e 'article-type type))
872         (add-text-properties b (incf b) gnus-hidden-properties)))))
873
874 (defun gnus-article-delete-text-of-type (type)
875   "Delete text of TYPE in the current buffer."
876   (save-excursion
877     (let ((b (point-min)))
878       (while (setq b (text-property-any b (point-max) 'article-type type))
879         (delete-region
880          b (or (text-property-not-all b (point-max) 'article-type type)
881                (point-max)))))))
882
883 (defun gnus-article-delete-invisible-text ()
884   "Delete all invisible text in the current buffer."
885   (save-excursion
886     (let ((b (point-min)))
887       (while (setq b (text-property-any b (point-max) 'invisible t))
888         (delete-region
889          b (or (text-property-not-all b (point-max) 'invisible t)
890                (point-max)))))))
891
892 (defun gnus-article-text-type-exists-p (type)
893   "Say whether any text of type TYPE exists in the buffer."
894   (text-property-any (point-min) (point-max) 'article-type type))
895
896 (defsubst gnus-article-header-rank ()
897   "Give the rank of the string HEADER as given by `gnus-sorted-header-list'."
898   (let ((list gnus-sorted-header-list)
899         (i 0))
900     (while list
901       (when (looking-at (car list))
902         (setq list nil))
903       (setq list (cdr list))
904       (incf i))
905     i))
906
907 (defun article-hide-headers (&optional arg delete)
908   "Toggle whether to hide unwanted headers and possibly sort them as well.
909 If given a negative prefix, always show; if given a positive prefix,
910 always hide."
911   (interactive (gnus-article-hidden-arg))
912   (current-buffer)
913   (if (gnus-article-check-hidden-text 'headers arg)
914       ;; Show boring headers as well.
915       (gnus-article-show-hidden-text 'boring-headers)
916     ;; This function might be inhibited.
917     (unless gnus-inhibit-hiding
918       (save-excursion
919         (save-restriction
920           (let ((buffer-read-only nil)
921                 (case-fold-search t)
922                 (props (nconc (list 'article-type 'headers)
923                               gnus-hidden-properties))
924                 (max (1+ (length gnus-sorted-header-list)))
925                 (ignored (when (not gnus-visible-headers)
926                            (cond ((stringp gnus-ignored-headers)
927                                   gnus-ignored-headers)
928                                  ((listp gnus-ignored-headers)
929                                   (mapconcat 'identity gnus-ignored-headers
930                                              "\\|")))))
931                 (visible
932                  (cond ((stringp gnus-visible-headers)
933                         gnus-visible-headers)
934                        ((and gnus-visible-headers
935                              (listp gnus-visible-headers))
936                         (mapconcat 'identity gnus-visible-headers "\\|"))))
937                 (inhibit-point-motion-hooks t)
938                 beg)
939             ;; First we narrow to just the headers.
940             (widen)
941             (goto-char (point-min))
942             ;; Hide any "From " lines at the beginning of (mail) articles.
943             (while (looking-at "From ")
944               (forward-line 1))
945             (unless (bobp)
946               (if delete
947                   (delete-region (point-min) (point))
948                 (gnus-article-hide-text (point-min) (point) props)))
949             ;; Then treat the rest of the header lines.
950             (narrow-to-region
951              (point)
952              (if (search-forward "\n\n" nil t) ; if there's a body
953                  (progn (forward-line -1) (point))
954                (point-max)))
955             ;; Then we use the two regular expressions
956             ;; `gnus-ignored-headers' and `gnus-visible-headers' to
957             ;; select which header lines is to remain visible in the
958             ;; article buffer.
959             (goto-char (point-min))
960             (while (re-search-forward "^[^ \t]*:" nil t)
961               (beginning-of-line)
962               ;; Mark the rank of the header.
963               (put-text-property
964                (point) (1+ (point)) 'message-rank
965                (if (or (and visible (looking-at visible))
966                        (and ignored
967                             (not (looking-at ignored))))
968                    (gnus-article-header-rank)
969                  (+ 2 max)))
970               (forward-line 1))
971             (message-sort-headers-1)
972             (when (setq beg (text-property-any
973                              (point-min) (point-max) 'message-rank (+ 2 max)))
974               ;; We make the unwanted headers invisible.
975               (if delete
976                   (delete-region beg (point-max))
977                 ;; Suggested by Sudish Joseph <joseph@cis.ohio-state.edu>.
978                 (gnus-article-hide-text-type beg (point-max) 'headers))
979               ;; Work around XEmacs lossage.
980               (put-text-property (point-min) beg 'invisible nil))))))))
981
982 (defun article-hide-boring-headers (&optional arg)
983   "Toggle hiding of headers that aren't very interesting.
984 If given a negative prefix, always show; if given a positive prefix,
985 always hide."
986   (interactive (gnus-article-hidden-arg))
987   (when (and (not (gnus-article-check-hidden-text 'boring-headers arg))
988              (not gnus-show-all-headers))
989     (save-excursion
990       (save-restriction
991         (let ((buffer-read-only nil)
992               (list gnus-boring-article-headers)
993               (inhibit-point-motion-hooks t)
994               elem)
995           (nnheader-narrow-to-headers)
996           (while list
997             (setq elem (pop list))
998             (goto-char (point-min))
999             (cond
1000              ;; Hide empty headers.
1001              ((eq elem 'empty)
1002               (while (re-search-forward "^[^:]+:[ \t]*\n[^ \t]" nil t)
1003                 (forward-line -1)
1004                 (gnus-article-hide-text-type
1005                  (progn (beginning-of-line) (point))
1006                  (progn
1007                    (end-of-line)
1008                    (if (re-search-forward "^[^ \t]" nil t)
1009                        (match-beginning 0)
1010                      (point-max)))
1011                  'boring-headers)))
1012              ;; Hide boring Newsgroups header.
1013              ((eq elem 'newsgroups)
1014               (when (equal (gnus-fetch-field "newsgroups")
1015                            (gnus-group-real-name
1016                             (if (boundp 'gnus-newsgroup-name)
1017                                 gnus-newsgroup-name
1018                               "")))
1019                 (gnus-article-hide-header "newsgroups")))
1020              ((eq elem 'followup-to)
1021               (when (equal (message-fetch-field "followup-to")
1022                            (message-fetch-field "newsgroups"))
1023                 (gnus-article-hide-header "followup-to")))
1024              ((eq elem 'reply-to)
1025               (let ((from (message-fetch-field "from"))
1026                     (reply-to (message-fetch-field "reply-to")))
1027                 (when (and
1028                        from reply-to
1029                        (ignore-errors
1030                          (equal
1031                           (nth 1 (mail-extract-address-components from))
1032                           (nth 1 (mail-extract-address-components reply-to)))))
1033                   (gnus-article-hide-header "reply-to"))))
1034              ((eq elem 'date)
1035               (let ((date (message-fetch-field "date")))
1036                 (when (and date
1037                            (< (days-between (current-time-string) date)
1038                               4))
1039                   (gnus-article-hide-header "date"))))
1040              ((eq elem 'long-to)
1041               (let ((to (message-fetch-field "to")))
1042                 (when (> (length to) 1024)
1043                   (gnus-article-hide-header "to"))))
1044              ((eq elem 'many-to)
1045               (let ((to-count 0))
1046                 (goto-char (point-min))
1047                 (while (re-search-forward "^to:" nil t)
1048                   (setq to-count (1+ to-count)))
1049                 (when (> to-count 1)
1050                   (while (> to-count 0)
1051                     (goto-char (point-min))
1052                     (save-restriction
1053                       (re-search-forward "^to:" nil nil to-count)
1054                       (forward-line -1)
1055                       (narrow-to-region (point) (point-max))
1056                       (gnus-article-hide-header "to"))
1057                     (setq to-count (1- to-count)))))))))))))
1058
1059 (defun gnus-article-hide-header (header)
1060   (save-excursion
1061     (goto-char (point-min))
1062     (when (re-search-forward (concat "^" header ":") nil t)
1063       (gnus-article-hide-text-type
1064        (progn (beginning-of-line) (point))
1065        (progn
1066          (end-of-line)
1067          (if (re-search-forward "^[^ \t]" nil t)
1068              (match-beginning 0)
1069            (point-max)))
1070        'boring-headers))))
1071
1072 (defvar gnus-article-normalized-header-length 40
1073   "Length of normalized headers.")
1074
1075 (defun article-normalize-headers ()
1076   "Make all header lines 40 characters long."
1077   (interactive)
1078   (let ((buffer-read-only nil)
1079         column)
1080     (save-excursion
1081       (save-restriction
1082         (message-narrow-to-head)
1083         (while (not (eobp))
1084           (cond
1085            ((< (setq column (- (gnus-point-at-eol) (point)))
1086                gnus-article-normalized-header-length)
1087             (end-of-line)
1088             (insert (make-string
1089                      (- gnus-article-normalized-header-length column)
1090                      ? )))
1091            ((> column gnus-article-normalized-header-length)
1092             (gnus-put-text-property
1093              (progn
1094                (forward-char gnus-article-normalized-header-length)
1095                (point))
1096              (gnus-point-at-eol)
1097              'invisible t))
1098            (t
1099             ;; Do nothing.
1100             ))
1101           (forward-line 1))))))
1102
1103 (defun article-treat-dumbquotes ()
1104   "Translate M******** sm*rtq**t*s into proper text.
1105 Note that this function guesses whether a character is a sm*rtq**t* or
1106 not, so it should only be used interactively."
1107   (interactive)
1108   (article-translate-strings gnus-article-dumbquotes-map))
1109
1110 (defun article-translate-characters (from to)
1111   "Translate all characters in the body of the article according to FROM and TO.
1112 FROM is a string of characters to translate from; to is a string of
1113 characters to translate to."
1114   (save-excursion
1115     (when (article-goto-body)
1116       (let ((buffer-read-only nil)
1117             (x (make-string 225 ?x))
1118             (i -1))
1119         (while (< (incf i) (length x))
1120           (aset x i i))
1121         (setq i 0)
1122         (while (< i (length from))
1123           (aset x (aref from i) (aref to i))
1124           (incf i))
1125         (translate-region (point) (point-max) x)))))
1126
1127 (defun article-translate-strings (map)
1128   "Translate all string in the body of the article according to MAP.
1129 MAP is an alist where the elements are on the form (\"from\" \"to\")."
1130   (save-excursion
1131     (when (article-goto-body)
1132       (let ((buffer-read-only nil)
1133             elem)
1134         (while (setq elem (pop map))
1135           (save-excursion
1136             (while (search-forward (car elem) nil t)
1137               (replace-match (cadr elem)))))))))
1138
1139 (defun article-treat-overstrike ()
1140   "Translate overstrikes into bold text."
1141   (interactive)
1142   (save-excursion
1143     (when (article-goto-body)
1144       (let ((buffer-read-only nil))
1145         (while (search-forward "\b" nil t)
1146           (let ((next (char-after))
1147                 (previous (char-after (- (point) 2))))
1148             ;; We do the boldification/underlining by hiding the
1149             ;; overstrikes and putting the proper text property
1150             ;; on the letters.
1151             (cond
1152              ((eq next previous)
1153               (gnus-article-hide-text-type (- (point) 2) (point) 'overstrike)
1154               (put-text-property (point) (1+ (point)) 'face 'bold))
1155              ((eq next ?_)
1156               (gnus-article-hide-text-type
1157                (1- (point)) (1+ (point)) 'overstrike)
1158               (put-text-property
1159                (- (point) 2) (1- (point)) 'face 'underline))
1160              ((eq previous ?_)
1161               (gnus-article-hide-text-type (- (point) 2) (point) 'overstrike)
1162               (put-text-property
1163                (point) (1+ (point)) 'face 'underline)))))))))
1164
1165 (defun article-fill-long-lines ()
1166   "Fill lines that are wider than the window width."
1167   (interactive)
1168   (save-excursion
1169     (let ((buffer-read-only nil)
1170           (width (window-width (get-buffer-window (current-buffer)))))
1171       (save-restriction
1172         (widen)
1173         (article-goto-body)
1174         (let ((adaptive-fill-mode nil))
1175           (while (not (eobp))
1176             (end-of-line)
1177             (when (>= (current-column) (min fill-column width))
1178               (narrow-to-region (point) (gnus-point-at-bol))
1179               (fill-paragraph nil)
1180               (goto-char (point-max))
1181               (widen))
1182             (forward-line 1)))))))
1183
1184 (defun article-remove-cr ()
1185   "Translate CRLF pairs into LF, and then CR into LF.."
1186   (interactive)
1187   (save-excursion
1188     (let ((buffer-read-only nil))
1189       (goto-char (point-min))
1190       (while (search-forward "\r$" nil t)
1191         (replace-match "" t t))
1192       (goto-char (point-min))
1193       (while (search-forward "\r" nil t)
1194         (replace-match "\n" t t)))))
1195
1196 (defun article-remove-trailing-blank-lines ()
1197   "Remove all trailing blank lines from the article."
1198   (interactive)
1199   (save-excursion
1200     (let ((buffer-read-only nil))
1201       (goto-char (point-max))
1202       (delete-region
1203        (point)
1204        (progn
1205          (while (and (not (bobp))
1206                      (looking-at "^[ \t]*$")
1207                      (not (gnus-annotation-in-region-p
1208                            (point) (gnus-point-at-eol))))
1209            (forward-line -1))
1210          (forward-line 1)
1211          (point))))))
1212
1213 (defun article-display-x-face (&optional force)
1214   "Look for an X-Face header and display it if present."
1215   (interactive (list 'force))
1216   (save-excursion
1217     ;; Delete the old process, if any.
1218     (when (process-status "article-x-face")
1219       (delete-process "article-x-face"))
1220     (let ((inhibit-point-motion-hooks t)
1221           (case-fold-search t)
1222           from last)
1223       (save-restriction
1224         (nnheader-narrow-to-headers)
1225         (setq from (message-fetch-field "from"))
1226         (goto-char (point-min))
1227         (while (and gnus-article-x-face-command
1228                     (not last)
1229                     (or force
1230                         ;; Check whether this face is censored.
1231                         (not gnus-article-x-face-too-ugly)
1232                         (and gnus-article-x-face-too-ugly from
1233                              (not (string-match gnus-article-x-face-too-ugly
1234                                                 from))))
1235                     ;; Has to be present.
1236                     (re-search-forward "^X-Face: " nil t))
1237           ;; This used to try to do multiple faces (`while' instead of
1238           ;; `when' above), but (a) sending multiple EOFs to xv doesn't
1239           ;; work (b) it can crash some versions of Emacs (c) are
1240           ;; multiple faces really something to encourage?
1241           (when (stringp gnus-article-x-face-command)
1242             (setq last t))
1243           ;; We now have the area of the buffer where the X-Face is stored.
1244           (save-excursion
1245             (let ((beg (point))
1246                   (end (1- (re-search-forward "^\\($\\|[^ \t]\\)" nil t))))
1247               ;; We display the face.
1248               (if (symbolp gnus-article-x-face-command)
1249                   ;; The command is a lisp function, so we call it.
1250                   (if (gnus-functionp gnus-article-x-face-command)
1251                       (funcall gnus-article-x-face-command beg end)
1252                     (error "%s is not a function" gnus-article-x-face-command))
1253                 ;; The command is a string, so we interpret the command
1254                 ;; as a, well, command, and fork it off.
1255                 (let ((process-connection-type nil))
1256                   (process-kill-without-query
1257                    (start-process
1258                     "article-x-face" nil shell-file-name shell-command-switch
1259                     gnus-article-x-face-command))
1260                   (process-send-region "article-x-face" beg end)
1261                   (process-send-eof "article-x-face"))))))))))
1262
1263 (defun article-decode-mime-words ()
1264   "Decode all MIME-encoded words in the article."
1265   (interactive)
1266   (save-excursion
1267     (set-buffer gnus-article-buffer)
1268     (let ((inhibit-point-motion-hooks t)
1269           buffer-read-only
1270           (mail-parse-charset gnus-newsgroup-charset))
1271       (mail-decode-encoded-word-region (point-min) (point-max)))))
1272
1273 (defun article-decode-charset (&optional prompt)
1274   "Decode charset-encoded text in the article.
1275 If PROMPT (the prefix), prompt for a coding system to use."
1276   (interactive "P")
1277   (save-excursion
1278     (save-restriction
1279       (message-narrow-to-head)
1280       (let* ((inhibit-point-motion-hooks t)
1281              (case-fold-search t)
1282              (ct (message-fetch-field "Content-Type" t))
1283              (cte (message-fetch-field "Content-Transfer-Encoding" t))
1284              (ctl (and ct (condition-case ()
1285                               (mail-header-parse-content-type ct)
1286                             (error nil))))
1287              (charset (cond
1288                        (prompt
1289                         (mm-read-coding-system "Charset to decode: "))
1290                        (ctl
1291                         (mail-content-type-get ctl 'charset))))
1292              (mail-parse-charset gnus-newsgroup-charset)
1293              buffer-read-only)
1294         (goto-char (point-max))
1295         (widen)
1296         (forward-line 1)
1297         (narrow-to-region (point) (point-max))
1298         (when (and (or (not ctl)
1299                        (equal (car ctl) "text/plain"))
1300                    (not (mm-uu-test)))
1301           (mm-decode-body
1302            charset (and cte (intern (downcase
1303                                      (gnus-strip-whitespace cte))))
1304            (car ctl)))))))
1305
1306 (defun article-decode-encoded-words ()
1307   "Remove encoded-word encoding from headers."
1308   (let ((inhibit-point-motion-hooks t)
1309         (mail-parse-charset gnus-newsgroup-charset)
1310         buffer-read-only)
1311     (save-restriction
1312       (message-narrow-to-head)
1313       (funcall gnus-decode-header-function (point-min) (point-max)))))
1314
1315 (defun article-de-quoted-unreadable (&optional force)
1316   "Translate a quoted-printable-encoded article.
1317 If FORCE, decode the article whether it is marked as quoted-printable
1318 or not."
1319   (interactive (list 'force))
1320   (save-excursion
1321     (let ((buffer-read-only nil)
1322           (type (gnus-fetch-field "content-transfer-encoding"))
1323           (charset gnus-newsgroup-charset))
1324       (when (or force
1325                 (and type (string-match "quoted-printable" (downcase type))))
1326         (article-goto-body)
1327         (save-restriction
1328           (narrow-to-region (point) (point-max))
1329           (quoted-printable-decode-region (point-min) (point-max))
1330           (when charset
1331             (mm-decode-body charset)))))))
1332
1333 (defun article-hide-pgp (&optional arg)
1334   "Toggle hiding of any PGP headers and signatures in the current article.
1335 If given a negative prefix, always show; if given a positive prefix,
1336 always hide."
1337   (interactive (gnus-article-hidden-arg))
1338   (unless (gnus-article-check-hidden-text 'pgp arg)
1339     (save-excursion
1340       (let ((inhibit-point-motion-hooks t)
1341             buffer-read-only beg end)
1342         (widen)
1343         (goto-char (point-min))
1344         ;; Hide the "header".
1345         (when (search-forward "\n-----BEGIN PGP SIGNED MESSAGE-----\n" nil t)
1346           (delete-region (1+ (match-beginning 0)) (match-end 0))
1347           ;; PGP 5 and GNU PG add a `Hash: <>' comment, hide that too
1348           (when (looking-at "Hash:.*$")
1349             (delete-region (point) (1+ (gnus-point-at-eol))))
1350           (setq beg (point))
1351           ;; Hide the actual signature.
1352           (and (search-forward "\n-----BEGIN PGP SIGNATURE-----\n" nil t)
1353                (setq end (1+ (match-beginning 0)))
1354                (delete-region
1355                 end
1356                 (if (search-forward "\n-----END PGP SIGNATURE-----\n" nil t)
1357                     (match-end 0)
1358                   ;; Perhaps we shouldn't hide to the end of the buffer
1359                   ;; if there is no end to the signature?
1360                   (point-max))))
1361           ;; Hide "- " PGP quotation markers.
1362           (when (and beg end)
1363             (narrow-to-region beg end)
1364             (goto-char (point-min))
1365             (while (re-search-forward "^- " nil t)
1366               (delete-region
1367                (match-beginning 0) (match-end 0)))
1368             (widen))
1369           (gnus-run-hooks 'gnus-article-hide-pgp-hook))))))
1370
1371 (defun article-hide-pem (&optional arg)
1372   "Toggle hiding of any PEM headers and signatures in the current article.
1373 If given a negative prefix, always show; if given a positive prefix,
1374 always hide."
1375   (interactive (gnus-article-hidden-arg))
1376   (unless (gnus-article-check-hidden-text 'pem arg)
1377     (save-excursion
1378       (let (buffer-read-only end)
1379         (widen)
1380         (goto-char (point-min))
1381         ;; hide the horrendously ugly "header".
1382         (and (search-forward "\n-----BEGIN PRIVACY-ENHANCED MESSAGE-----\n"
1383                              nil
1384                              t)
1385              (setq end (1+ (match-beginning 0)))
1386              (gnus-article-hide-text-type
1387               end
1388               (if (search-forward "\n\n" nil t)
1389                   (match-end 0)
1390                 (point-max))
1391               'pem))
1392         ;; hide the trailer as well
1393         (and (search-forward "\n-----END PRIVACY-ENHANCED MESSAGE-----\n"
1394                              nil
1395                              t)
1396              (gnus-article-hide-text-type
1397               (match-beginning 0) (match-end 0) 'pem))))))
1398
1399 (defun article-hide-signature (&optional arg)
1400   "Hide the signature in the current article.
1401 If given a negative prefix, always show; if given a positive prefix,
1402 always hide."
1403   (interactive (gnus-article-hidden-arg))
1404   (unless (gnus-article-check-hidden-text 'signature arg)
1405     (save-excursion
1406       (save-restriction
1407         (let ((buffer-read-only nil))
1408           (when (gnus-article-narrow-to-signature)
1409             (gnus-article-hide-text-type
1410              (point-min) (point-max) 'signature)))))))
1411
1412 (defun article-strip-leading-blank-lines ()
1413   "Remove all blank lines from the beginning of the article."
1414   (interactive)
1415   (save-excursion
1416     (let ((inhibit-point-motion-hooks t)
1417           buffer-read-only)
1418       (when (article-goto-body)
1419         (while (and (not (eobp))
1420                     (looking-at "[ \t]*$"))
1421           (gnus-delete-line))))))
1422
1423 (defun article-goto-body ()
1424   "Place point at the start of the body."
1425   (goto-char (point-min))
1426   (cond
1427    (article-goto-body-goes-to-point-min-p
1428     t)
1429    ((search-forward "\n\n" nil t)
1430     t)
1431    (t
1432     (goto-char (point-max))
1433     nil)))
1434
1435 (defun article-strip-multiple-blank-lines ()
1436   "Replace consecutive blank lines with one empty line."
1437   (interactive)
1438   (save-excursion
1439     (let ((inhibit-point-motion-hooks t)
1440           buffer-read-only)
1441       ;; First make all blank lines empty.
1442       (article-goto-body)
1443       (while (re-search-forward "^[ \t]+$" nil t)
1444         (unless (gnus-annotation-in-region-p
1445                  (match-beginning 0) (match-end 0))
1446           (replace-match "" nil t)))
1447       ;; Then replace multiple empty lines with a single empty line.
1448       (article-goto-body)
1449       (while (re-search-forward "\n\n\n+" nil t)
1450         (unless (gnus-annotation-in-region-p
1451                  (match-beginning 0) (match-end 0))
1452           (replace-match "\n\n" t t))))))
1453
1454 (defun article-strip-leading-space ()
1455   "Remove all white space from the beginning of the lines in the article."
1456   (interactive)
1457   (save-excursion
1458     (let ((inhibit-point-motion-hooks t)
1459           buffer-read-only)
1460       (article-goto-body)
1461       (while (re-search-forward "^[ \t]+" nil t)
1462         (replace-match "" t t)))))
1463
1464 (defun article-strip-trailing-space ()
1465   "Remove all white space from the end of the lines in the article."
1466   (interactive)
1467   (save-excursion
1468     (let ((inhibit-point-motion-hooks t)
1469           buffer-read-only)
1470       (article-goto-body)
1471       (while (re-search-forward "[ \t]+$" nil t)
1472         (replace-match "" t t)))))
1473
1474 (defun article-strip-blank-lines ()
1475   "Strip leading, trailing and multiple blank lines."
1476   (interactive)
1477   (article-strip-leading-blank-lines)
1478   (article-remove-trailing-blank-lines)
1479   (article-strip-multiple-blank-lines))
1480
1481 (defun article-strip-all-blank-lines ()
1482   "Strip all blank lines."
1483   (interactive)
1484   (save-excursion
1485     (let ((inhibit-point-motion-hooks t)
1486           buffer-read-only)
1487       (article-goto-body)
1488       (while (re-search-forward "^[ \t]*\n" nil t)
1489         (replace-match "" t t)))))
1490
1491 (defun gnus-article-narrow-to-signature ()
1492   "Narrow to the signature; return t if a signature is found, else nil."
1493   (widen)
1494   (let ((inhibit-point-motion-hooks t))
1495     (when (gnus-article-search-signature)
1496       (forward-line 1)
1497       ;; Check whether we have some limits to what we consider
1498       ;; to be a signature.
1499       (let ((limits (if (listp gnus-signature-limit) gnus-signature-limit
1500                       (list gnus-signature-limit)))
1501             limit limited)
1502         (while (setq limit (pop limits))
1503           (if (or (and (integerp limit)
1504                        (< (- (point-max) (point)) limit))
1505                   (and (floatp limit)
1506                        (< (count-lines (point) (point-max)) limit))
1507                   (and (gnus-functionp limit)
1508                        (funcall limit))
1509                   (and (stringp limit)
1510                        (not (re-search-forward limit nil t))))
1511               ()                        ; This limit did not succeed.
1512             (setq limited t
1513                   limits nil)))
1514         (unless limited
1515           (narrow-to-region (point) (point-max))
1516           t)))))
1517
1518 (defun gnus-article-search-signature ()
1519   "Search the current buffer for the signature separator.
1520 Put point at the beginning of the signature separator."
1521   (let ((cur (point)))
1522     (goto-char (point-max))
1523     (if (if (stringp gnus-signature-separator)
1524             (re-search-backward gnus-signature-separator nil t)
1525           (let ((seps gnus-signature-separator))
1526             (while (and seps
1527                         (not (re-search-backward (car seps) nil t)))
1528               (pop seps))
1529             seps))
1530         t
1531       (goto-char cur)
1532       nil)))
1533
1534 (eval-and-compile
1535   (autoload 'w3-display "w3-parse")
1536   (autoload 'w3-do-setup "w3" "" t)
1537   (autoload 'w3-region "w3-display" "" t))
1538
1539 (defun gnus-article-treat-html ()
1540   "Render HTML."
1541   (interactive)
1542   (let ((cbuf (current-buffer)))
1543     (set-buffer gnus-article-buffer)
1544     (let (buf buffer-read-only b e)
1545       (w3-do-setup)
1546       (goto-char (point-min))
1547       (narrow-to-region
1548        (if (search-forward "\n\n" nil t)
1549            (setq b (point))
1550          (point-max))
1551        (setq e (point-max)))
1552       (with-temp-buffer
1553         (insert-buffer-substring gnus-article-buffer b e)
1554         (require 'url)
1555         (save-window-excursion
1556           (w3-region (point-min) (point-max))
1557           (setq buf (buffer-substring-no-properties (point-min) (point-max)))))
1558       (when buf
1559         (delete-region (point-min) (point-max))
1560         (insert buf))
1561       (widen)
1562       (goto-char (point-min))
1563       (set-window-start (get-buffer-window (current-buffer)) (point-min))
1564       (set-buffer cbuf))))
1565
1566 (defun gnus-article-hidden-arg ()
1567   "Return the current prefix arg as a number, or 0 if no prefix."
1568   (list (if current-prefix-arg
1569             (prefix-numeric-value current-prefix-arg)
1570           0)))
1571
1572 (defun gnus-article-check-hidden-text (type arg)
1573   "Return nil if hiding is necessary.
1574 Arg can be nil or a number.  Nil and positive means hide, negative
1575 means show, 0 means toggle."
1576   (save-excursion
1577     (save-restriction
1578       (widen)
1579       (let ((hide (gnus-article-hidden-text-p type)))
1580         (cond
1581          ((or (null arg)
1582               (> arg 0))
1583           nil)
1584          ((< arg 0)
1585           (gnus-article-show-hidden-text type))
1586          (t
1587           (if (eq hide 'hidden)
1588               (gnus-article-show-hidden-text type)
1589             nil)))))))
1590
1591 (defun gnus-article-hidden-text-p (type)
1592   "Say whether the current buffer contains hidden text of type TYPE."
1593   (let ((pos (text-property-any (point-min) (point-max) 'article-type type)))
1594     (while (and pos
1595                 (not (get-text-property pos 'invisible)))
1596       (setq pos
1597             (text-property-any (1+ pos) (point-max) 'article-type type)))
1598     (if pos
1599         'hidden
1600       nil)))
1601
1602 (defun gnus-article-show-hidden-text (type &optional hide)
1603   "Show all hidden text of type TYPE.
1604 If HIDE, hide the text instead."
1605   (save-excursion
1606     (let ((buffer-read-only nil)
1607           (inhibit-point-motion-hooks t)
1608           (end (point-min))
1609           beg)
1610       (while (setq beg (text-property-any end (point-max) 'article-type type))
1611         (goto-char beg)
1612         (setq end (or
1613                    (text-property-not-all beg (point-max) 'article-type type)
1614                    (point-max)))
1615         (if hide
1616             (gnus-article-hide-text beg end gnus-hidden-properties)
1617           (gnus-article-unhide-text beg end))
1618         (goto-char end))
1619       t)))
1620
1621 (defconst article-time-units
1622   `((year . ,(* 365.25 24 60 60))
1623     (week . ,(* 7 24 60 60))
1624     (day . ,(* 24 60 60))
1625     (hour . ,(* 60 60))
1626     (minute . 60)
1627     (second . 1))
1628   "Mapping from time units to seconds.")
1629
1630 (defun article-date-ut (&optional type highlight header)
1631   "Convert DATE date to universal time in the current article.
1632 If TYPE is `local', convert to local time; if it is `lapsed', output
1633 how much time has lapsed since DATE."
1634   (interactive (list 'ut t))
1635   (let* ((header (or header
1636                      (mail-header-date (save-excursion
1637                                          (set-buffer gnus-summary-buffer)
1638                                          gnus-current-headers))
1639                      (message-fetch-field "date")
1640                      ""))
1641          (date (if (vectorp header) (mail-header-date header)
1642                  header))
1643          (date-regexp "^Date:[ \t]\\|^X-Sent:[ \t]")
1644          (inhibit-point-motion-hooks t)
1645          bface eface newline)
1646     (when (and date (not (string= date "")))
1647       (save-excursion
1648         (save-restriction
1649           (nnheader-narrow-to-headers)
1650           (let ((buffer-read-only nil))
1651             ;; Delete any old Date headers.
1652             (if (re-search-forward date-regexp nil t)
1653                 (progn
1654                   (setq bface (get-text-property (gnus-point-at-bol) 'face)
1655                         eface (get-text-property (1- (gnus-point-at-eol))
1656                                                  'face))
1657                   (delete-region (progn (beginning-of-line) (point))
1658                                  (progn (end-of-line) (point)))
1659                   (beginning-of-line))
1660               (goto-char (point-max))
1661               (setq newline t))
1662             (insert (article-make-date-line date type))
1663             ;; Do highlighting.
1664             (beginning-of-line)
1665             (when (looking-at "\\([^:]+\\): *\\(.*\\)$")
1666               (put-text-property (match-beginning 1) (1+ (match-end 1))
1667                                  'face bface)
1668               (put-text-property (match-beginning 2) (match-end 2)
1669                                  'face eface))
1670             (when newline
1671               (end-of-line)
1672               (insert "\n"))))))))
1673
1674 (defun article-make-date-line (date type)
1675   "Return a DATE line of TYPE."
1676   (let ((time (condition-case ()
1677                   (date-to-time date)
1678                 (error '(0 0)))))
1679     (cond
1680      ;; Convert to the local timezone.  We have to slap a
1681      ;; `condition-case' round the calls to the timezone
1682      ;; functions since they aren't particularly resistant to
1683      ;; buggy dates.
1684      ((eq type 'local)
1685       (let ((tz (car (current-time-zone))))
1686         (format "Date: %s %s%04d" (current-time-string time)
1687                 (if (> tz 0) "+" "-") (abs (/ tz 36)))))
1688      ;; Convert to Universal Time.
1689      ((eq type 'ut)
1690       (concat "Date: "
1691               (current-time-string
1692                (let* ((e (parse-time-string date))
1693                      (tm (apply 'encode-time e))
1694                      (ms (car tm))
1695                      (ls (- (cadr tm) (car (current-time-zone)))))
1696                  (cond ((< ls 0) (list (1- ms) (+ ls 65536)))
1697                        ((> ls 65535) (list (1+ ms) (- ls 65536)))
1698                        (t (list ms ls)))))
1699               " UT"))
1700      ;; Get the original date from the article.
1701      ((eq type 'original)
1702       (concat "Date: " (if (string-match "\n+$" date)
1703                            (substring date 0 (match-beginning 0))
1704                          date)))
1705      ;; Let the user define the format.
1706      ((eq type 'user)
1707       (if (gnus-functionp gnus-article-time-format)
1708           (funcall gnus-article-time-format time)
1709         (concat
1710          "Date: "
1711          (format-time-string gnus-article-time-format time))))
1712      ;; ISO 8601.
1713      ((eq type 'iso8601)
1714       (concat
1715        "Date: "
1716        (format-time-string "%Y%m%dT%H%M%S" time)))
1717      ;; Do an X-Sent lapsed format.
1718      ((eq type 'lapsed)
1719       ;; If the date is seriously mangled, the timezone functions are
1720       ;; liable to bug out, so we ignore all errors.
1721       (let* ((now (current-time))
1722              (real-time (subtract-time now time))
1723              (real-sec (and real-time
1724                             (+ (* (float (car real-time)) 65536)
1725                                (cadr real-time))))
1726              (sec (and real-time (abs real-sec)))
1727              num prev)
1728         (cond
1729          ((null real-time)
1730           "X-Sent: Unknown")
1731          ((zerop sec)
1732           "X-Sent: Now")
1733          (t
1734           (concat
1735            "X-Sent: "
1736            ;; This is a bit convoluted, but basically we go
1737            ;; through the time units for years, weeks, etc,
1738            ;; and divide things to see whether that results
1739            ;; in positive answers.
1740            (mapconcat
1741             (lambda (unit)
1742               (if (zerop (setq num (ffloor (/ sec (cdr unit)))))
1743                   ;; The (remaining) seconds are too few to
1744                   ;; be divided into this time unit.
1745                   ""
1746                 ;; It's big enough, so we output it.
1747                 (setq sec (- sec (* num (cdr unit))))
1748                 (prog1
1749                     (concat (if prev ", " "") (int-to-string
1750                                                (floor num))
1751                             " " (symbol-name (car unit))
1752                             (if (> num 1) "s" ""))
1753                   (setq prev t))))
1754             article-time-units "")
1755            ;; If dates are odd, then it might appear like the
1756            ;; article was sent in the future.
1757            (if (> real-sec 0)
1758                " ago"
1759              " in the future"))))))
1760      (t
1761       (error "Unknown conversion type: %s" type)))))
1762
1763 (defun article-date-local (&optional highlight)
1764   "Convert the current article date to the local timezone."
1765   (interactive (list t))
1766   (article-date-ut 'local highlight))
1767
1768 (defun article-date-original (&optional highlight)
1769   "Convert the current article date to what it was originally.
1770 This is only useful if you have used some other date conversion
1771 function and want to see what the date was before converting."
1772   (interactive (list t))
1773   (article-date-ut 'original highlight))
1774
1775 (defun article-date-lapsed (&optional highlight)
1776   "Convert the current article date to time lapsed since it was sent."
1777   (interactive (list t))
1778   (article-date-ut 'lapsed highlight))
1779
1780 (defun article-update-date-lapsed ()
1781   "Function to be run from a timer to update the lapsed time line."
1782   (let (deactivate-mark)
1783     (save-excursion
1784       (ignore-errors
1785         (walk-windows
1786          (lambda (w)
1787            (set-buffer (window-buffer w))
1788            (when (eq major-mode 'gnus-article-mode)
1789              (goto-char (point-min))
1790              (when (re-search-forward "^X-Sent:" nil t)
1791                (article-date-lapsed t))))
1792          nil 'visible)))))
1793
1794 (defun gnus-start-date-timer (&optional n)
1795   "Start a timer to update the X-Sent header in the article buffers.
1796 The numerical prefix says how frequently (in seconds) the function
1797 is to run."
1798   (interactive "p")
1799   (unless n
1800     (setq n 1))
1801   (gnus-stop-date-timer)
1802   (setq article-lapsed-timer
1803         (nnheader-run-at-time 1 n 'article-update-date-lapsed)))
1804
1805 (defun gnus-stop-date-timer ()
1806   "Stop the X-Sent timer."
1807   (interactive)
1808   (when article-lapsed-timer
1809     (nnheader-cancel-timer article-lapsed-timer)
1810     (setq article-lapsed-timer nil)))
1811
1812 (defun article-date-user (&optional highlight)
1813   "Convert the current article date to the user-defined format.
1814 This format is defined by the `gnus-article-time-format' variable."
1815   (interactive (list t))
1816   (article-date-ut 'user highlight))
1817
1818 (defun article-date-iso8601 (&optional highlight)
1819   "Convert the current article date to ISO8601."
1820   (interactive (list t))
1821   (article-date-ut 'iso8601 highlight))
1822
1823 (defun article-show-all ()
1824   "Show all hidden text in the article buffer."
1825   (interactive)
1826   (save-excursion
1827     (let ((buffer-read-only nil))
1828       (gnus-article-unhide-text (point-min) (point-max)))))
1829
1830 (defun article-emphasize (&optional arg)
1831   "Emphasize text according to `gnus-emphasis-alist'."
1832   (interactive (gnus-article-hidden-arg))
1833   (unless (gnus-article-check-hidden-text 'emphasis arg)
1834     (save-excursion
1835       (let ((alist gnus-emphasis-alist)
1836             (buffer-read-only nil)
1837             (props (append '(article-type emphasis)
1838                            gnus-hidden-properties))
1839             regexp elem beg invisible visible face)
1840         (article-goto-body)
1841         (setq beg (point))
1842         (while (setq elem (pop alist))
1843           (goto-char beg)
1844           (setq regexp (car elem)
1845                 invisible (nth 1 elem)
1846                 visible (nth 2 elem)
1847                 face (nth 3 elem))
1848           (while (re-search-forward regexp nil t)
1849             (when (and (match-beginning visible) (match-beginning invisible))
1850               (gnus-article-hide-text
1851                (match-beginning invisible) (match-end invisible) props)
1852               (gnus-article-unhide-text-type
1853                (match-beginning visible) (match-end visible) 'emphasis)
1854               (gnus-put-text-property-excluding-newlines
1855                (match-beginning visible) (match-end visible) 'face face)
1856               (goto-char (match-end invisible)))))))))
1857
1858 (defvar gnus-summary-article-menu)
1859 (defvar gnus-summary-post-menu)
1860
1861 ;;; Saving functions.
1862
1863 (defun gnus-article-save (save-buffer file &optional num)
1864   "Save the currently selected article."
1865   (unless gnus-save-all-headers
1866     ;; Remove headers according to `gnus-saved-headers'.
1867     (let ((gnus-visible-headers
1868            (or gnus-saved-headers gnus-visible-headers))
1869           (gnus-article-buffer save-buffer))
1870       (save-excursion
1871         (set-buffer save-buffer)
1872         (article-hide-headers 1 t))))
1873   (save-window-excursion
1874     (if (not gnus-default-article-saver)
1875         (error "No default saver is defined")
1876       ;; !!! Magic!  The saving functions all save
1877       ;; `gnus-save-article-buffer' (or so they think), but we
1878       ;; bind that variable to our save-buffer.
1879       (set-buffer gnus-article-buffer)
1880       (let* ((gnus-save-article-buffer save-buffer)
1881              (filename
1882               (cond
1883                ((not gnus-prompt-before-saving) 'default)
1884                ((eq gnus-prompt-before-saving 'always) nil)
1885                (t file)))
1886              (gnus-number-of-articles-to-be-saved
1887               (when (eq gnus-prompt-before-saving t)
1888                 num)))                  ; Magic
1889         (set-buffer gnus-article-current-summary)
1890         (funcall gnus-default-article-saver filename)))))
1891
1892 (defun gnus-read-save-file-name (prompt &optional filename
1893                                         function group headers variable)
1894   (let ((default-name
1895           (funcall function group headers (symbol-value variable)))
1896         result)
1897     (setq
1898      result
1899      (cond
1900       ((eq filename 'default)
1901        default-name)
1902       ((eq filename t)
1903        default-name)
1904       (filename filename)
1905       (t
1906        (let* ((split-name (gnus-get-split-value gnus-split-methods))
1907               (prompt
1908                (format prompt
1909                        (if (and gnus-number-of-articles-to-be-saved
1910                                 (> gnus-number-of-articles-to-be-saved 1))
1911                            (format "these %d articles"
1912                                    gnus-number-of-articles-to-be-saved)
1913                          "this article")))
1914               (file
1915                ;; Let the split methods have their say.
1916                (cond
1917                 ;; No split name was found.
1918                 ((null split-name)
1919                  (read-file-name
1920                   (concat prompt " (default "
1921                           (file-name-nondirectory default-name) ") ")
1922                   (file-name-directory default-name)
1923                   default-name))
1924                 ;; A single group name is returned.
1925                 ((stringp split-name)
1926                  (setq default-name
1927                        (funcall function split-name headers
1928                                 (symbol-value variable)))
1929                  (read-file-name
1930                   (concat prompt " (default "
1931                           (file-name-nondirectory default-name) ") ")
1932                   (file-name-directory default-name)
1933                   default-name))
1934                 ;; A single split name was found
1935                 ((= 1 (length split-name))
1936                  (let* ((name (expand-file-name
1937                                (car split-name) gnus-article-save-directory))
1938                         (dir (cond ((file-directory-p name)
1939                                     (file-name-as-directory name))
1940                                    ((file-exists-p name) name)
1941                                    (t gnus-article-save-directory))))
1942                    (read-file-name
1943                     (concat prompt " (default " name ") ")
1944                     dir name)))
1945                 ;; A list of splits was found.
1946                 (t
1947                  (setq split-name (nreverse split-name))
1948                  (let (result)
1949                    (let ((file-name-history
1950                           (nconc split-name file-name-history)))
1951                      (setq result
1952                            (expand-file-name
1953                             (read-file-name
1954                              (concat prompt " (`M-p' for defaults) ")
1955                              gnus-article-save-directory
1956                              (car split-name))
1957                             gnus-article-save-directory)))
1958                    (car (push result file-name-history)))))))
1959          ;; Create the directory.
1960          (gnus-make-directory (file-name-directory file))
1961          ;; If we have read a directory, we append the default file name.
1962          (when (file-directory-p file)
1963            (setq file (concat (file-name-as-directory file)
1964                               (file-name-nondirectory default-name))))
1965          ;; Possibly translate some characters.
1966          (nnheader-translate-file-chars file)))))
1967     (gnus-make-directory (file-name-directory result))
1968     (set variable result)))
1969
1970 (defun gnus-article-archive-name (group)
1971   "Return the first instance of an \"Archive-name\" in the current buffer."
1972   (let ((case-fold-search t))
1973     (when (re-search-forward "archive-name: *\\([^ \n\t]+\\)[ \t]*$" nil t)
1974       (nnheader-concat gnus-article-save-directory
1975                        (match-string 1)))))
1976
1977 (defun gnus-article-nndoc-name (group)
1978   "If GROUP is an nndoc group, return the name of the parent group."
1979   (when (eq (car (gnus-find-method-for-group group)) 'nndoc)
1980     (gnus-group-get-parameter group 'save-article-group)))
1981
1982 (defun gnus-summary-save-in-rmail (&optional filename)
1983   "Append this article to Rmail file.
1984 Optional argument FILENAME specifies file name.
1985 Directory to save to is default to `gnus-article-save-directory'."
1986   (setq filename (gnus-read-save-file-name
1987                   "Save %s in rmail file:" filename
1988                   gnus-rmail-save-name gnus-newsgroup-name
1989                   gnus-current-headers 'gnus-newsgroup-last-rmail))
1990   (gnus-eval-in-buffer-window gnus-save-article-buffer
1991     (save-excursion
1992       (save-restriction
1993         (widen)
1994         (gnus-output-to-rmail filename))))
1995   filename)
1996
1997 (defun gnus-summary-save-in-mail (&optional filename)
1998   "Append this article to Unix mail file.
1999 Optional argument FILENAME specifies file name.
2000 Directory to save to is default to `gnus-article-save-directory'."
2001   (setq filename (gnus-read-save-file-name
2002                   "Save %s in Unix mail file:" filename
2003                   gnus-mail-save-name gnus-newsgroup-name
2004                   gnus-current-headers 'gnus-newsgroup-last-mail))
2005   (gnus-eval-in-buffer-window gnus-save-article-buffer
2006     (save-excursion
2007       (save-restriction
2008         (widen)
2009         (if (and (file-readable-p filename)
2010                  (mail-file-babyl-p filename))
2011             (rmail-output-to-rmail-file filename t)
2012           (gnus-output-to-mail filename)))))
2013   filename)
2014
2015 (defun gnus-summary-save-in-file (&optional filename overwrite)
2016   "Append this article to file.
2017 Optional argument FILENAME specifies file name.
2018 Directory to save to is default to `gnus-article-save-directory'."
2019   (setq filename (gnus-read-save-file-name
2020                   "Save %s in file:" filename
2021                   gnus-file-save-name gnus-newsgroup-name
2022                   gnus-current-headers 'gnus-newsgroup-last-file))
2023   (gnus-eval-in-buffer-window gnus-save-article-buffer
2024     (save-excursion
2025       (save-restriction
2026         (widen)
2027         (when (and overwrite
2028                    (file-exists-p filename))
2029           (delete-file filename))
2030         (gnus-output-to-file filename))))
2031   filename)
2032
2033 (defun gnus-summary-write-to-file (&optional filename)
2034   "Write this article to a file.
2035 Optional argument FILENAME specifies file name.
2036 The directory to save in defaults to `gnus-article-save-directory'."
2037   (gnus-summary-save-in-file nil t))
2038
2039 (defun gnus-summary-save-body-in-file (&optional filename)
2040   "Append this article body to a file.
2041 Optional argument FILENAME specifies file name.
2042 The directory to save in defaults to `gnus-article-save-directory'."
2043   (setq filename (gnus-read-save-file-name
2044                   "Save %s body in file:" filename
2045                   gnus-file-save-name gnus-newsgroup-name
2046                   gnus-current-headers 'gnus-newsgroup-last-file))
2047   (gnus-eval-in-buffer-window gnus-save-article-buffer
2048     (save-excursion
2049       (save-restriction
2050         (widen)
2051         (when (article-goto-body)
2052           (narrow-to-region (point) (point-max)))
2053         (gnus-output-to-file filename))))
2054   filename)
2055
2056 (defun gnus-summary-save-in-pipe (&optional command)
2057   "Pipe this article to subprocess."
2058   (setq command
2059         (cond ((and (eq command 'default)
2060                     gnus-last-shell-command)
2061                gnus-last-shell-command)
2062               (command command)
2063               (t (read-string
2064                   (format
2065                    "Shell command on %s: "
2066                    (if (and gnus-number-of-articles-to-be-saved
2067                             (> gnus-number-of-articles-to-be-saved 1))
2068                        (format "these %d articles"
2069                                gnus-number-of-articles-to-be-saved)
2070                      "this article"))
2071                   gnus-last-shell-command))))
2072   (when (string-equal command "")
2073     (setq command gnus-last-shell-command))
2074   (gnus-eval-in-buffer-window gnus-article-buffer
2075     (save-restriction
2076       (widen)
2077       (shell-command-on-region (point-min) (point-max) command nil)))
2078   (setq gnus-last-shell-command command))
2079
2080 ;;; Article file names when saving.
2081
2082 (defun gnus-capitalize-newsgroup (newsgroup)
2083   "Capitalize NEWSGROUP name."
2084   (when (not (zerop (length newsgroup)))
2085     (concat (char-to-string (upcase (aref newsgroup 0)))
2086             (substring newsgroup 1))))
2087
2088 (defun gnus-Numeric-save-name (newsgroup headers &optional last-file)
2089   "Generate file name from NEWSGROUP, HEADERS, and optional LAST-FILE.
2090 If variable `gnus-use-long-file-name' is non-nil, it is ~/News/News.group/num.
2091 Otherwise, it is like ~/News/news/group/num."
2092   (let ((default
2093           (expand-file-name
2094            (concat (if (gnus-use-long-file-name 'not-save)
2095                        (gnus-capitalize-newsgroup newsgroup)
2096                      (gnus-newsgroup-directory-form newsgroup))
2097                    "/" (int-to-string (mail-header-number headers)))
2098            gnus-article-save-directory)))
2099     (if (and last-file
2100              (string-equal (file-name-directory default)
2101                            (file-name-directory last-file))
2102              (string-match "^[0-9]+$" (file-name-nondirectory last-file)))
2103         default
2104       (or last-file default))))
2105
2106 (defun gnus-numeric-save-name (newsgroup headers &optional last-file)
2107   "Generate file name from NEWSGROUP, HEADERS, and optional LAST-FILE.
2108 If variable `gnus-use-long-file-name' is non-nil, it is
2109 ~/News/news.group/num.  Otherwise, it is like ~/News/news/group/num."
2110   (let ((default
2111           (expand-file-name
2112            (concat (if (gnus-use-long-file-name 'not-save)
2113                        newsgroup
2114                      (gnus-newsgroup-directory-form newsgroup))
2115                    "/" (int-to-string (mail-header-number headers)))
2116            gnus-article-save-directory)))
2117     (if (and last-file
2118              (string-equal (file-name-directory default)
2119                            (file-name-directory last-file))
2120              (string-match "^[0-9]+$" (file-name-nondirectory last-file)))
2121         default
2122       (or last-file default))))
2123
2124 (defun gnus-Plain-save-name (newsgroup headers &optional last-file)
2125   "Generate file name from NEWSGROUP, HEADERS, and optional LAST-FILE.
2126 If variable `gnus-use-long-file-name' is non-nil, it is
2127 ~/News/News.group.  Otherwise, it is like ~/News/news/group/news."
2128   (or last-file
2129       (expand-file-name
2130        (if (gnus-use-long-file-name 'not-save)
2131            (gnus-capitalize-newsgroup newsgroup)
2132          (concat (gnus-newsgroup-directory-form newsgroup) "/news"))
2133        gnus-article-save-directory)))
2134
2135 (defun gnus-plain-save-name (newsgroup headers &optional last-file)
2136   "Generate file name from NEWSGROUP, HEADERS, and optional LAST-FILE.
2137 If variable `gnus-use-long-file-name' is non-nil, it is
2138 ~/News/news.group.  Otherwise, it is like ~/News/news/group/news."
2139   (or last-file
2140       (expand-file-name
2141        (if (gnus-use-long-file-name 'not-save)
2142            newsgroup
2143          (concat (gnus-newsgroup-directory-form newsgroup) "/news"))
2144        gnus-article-save-directory)))
2145
2146 (eval-and-compile
2147   (mapcar
2148    (lambda (func)
2149      (let (afunc gfunc)
2150        (if (consp func)
2151            (setq afunc (car func)
2152                  gfunc (cdr func))
2153          (setq afunc func
2154                gfunc (intern (format "gnus-%s" func))))
2155        (fset gfunc
2156              (if (not (fboundp afunc))
2157                  nil
2158                `(lambda (&optional interactive &rest args)
2159                   ,(documentation afunc t)
2160                   (interactive (list t))
2161                   (save-excursion
2162                     (set-buffer gnus-article-buffer)
2163                     (if interactive
2164                         (call-interactively ',afunc)
2165                       (apply ',afunc args))))))))
2166    '(article-hide-headers
2167      article-hide-boring-headers
2168      article-treat-overstrike
2169      article-fill-long-lines
2170      article-remove-cr
2171      article-display-x-face
2172      article-de-quoted-unreadable
2173      article-mime-decode-quoted-printable
2174      article-hide-pgp
2175      article-hide-pem
2176      article-hide-signature
2177      article-remove-trailing-blank-lines
2178      article-strip-leading-blank-lines
2179      article-strip-multiple-blank-lines
2180      article-strip-leading-space
2181      article-strip-trailing-space
2182      article-strip-blank-lines
2183      article-strip-all-blank-lines
2184      article-date-local
2185      article-date-iso8601
2186      article-date-original
2187      article-date-ut
2188      article-decode-mime-words
2189      article-decode-charset
2190      article-decode-encoded-words
2191      article-date-user
2192      article-date-lapsed
2193      article-emphasize
2194      article-treat-dumbquotes
2195      article-normalize-headers
2196      (article-show-all . gnus-article-show-all-headers))))
2197 \f
2198 ;;;
2199 ;;; Gnus article mode
2200 ;;;
2201
2202 (put 'gnus-article-mode 'mode-class 'special)
2203
2204 (set-keymap-parent gnus-article-mode-map widget-keymap)
2205
2206 (gnus-define-keys gnus-article-mode-map
2207   " " gnus-article-goto-next-page
2208   "\177" gnus-article-goto-prev-page
2209   [delete] gnus-article-goto-prev-page
2210   [backspace] gnus-article-goto-prev-page
2211   "\C-c^" gnus-article-refer-article
2212   "h" gnus-article-show-summary
2213   "s" gnus-article-show-summary
2214   "\C-c\C-m" gnus-article-mail
2215   "?" gnus-article-describe-briefly
2216   "e" gnus-article-edit
2217   "<" beginning-of-buffer
2218   ">" end-of-buffer
2219   "\C-c\C-i" gnus-info-find-node
2220   "\C-c\C-b" gnus-bug
2221
2222   "\C-d" gnus-article-read-summary-keys
2223   "\M-*" gnus-article-read-summary-keys
2224   "\M-#" gnus-article-read-summary-keys
2225   "\M-^" gnus-article-read-summary-keys
2226   "\M-g" gnus-article-read-summary-keys)
2227
2228 (substitute-key-definition
2229  'undefined 'gnus-article-read-summary-keys gnus-article-mode-map)
2230
2231 (defun gnus-article-make-menu-bar ()
2232   (gnus-turn-off-edit-menu 'article)
2233   (unless (boundp 'gnus-article-article-menu)
2234     (easy-menu-define
2235      gnus-article-article-menu gnus-article-mode-map ""
2236      '("Article"
2237        ["Scroll forwards" gnus-article-goto-next-page t]
2238        ["Scroll backwards" gnus-article-goto-prev-page t]
2239        ["Show summary" gnus-article-show-summary t]
2240        ["Fetch Message-ID at point" gnus-article-refer-article t]
2241        ["Mail to address at point" gnus-article-mail t]
2242        ["Send a bug report" gnus-bug t]))
2243
2244     (easy-menu-define
2245      gnus-article-treatment-menu gnus-article-mode-map ""
2246      '("Treatment"
2247        ["Hide headers" gnus-article-hide-headers t]
2248        ["Hide signature" gnus-article-hide-signature t]
2249        ["Hide citation" gnus-article-hide-citation t]
2250        ["Treat overstrike" gnus-article-treat-overstrike t]
2251        ["Remove carriage return" gnus-article-remove-cr t]
2252        ["Remove quoted-unreadable" gnus-article-de-quoted-unreadable t]))
2253
2254     ;; Note "Commands" menu is defined in gnus-sum.el for consistency
2255
2256     (when (boundp 'gnus-summary-post-menu)
2257       (define-key gnus-article-mode-map [menu-bar post]
2258         (cons "Post" gnus-summary-post-menu)))
2259
2260     (gnus-run-hooks 'gnus-article-menu-hook)))
2261
2262 (defun gnus-article-mode ()
2263   "Major mode for displaying an article.
2264
2265 All normal editing commands are switched off.
2266
2267 The following commands are available in addition to all summary mode
2268 commands:
2269 \\<gnus-article-mode-map>
2270 \\[gnus-article-next-page]\t Scroll the article one page forwards
2271 \\[gnus-article-prev-page]\t Scroll the article one page backwards
2272 \\[gnus-article-refer-article]\t Go to the article referred to by an article id near point
2273 \\[gnus-article-show-summary]\t Display the summary buffer
2274 \\[gnus-article-mail]\t Send a reply to the address near point
2275 \\[gnus-article-describe-briefly]\t Describe the current mode briefly
2276 \\[gnus-info-find-node]\t Go to the Gnus info node"
2277   (interactive)
2278   (when (gnus-visual-p 'article-menu 'menu)
2279     (gnus-article-make-menu-bar))
2280   (gnus-simplify-mode-line)
2281   (setq mode-name "Article")
2282   (setq major-mode 'gnus-article-mode)
2283   (make-local-variable 'minor-mode-alist)
2284   (use-local-map gnus-article-mode-map)
2285   (gnus-update-format-specifications nil 'article-mode)
2286   (set (make-local-variable 'page-delimiter) gnus-page-delimiter)
2287   (make-local-variable 'gnus-page-broken)
2288   (make-local-variable 'gnus-button-marker-list)
2289   (make-local-variable 'gnus-article-current-summary)
2290   (make-local-variable 'gnus-article-mime-handles)
2291   (make-local-variable 'gnus-article-decoded-p)
2292   (make-local-variable 'gnus-article-mime-handle-alist)
2293   (gnus-set-default-directory)
2294   (buffer-disable-undo)
2295   (setq buffer-read-only t)
2296   (set-syntax-table gnus-article-mode-syntax-table)
2297   (mm-enable-multibyte)
2298   (gnus-run-hooks 'gnus-article-mode-hook))
2299
2300 (defun gnus-article-setup-buffer ()
2301   "Initialize the article buffer."
2302   (let* ((name (if gnus-single-article-buffer "*Article*"
2303                  (concat "*Article " gnus-newsgroup-name "*")))
2304          (original
2305           (progn (string-match "\\*Article" name)
2306                  (concat " *Original Article"
2307                          (substring name (match-end 0))))))
2308     (setq gnus-article-buffer name)
2309     (setq gnus-original-article-buffer original)
2310     (setq gnus-article-mime-handle-alist nil)
2311     ;; This might be a variable local to the summary buffer.
2312     (unless gnus-single-article-buffer
2313       (save-excursion
2314         (set-buffer gnus-summary-buffer)
2315         (setq gnus-article-buffer name)
2316         (setq gnus-original-article-buffer original)
2317         (gnus-set-global-variables)))
2318     ;; Init original article buffer.
2319     (save-excursion
2320       (set-buffer (gnus-get-buffer-create gnus-original-article-buffer))
2321       (mm-enable-multibyte)
2322       (setq major-mode 'gnus-original-article-mode)
2323       (make-local-variable 'gnus-original-article))
2324     (if (get-buffer name)
2325         (save-excursion
2326           (set-buffer name)
2327           (buffer-disable-undo)
2328           (setq buffer-read-only t)
2329           (unless (eq major-mode 'gnus-article-mode)
2330             (gnus-article-mode))
2331           (current-buffer))
2332       (save-excursion
2333         (set-buffer (gnus-get-buffer-create name))
2334         (gnus-article-mode)
2335         (make-local-variable 'gnus-summary-buffer)
2336         (gnus-summary-set-local-parameters gnus-newsgroup-name)
2337         (current-buffer)))))
2338
2339 ;; Set article window start at LINE, where LINE is the number of lines
2340 ;; from the head of the article.
2341 (defun gnus-article-set-window-start (&optional line)
2342   (set-window-start
2343    (get-buffer-window gnus-article-buffer t)
2344    (save-excursion
2345      (set-buffer gnus-article-buffer)
2346      (goto-char (point-min))
2347      (if (not line)
2348          (point-min)
2349        (gnus-message 6 "Moved to bookmark")
2350        (search-forward "\n\n" nil t)
2351        (forward-line line)
2352        (point)))))
2353
2354 (defun gnus-article-prepare (article &optional all-headers header)
2355   "Prepare ARTICLE in article mode buffer.
2356 ARTICLE should either be an article number or a Message-ID.
2357 If ARTICLE is an id, HEADER should be the article headers.
2358 If ALL-HEADERS is non-nil, no headers are hidden."
2359   (save-excursion
2360     ;; Make sure we start in a summary buffer.
2361     (unless (eq major-mode 'gnus-summary-mode)
2362       (set-buffer gnus-summary-buffer))
2363     (setq gnus-summary-buffer (current-buffer))
2364     (let* ((gnus-article (if header (mail-header-number header) article))
2365            (summary-buffer (current-buffer))
2366            (gnus-tmp-internal-hook gnus-article-internal-prepare-hook)
2367            (group gnus-newsgroup-name)
2368            result)
2369       (save-excursion
2370         (gnus-article-setup-buffer)
2371         (set-buffer gnus-article-buffer)
2372         ;; Deactivate active regions.
2373         (when (and (boundp 'transient-mark-mode)
2374                    transient-mark-mode)
2375           (setq mark-active nil))
2376         (if (not (setq result (let ((buffer-read-only nil))
2377                                 (gnus-request-article-this-buffer
2378                                  article group))))
2379             ;; There is no such article.
2380             (save-excursion
2381               (when (and (numberp article)
2382                          (not (memq article gnus-newsgroup-sparse)))
2383                 (setq gnus-article-current
2384                       (cons gnus-newsgroup-name article))
2385                 (set-buffer gnus-summary-buffer)
2386                 (setq gnus-current-article article)
2387                 (if (eq (gnus-article-mark article) gnus-undownloaded-mark)
2388                     (progn
2389                       (gnus-summary-set-agent-mark article)
2390                       (message "Message marked for downloading"))
2391                   (gnus-summary-mark-article article gnus-canceled-mark)
2392                   (unless (memq article gnus-newsgroup-sparse)
2393                     (gnus-error 1
2394                      "No such article (may have expired or been canceled)")))))
2395           (if (or (eq result 'pseudo)
2396                   (eq result 'nneething))
2397               (progn
2398                 (save-excursion
2399                   (set-buffer summary-buffer)
2400                   (push article gnus-newsgroup-history)
2401                   (setq gnus-last-article gnus-current-article
2402                         gnus-current-article 0
2403                         gnus-current-headers nil
2404                         gnus-article-current nil)
2405                   (if (eq result 'nneething)
2406                       (gnus-configure-windows 'summary)
2407                     (gnus-configure-windows 'article))
2408                   (gnus-set-global-variables))
2409                 (let ((gnus-article-mime-handle-alist-1
2410                        gnus-article-mime-handle-alist))
2411                   (gnus-set-mode-line 'article)))
2412             ;; The result from the `request' was an actual article -
2413             ;; or at least some text that is now displayed in the
2414             ;; article buffer.
2415             (when (and (numberp article)
2416                        (not (eq article gnus-current-article)))
2417               ;; Seems like a new article has been selected.
2418               ;; `gnus-current-article' must be an article number.
2419               (save-excursion
2420                 (set-buffer summary-buffer)
2421                 (push article gnus-newsgroup-history)
2422                 (setq gnus-last-article gnus-current-article
2423                       gnus-current-article article
2424                       gnus-current-headers
2425                       (gnus-summary-article-header gnus-current-article)
2426                       gnus-article-current
2427                       (cons gnus-newsgroup-name gnus-current-article))
2428                 (unless (vectorp gnus-current-headers)
2429                   (setq gnus-current-headers nil))
2430                 (gnus-summary-goto-subject gnus-current-article)
2431                 (when (gnus-summary-show-thread)
2432                   ;; If the summary buffer really was folded, the
2433                   ;; previous goto may not actually have gone to
2434                   ;; the right article, but the thread root instead.
2435                   ;; So we go again.
2436                   (gnus-summary-goto-subject gnus-current-article))
2437                 (gnus-run-hooks 'gnus-mark-article-hook)
2438                 (gnus-set-mode-line 'summary)
2439                 (when (gnus-visual-p 'article-highlight 'highlight)
2440                   (gnus-run-hooks 'gnus-visual-mark-article-hook))
2441                 ;; Set the global newsgroup variables here.
2442                 ;; Suggested by Jim Sisolak
2443                 ;; <sisolak@trans4.neep.wisc.edu>.
2444                 (gnus-set-global-variables)
2445                 (setq gnus-have-all-headers
2446                       (or all-headers gnus-show-all-headers))))
2447             (when (or (numberp article)
2448                       (stringp article))
2449               (gnus-article-prepare-display)
2450               ;; Do page break.
2451               (goto-char (point-min))
2452               (setq gnus-page-broken
2453                     (when gnus-break-pages
2454                       (gnus-narrow-to-page)
2455                       t)))
2456             (let ((gnus-article-mime-handle-alist-1
2457                    gnus-article-mime-handle-alist))
2458               (gnus-set-mode-line 'article))
2459             (gnus-configure-windows 'article)
2460             (article-goto-body)
2461             (set-window-point (get-buffer-window (current-buffer)) (point))
2462             t))))))
2463
2464 ;;;###autoload
2465 (defun gnus-article-prepare-display ()
2466   "Make the current buffer look like a nice article."
2467   ;; Hooks for getting information from the article.
2468   ;; This hook must be called before being narrowed.
2469   (let ((gnus-article-buffer (current-buffer))
2470         buffer-read-only)
2471     (unless (eq major-mode 'gnus-article-mode)
2472       (gnus-article-mode))
2473     (setq buffer-read-only nil)
2474     (gnus-run-hooks 'gnus-tmp-internal-hook)
2475     (gnus-run-hooks 'gnus-article-prepare-hook)
2476     (when gnus-display-mime-function
2477       (funcall gnus-display-mime-function))))
2478
2479 ;;;
2480 ;;; Gnus MIME viewing functions
2481 ;;;
2482
2483 (defvar gnus-mime-button-line-format "%{%([%p. %d%T]%)%}%e\n"
2484   "The following specs can be used:
2485 %t  The MIME type
2486 %T  MIME type, along with additional info
2487 %n  The `name' parameter
2488 %d  The description, if any
2489 %l  The length of the encoded part
2490 %p  The part identifier number
2491 %e  Dots if the part isn't displayed")
2492
2493 (defvar gnus-mime-button-line-format-alist
2494   '((?t gnus-tmp-type ?s)
2495     (?T gnus-tmp-type-long ?s)
2496     (?n gnus-tmp-name ?s)
2497     (?d gnus-tmp-description ?s)
2498     (?p gnus-tmp-id ?s)
2499     (?l gnus-tmp-length ?d)
2500     (?e gnus-tmp-dots ?s)))
2501
2502 (defvar gnus-mime-button-commands
2503   '((gnus-article-press-button  "\r"    "Toggle Display")
2504     (gnus-mime-view-part        "v"     "View Interactively...")
2505     (gnus-mime-save-part        "o"     "Save...")
2506     (gnus-mime-copy-part        "c"     "View As Text, In Other Buffer")
2507     (gnus-mime-inline-part      "i"     "View As Text, In This Buffer")
2508     (gnus-mime-internalize-part "E"     "View Internally")
2509     (gnus-mime-externalize-part "e"     "View Externally")
2510     (gnus-mime-pipe-part        "|"     "Pipe To Command...")))
2511
2512 (defun gnus-article-mime-part-status ()
2513   (if gnus-article-mime-handle-alist-1
2514       (format " (%d parts)" (length gnus-article-mime-handle-alist-1))
2515     ""))
2516
2517 (defvar gnus-mime-button-map nil)
2518 (unless gnus-mime-button-map
2519   (setq gnus-mime-button-map (make-sparse-keymap))
2520   (set-keymap-parent gnus-mime-button-map gnus-article-mode-map)
2521   (define-key gnus-mime-button-map gnus-mouse-2 'gnus-article-push-button)
2522   (define-key gnus-mime-button-map gnus-down-mouse-3 'gnus-mime-button-menu)
2523   (mapcar (lambda (c)
2524             (define-key gnus-mime-button-map (cadr c) (car c)))
2525           gnus-mime-button-commands))
2526
2527 (defun gnus-mime-button-menu (event)
2528   "Construct a context-sensitive menu of MIME commands."
2529   (interactive "e")
2530   (save-excursion
2531     (let ((pos (event-start event)))
2532       (set-buffer (window-buffer (posn-window pos)))
2533       (goto-char (posn-point pos))
2534       (gnus-article-check-buffer)
2535       (let ((response (x-popup-menu
2536                        t `("MIME Part"
2537                            ("" ,@(mapcar (lambda (c)
2538                                            (cons (caddr c) (car c)))
2539                                          gnus-mime-button-commands))))))
2540         (if response
2541             (funcall response))))))
2542
2543 (defun gnus-mime-view-all-parts (&optional handles)
2544   "View all the MIME parts."
2545   (interactive)
2546   (save-current-buffer
2547     (set-buffer gnus-article-buffer)
2548     (let ((handles (or handles gnus-article-mime-handles))
2549           (mail-parse-charset gnus-newsgroup-charset))
2550       (if (stringp (car handles))
2551           (gnus-mime-view-all-parts (cdr handles))
2552         (mapcar 'mm-display-part handles)))))
2553
2554 (defun gnus-mime-save-part ()
2555   "Save the MIME part under point."
2556   (interactive)
2557   (gnus-article-check-buffer)
2558   (let ((data (get-text-property (point) 'gnus-data)))
2559     (mm-save-part data)))
2560
2561 (defun gnus-mime-pipe-part ()
2562   "Pipe the MIME part under point to a process."
2563   (interactive)
2564   (gnus-article-check-buffer)
2565   (let ((data (get-text-property (point) 'gnus-data)))
2566     (mm-pipe-part data)))
2567
2568 (defun gnus-mime-view-part ()
2569   "Interactively choose a view method for the MIME part under point."
2570   (interactive)
2571   (gnus-article-check-buffer)
2572   (let ((data (get-text-property (point) 'gnus-data)))
2573     (mm-interactively-view-part data)))
2574
2575 (defun gnus-mime-copy-part (&optional handle)
2576   "Put the the MIME part under point into a new buffer."
2577   (interactive)
2578   (gnus-article-check-buffer)
2579   (let* ((handle (or handle (get-text-property (point) 'gnus-data)))
2580          (contents (mm-get-part handle))|
2581          (base (file-name-nondirectory
2582                 (or
2583                  (mail-content-type-get (mm-handle-type handle) 'name)
2584                  (mail-content-type-get (mm-handle-type handle)
2585                                         'filename)
2586                  "*decoded*")))
2587          (buffer (generate-new-buffer base)))
2588     (switch-to-buffer buffer)
2589     (insert contents)
2590     ;; We do it this way to make `normal-mode' set the appropriate mode.
2591     (unwind-protect
2592         (progn
2593           (setq buffer-file-name (expand-file-name base))
2594           (normal-mode))
2595       (setq buffer-file-name nil))
2596     (goto-char (point-min))))
2597
2598 (defun gnus-mime-inline-part (&optional charset)
2599   "Insert the MIME part under point into the current buffer."
2600   (interactive "P") ; For compatibility reasons we are not using "z".
2601   (gnus-article-check-buffer)
2602   (let* ((data (get-text-property (point) 'gnus-data))
2603          contents
2604          (b (point))
2605          buffer-read-only)
2606     (if (mm-handle-undisplayer data)
2607         (mm-remove-part data)
2608       (setq contents (mm-get-part data))
2609       (forward-line 2)
2610       (when charset
2611         (unless (symbolp charset)
2612           (setq charset (mm-read-coding-system "Charset: ")))
2613         (setq contents (mm-decode-coding-string contents charset)))
2614       (mm-insert-inline data contents)
2615       (goto-char b))))
2616
2617 (defun gnus-mime-externalize-part (&optional handle)
2618   "View the MIME part under point with an external viewer."
2619   (interactive)
2620   (gnus-article-check-buffer)
2621   (let* ((handle (or handle (get-text-property (point) 'gnus-data)))
2622          (mm-user-display-methods nil)
2623          (mm-all-images-fit t)
2624          (mail-parse-charset gnus-newsgroup-charset))
2625     (if (mm-handle-undisplayer handle)
2626         (mm-remove-part handle)
2627       (mm-display-part handle))))
2628
2629 (defun gnus-mime-internalize-part (&optional handle)
2630   "View the MIME part under point with an internal viewer."
2631   (interactive)
2632   (gnus-article-check-buffer)
2633   (let* ((handle (or handle (get-text-property (point) 'gnus-data)))
2634          (mm-user-display-methods '((".*" . inline)))
2635          (mm-all-images-fit t)
2636          (mail-parse-charset gnus-newsgroup-charset))
2637     (if (mm-handle-undisplayer handle)
2638         (mm-remove-part handle)
2639       (mm-display-part handle))))
2640
2641 (defun gnus-article-part-wrapper (n function)
2642   (save-current-buffer
2643     (set-buffer gnus-article-buffer)
2644     (when (> n (length gnus-article-mime-handle-alist))
2645       (error "No such part"))
2646     (let ((handle (cdr (assq n gnus-article-mime-handle-alist))))
2647       (funcall function handle))))
2648
2649 (defun gnus-article-pipe-part (n)
2650   "Pipe MIME part N, which is the numerical prefix."
2651   (interactive "p")
2652   (gnus-article-part-wrapper n 'mm-pipe-part))
2653
2654 (defun gnus-article-save-part (n)
2655   "Save MIME part N, which is the numerical prefix."
2656   (interactive "p")
2657   (gnus-article-part-wrapper n 'mm-save-part))
2658
2659 (defun gnus-article-interactively-view-part (n)
2660   "Pipe MIME part N, which is the numerical prefix."
2661   (interactive "p")
2662   (gnus-article-part-wrapper n 'mm-interactively-view-part))
2663
2664 (defun gnus-article-copy-part (n)
2665   "Pipe MIME part N, which is the numerical prefix."
2666   (interactive "p")
2667   (gnus-article-part-wrapper n 'gnus-mime-copy-part))
2668
2669 (defun gnus-article-externalize-part (n)
2670   "Pipe MIME part N, which is the numerical prefix."
2671   (interactive "p")
2672   (gnus-article-part-wrapper n 'gnus-mime-externalize-part))
2673
2674 (defun gnus-article-view-part (n)
2675   "View MIME part N, which is the numerical prefix."
2676   (interactive "p")
2677   (save-current-buffer
2678     (set-buffer gnus-article-buffer)
2679     (when (> n (length gnus-article-mime-handle-alist))
2680       (error "No such part"))
2681     (let ((handle (cdr (assq n gnus-article-mime-handle-alist))))
2682       (when (gnus-article-goto-part n)
2683         (if (equal (car handle) "multipart/alternative")
2684             (gnus-article-press-button)
2685           (when (eq (gnus-mm-display-part handle) 'internal)
2686             (gnus-set-window-start)))))))
2687
2688 (defun gnus-mm-display-part (handle)
2689   "Display HANDLE and fix MIME button."
2690   (let ((id (get-text-property (point) 'gnus-part))
2691         (point (point))
2692         buffer-read-only)
2693     (forward-line 1)
2694     (prog1
2695         (let ((window (selected-window))
2696               (mail-parse-charset gnus-newsgroup-charset))
2697           (save-excursion
2698             (unwind-protect
2699                 (let ((win (get-buffer-window (current-buffer) t))
2700                       (beg (point)))
2701                   (when win
2702                     (select-window win))
2703                   (goto-char point)
2704                   (forward-line)
2705                   (if (mm-handle-displayed-p handle)
2706                       ;; This will remove the part.
2707                       (mm-display-part handle)
2708                     (save-restriction
2709                       (narrow-to-region (point) (1+ (point)))
2710                       (mm-display-part handle)
2711                       (gnus-treat-article
2712                        nil id
2713                        (1- (length gnus-article-mime-handles))
2714                        (car (mm-handle-type handle))))))
2715               (select-window window))))
2716       (goto-char point)
2717       (delete-region (gnus-point-at-bol) (progn (forward-line 1) (point)))
2718       (gnus-insert-mime-button
2719        handle id (list (mm-handle-displayed-p handle)))
2720       (goto-char point))))
2721
2722 (defun gnus-article-goto-part (n)
2723   "Go to MIME part N."
2724   (let ((point (text-property-any (point-min) (point-max) 'gnus-part n)))
2725     (when point
2726       (goto-char point))))
2727
2728 (defun gnus-insert-mime-button (handle gnus-tmp-id &optional displayed)
2729   (let ((gnus-tmp-name
2730          (or (mail-content-type-get (mm-handle-type handle)
2731                                     'name)
2732              (mail-content-type-get (mm-handle-disposition handle)
2733                                     'filename)
2734              ""))
2735         (gnus-tmp-type (car (mm-handle-type handle)))
2736         (gnus-tmp-description
2737          (mail-decode-encoded-word-string (or (mm-handle-description handle)
2738                                               "")))
2739         (gnus-tmp-dots
2740          (if (if displayed (car displayed)
2741                (mm-handle-displayed-p handle))
2742              "" "..."))
2743         (gnus-tmp-length (with-current-buffer (mm-handle-buffer handle)
2744                            (buffer-size)))
2745         gnus-tmp-type-long b e)
2746     (when (string-match ".*/" gnus-tmp-name)
2747       (setq gnus-tmp-name (replace-match "" t t gnus-tmp-name)))
2748     (setq gnus-tmp-type-long (concat gnus-tmp-type
2749                                      (and (not (equal gnus-tmp-name ""))
2750                                           (concat "; " gnus-tmp-name))))
2751     (or (equal gnus-tmp-description "")
2752         (setq gnus-tmp-type-long (concat " --- " gnus-tmp-type-long)))
2753     (unless (bolp)
2754       (insert "\n"))
2755     (setq b (point))
2756     (gnus-eval-format
2757      gnus-mime-button-line-format gnus-mime-button-line-format-alist
2758      `(local-map ,gnus-mime-button-map
2759                  keymap ,gnus-mime-button-map
2760                  gnus-callback gnus-mm-display-part
2761                  gnus-part ,gnus-tmp-id
2762                  article-type annotation
2763                  gnus-data ,handle))
2764     (setq e (point))
2765     (widget-convert-button 'link b e
2766                            :mime-handle handle
2767                            :action 'gnus-widget-press-button
2768                            :button-keymap gnus-mime-button-map
2769                            :help-echo
2770                            (lambda (widget)
2771                              ;; Needed to properly clear the message
2772                              ;; due to a bug in wid-edit
2773                              (setq help-echo-owns-message t)
2774                              (format
2775                               "Click to %s the MIME part; %s for more options"
2776                               (if (mm-handle-displayed-p
2777                                    (widget-get widget :mime-handle))
2778                                   "hide" "show")
2779                               (if gnus-xemacs "button3" "mouse-3"))))))
2780
2781 (defun gnus-widget-press-button (elems el)
2782   (goto-char (widget-get elems :from))
2783   (gnus-article-press-button))
2784
2785 (defun gnus-display-mime (&optional ihandles)
2786   "Display the MIME parts."
2787   (save-excursion
2788     (save-selected-window
2789       (let ((window (get-buffer-window gnus-article-buffer))
2790             (point (point)))
2791         (when window
2792           (select-window window)
2793           ;; We have to do this since selecting the window
2794           ;; may change the point.  So we set the window point.
2795           (set-window-point window point)))
2796       (let* ((handles (or ihandles (mm-dissect-buffer) (mm-uu-dissect)))
2797              handle name type b e display)
2798         (unless ihandles
2799           ;; Top-level call; we clean up.
2800           (mm-destroy-parts gnus-article-mime-handles)
2801           (setq gnus-article-mime-handles handles
2802                 gnus-article-mime-handle-alist nil)
2803           ;; We allow users to glean info from the handles.
2804           (when gnus-article-mime-part-function
2805             (gnus-mime-part-function handles)))
2806         (if (and handles
2807                  (or (not (stringp (car handles)))
2808                      (cdr handles)))
2809             (progn
2810               (unless ihandles
2811                 ;; Clean up for mime parts.
2812                 (article-goto-body)
2813                 (delete-region (point) (point-max)))
2814               (gnus-mime-display-part handles))
2815           (save-restriction
2816             (article-goto-body)
2817             (narrow-to-region (point) (point-max))
2818             (gnus-treat-article nil 1 1)))
2819         ;; Highlight the headers.
2820         (save-excursion
2821           (save-restriction
2822             (article-goto-body)
2823             (narrow-to-region (point-min) (point))
2824             (gnus-treat-article 'head)))))))
2825
2826 (defvar gnus-mime-display-multipart-as-mixed nil)
2827
2828 (defun gnus-mime-display-part (handle)
2829   (cond
2830    ;; Single part.
2831    ((not (stringp (car handle)))
2832     (gnus-mime-display-single handle))
2833    ;; multipart/alternative
2834    ((and (equal (car handle) "multipart/alternative")
2835          (not gnus-mime-display-multipart-as-mixed))
2836     (let ((id (1+ (length gnus-article-mime-handle-alist))))
2837       (push (cons id handle) gnus-article-mime-handle-alist)
2838       (gnus-mime-display-alternative (cdr handle) nil nil id)))
2839    ;; multipart/related
2840    ((and (equal (car handle) "multipart/related")
2841          (not gnus-mime-display-multipart-as-mixed))
2842     ;;;!!!We should find the start part, but we just default
2843     ;;;!!!to the first part.
2844     (gnus-mime-display-part (cadr handle)))
2845    ;; Other multiparts are handled like multipart/mixed.
2846    (t
2847     (gnus-mime-display-mixed (cdr handle)))))
2848
2849 (defun gnus-mime-part-function (handles)
2850   (if (stringp (car handles))
2851       (mapcar 'gnus-mime-part-function (cdr handles))
2852     (funcall gnus-article-mime-part-function handles)))
2853
2854 (defun gnus-mime-display-mixed (handles)
2855   (mapcar 'gnus-mime-display-part handles))
2856
2857 (defun gnus-mime-display-single (handle)
2858   (let ((type (car (mm-handle-type handle)))
2859         (ignored gnus-ignored-mime-types)
2860         (not-attachment t)
2861         (move nil)
2862         display text)
2863     (catch 'ignored
2864       (progn
2865         (while ignored
2866           (when (string-match (pop ignored) type)
2867             (throw 'ignored nil)))
2868         (if (and (setq not-attachment
2869                        (or (not (mm-handle-disposition handle))
2870                            (equal (car (mm-handle-disposition handle))
2871                                   "inline")
2872                            (mm-attachment-override-p type)))
2873                  (mm-automatic-display-p type)
2874                  (or (mm-inlinable-part-p type)
2875                      (mm-automatic-external-display-p type)))
2876             (setq display t)
2877           (when (equal (car (split-string type "/"))
2878                        "text")
2879             (setq text t)))
2880         (let ((id (1+ (length gnus-article-mime-handle-alist))))
2881           (push (cons id handle) gnus-article-mime-handle-alist)
2882           (when (or (not display)
2883                     (not (gnus-unbuttonized-mime-type-p type)))
2884             (gnus-article-insert-newline)
2885             (gnus-insert-mime-button
2886              handle id (list (or display (and not-attachment text))))
2887             (gnus-article-insert-newline)
2888             (gnus-article-insert-newline)
2889             (setq move t)))
2890         (let ((beg (point)))
2891           (cond
2892            (display
2893             (when move
2894               (forward-line -2))
2895             (let ((mail-parse-charset gnus-newsgroup-charset))
2896               (mm-display-part handle t))
2897             (goto-char (point-max)))
2898            ((and text not-attachment)
2899             (when move
2900               (forward-line -2))
2901             (gnus-article-insert-newline)
2902             (mm-insert-inline handle (mm-get-part handle))
2903             (goto-char (point-max))))
2904           ;; Do highlighting.
2905           (save-excursion
2906             (save-restriction
2907               (narrow-to-region beg (point))
2908               (gnus-treat-article
2909                nil (length gnus-article-mime-handle-alist)
2910                (1- (length gnus-article-mime-handles))
2911                (car (mm-handle-type handle))))))))))
2912
2913 (defun gnus-unbuttonized-mime-type-p (type)
2914   "Say whether TYPE is to be unbuttonized."
2915   (unless gnus-inhibit-mime-unbuttonizing
2916     (catch 'found
2917       (let ((types gnus-unbuttonized-mime-types))
2918         (while types
2919           (when (string-match (pop types) type)
2920             (throw 'found t)))))))
2921
2922 (defun gnus-article-insert-newline ()
2923   "Insert a newline, but mark it as undeletable."
2924   (gnus-put-text-property
2925    (point) (progn (insert "\n") (point)) 'gnus-undeletable t))
2926
2927 (defun gnus-mime-display-alternative (handles &optional preferred ibegend id)
2928   (let* ((preferred (or preferred (mm-preferred-alternative handles)))
2929          (ihandles handles)
2930          (point (point))
2931          handle buffer-read-only from props begend not-pref)
2932     (save-window-excursion
2933       (save-restriction
2934         (when ibegend
2935           (narrow-to-region (car ibegend)
2936                             (or (cdr ibegend)
2937                                 (progn
2938                                   (goto-char (car ibegend))
2939                                   (forward-line 2)
2940                                   (point))))
2941           (delete-region (point-min) (point-max))
2942           (mm-remove-parts handles))
2943         (setq begend (list (point-marker)))
2944         ;; Do the toggle.
2945         (unless (setq not-pref (cadr (member preferred ihandles)))
2946           (setq not-pref (car ihandles)))
2947         (when (or ibegend
2948                   (not (gnus-unbuttonized-mime-type-p
2949                         "multipart/alternative")))
2950           (gnus-add-text-properties
2951            (setq from (point))
2952            (progn
2953              (insert (format "%d.  " id))
2954              (point))
2955            `(gnus-callback
2956              (lambda (handles)
2957                (unless ,(not ibegend)
2958                  (setq gnus-article-mime-handle-alist
2959                        ',gnus-article-mime-handle-alist))
2960                (gnus-mime-display-alternative
2961                 ',ihandles ',not-pref ',begend ,id))
2962              local-map ,gnus-mime-button-map
2963              ,gnus-mouse-face-prop ,gnus-article-mouse-face
2964              face ,gnus-article-button-face
2965              keymap ,gnus-mime-button-map
2966              gnus-part ,id
2967              gnus-data ,handle))
2968           (widget-convert-button 'link from (point)
2969                                  :action 'gnus-widget-press-button
2970                                  :button-keymap gnus-widget-button-keymap)
2971           ;; Do the handles
2972           (while (setq handle (pop handles))
2973             (gnus-add-text-properties
2974              (setq from (point))
2975              (progn
2976                (insert (format "(%c) %-18s"
2977                                (if (equal handle preferred) ?* ? )
2978                                (if (stringp (car handle))
2979                                    (car handle)
2980                                  (car (mm-handle-type handle)))))
2981                (point))
2982              `(gnus-callback
2983                (lambda (handles)
2984                  (unless ,(not ibegend)
2985                    (setq gnus-article-mime-handle-alist
2986                          ',gnus-article-mime-handle-alist))
2987                  (gnus-mime-display-alternative
2988                   ',ihandles ',handle ',begend ,id))
2989                local-map ,gnus-mime-button-map
2990                ,gnus-mouse-face-prop ,gnus-article-mouse-face
2991                face ,gnus-article-button-face
2992                keymap ,gnus-mime-button-map
2993                gnus-part ,id
2994                gnus-data ,handle))
2995             (widget-convert-button 'link from (point)
2996                                    :action 'gnus-widget-press-button
2997                                    :button-keymap gnus-widget-button-keymap)
2998             (insert "  "))
2999           (insert "\n\n"))
3000         (when preferred
3001           (if (stringp (car preferred))
3002               (gnus-display-mime preferred)
3003             (let ((mail-parse-charset gnus-newsgroup-charset))
3004               (mm-display-part preferred)))
3005           (goto-char (point-max))
3006           (setcdr begend (point-marker)))))
3007     (when ibegend
3008       (goto-char point))))
3009
3010 (defun gnus-article-wash-status ()
3011   "Return a string which display status of article washing."
3012   (save-excursion
3013     (set-buffer gnus-article-buffer)
3014     (let ((cite (gnus-article-hidden-text-p 'cite))
3015           (headers (gnus-article-hidden-text-p 'headers))
3016           (boring (gnus-article-hidden-text-p 'boring-headers))
3017           (pgp (gnus-article-hidden-text-p 'pgp))
3018           (pem (gnus-article-hidden-text-p 'pem))
3019           (signature (gnus-article-hidden-text-p 'signature))
3020           (overstrike (gnus-article-hidden-text-p 'overstrike))
3021           (emphasis (gnus-article-hidden-text-p 'emphasis)))
3022       (format "%c%c%c%c%c%c"
3023               (if cite ?c ? )
3024               (if (or headers boring) ?h ? )
3025               (if (or pgp pem) ?p ? )
3026               (if signature ?s ? )
3027               (if overstrike ?o ? )
3028               (if emphasis ?e ? )))))
3029
3030 (fset 'gnus-article-hide-headers-if-wanted 'gnus-article-maybe-hide-headers)
3031
3032 (defun gnus-article-maybe-hide-headers ()
3033   "Hide unwanted headers if `gnus-have-all-headers' is nil.
3034 Provided for backwards compatibility."
3035   (when (and (or (not (gnus-buffer-live-p gnus-summary-buffer))
3036                  (not (save-excursion (set-buffer gnus-summary-buffer)
3037                                       gnus-have-all-headers)))
3038              (not gnus-inhibit-hiding))
3039     (gnus-article-hide-headers)))
3040
3041 ;;; Article savers.
3042
3043 (defun gnus-output-to-file (file-name)
3044   "Append the current article to a file named FILE-NAME."
3045   (let ((artbuf (current-buffer)))
3046     (with-temp-buffer
3047       (insert-buffer-substring artbuf)
3048       ;; Append newline at end of the buffer as separator, and then
3049       ;; save it to file.
3050       (goto-char (point-max))
3051       (insert "\n")
3052       (append-to-file (point-min) (point-max) file-name)
3053       t)))
3054
3055 (defun gnus-narrow-to-page (&optional arg)
3056   "Narrow the article buffer to a page.
3057 If given a numerical ARG, move forward ARG pages."
3058   (interactive "P")
3059   (setq arg (if arg (prefix-numeric-value arg) 0))
3060   (save-excursion
3061     (set-buffer gnus-article-buffer)
3062     (goto-char (point-min))
3063     (widen)
3064     ;; Remove any old next/prev buttons.
3065     (when (gnus-visual-p 'page-marker)
3066       (let ((buffer-read-only nil))
3067         (gnus-remove-text-with-property 'gnus-prev)
3068         (gnus-remove-text-with-property 'gnus-next)))
3069     (when
3070         (cond ((< arg 0)
3071                (re-search-backward page-delimiter nil 'move (1+ (abs arg))))
3072               ((> arg 0)
3073                (re-search-forward page-delimiter nil 'move arg)))
3074       (goto-char (match-end 0)))
3075     (narrow-to-region
3076      (point)
3077      (if (re-search-forward page-delimiter nil 'move)
3078          (match-beginning 0)
3079        (point)))
3080     (when (and (gnus-visual-p 'page-marker)
3081                (not (= (point-min) 1)))
3082       (save-excursion
3083         (goto-char (point-min))
3084         (gnus-insert-prev-page-button)))
3085     (when (and (gnus-visual-p 'page-marker)
3086                (< (+ (point-max) 2) (buffer-size)))
3087       (save-excursion
3088         (goto-char (point-max))
3089         (gnus-insert-next-page-button)))))
3090
3091 ;; Article mode commands
3092
3093 (defun gnus-article-goto-next-page ()
3094   "Show the next page of the article."
3095   (interactive)
3096   (when (gnus-article-next-page)
3097     (goto-char (point-min))
3098     (gnus-article-read-summary-keys nil (gnus-character-to-event ?n))))
3099
3100 (defun gnus-article-goto-prev-page ()
3101   "Show the next page of the article."
3102   (interactive)
3103   (if (bobp) (gnus-article-read-summary-keys nil (gnus-character-to-event ?p))
3104     (gnus-article-prev-page nil)))
3105
3106 (defun gnus-article-next-page (&optional lines)
3107   "Show the next page of the current article.
3108 If end of article, return non-nil.  Otherwise return nil.
3109 Argument LINES specifies lines to be scrolled up."
3110   (interactive "p")
3111   (move-to-window-line -1)
3112   (if (save-excursion
3113         (end-of-line)
3114         (and (pos-visible-in-window-p)  ;Not continuation line.
3115              (eobp)))
3116       ;; Nothing in this page.
3117       (if (or (not gnus-page-broken)
3118               (save-excursion
3119                 (save-restriction
3120                   (widen) (forward-line 1) (eobp)))) ;Real end-of-buffer?
3121           t                             ;Nothing more.
3122         (gnus-narrow-to-page 1)         ;Go to next page.
3123         nil)
3124     ;; More in this page.
3125     (let ((scroll-in-place nil))
3126       (condition-case ()
3127           (scroll-up lines)
3128         (end-of-buffer
3129          ;; Long lines may cause an end-of-buffer error.
3130          (goto-char (point-max)))))
3131     (move-to-window-line 0)
3132     nil))
3133
3134 (defun gnus-article-prev-page (&optional lines)
3135   "Show previous page of current article.
3136 Argument LINES specifies lines to be scrolled down."
3137   (interactive "p")
3138   (move-to-window-line 0)
3139   (if (and gnus-page-broken
3140            (bobp)
3141            (not (save-restriction (widen) (bobp)))) ;Real beginning-of-buffer?
3142       (progn
3143         (gnus-narrow-to-page -1)        ;Go to previous page.
3144         (goto-char (point-max))
3145         (recenter -1))
3146     (let ((scroll-in-place nil))
3147       (prog1
3148           (condition-case ()
3149               (scroll-down lines)
3150             (beginning-of-buffer
3151              (goto-char (point-min))))
3152         (move-to-window-line 0)))))
3153
3154 (defun gnus-article-refer-article ()
3155   "Read article specified by message-id around point."
3156   (interactive)
3157   (let ((point (point)))
3158     (search-forward ">" nil t)          ;Move point to end of "<....>".
3159     (if (re-search-backward "\\(<[^<> \t\n]+>\\)" nil t)
3160         (let ((message-id (match-string 1)))
3161           (goto-char point)
3162           (set-buffer gnus-summary-buffer)
3163           (gnus-summary-refer-article message-id))
3164       (goto-char (point))
3165       (error "No references around point"))))
3166
3167 (defun gnus-article-show-summary ()
3168   "Reconfigure windows to show summary buffer."
3169   (interactive)
3170   (if (not (gnus-buffer-live-p gnus-summary-buffer))
3171       (error "There is no summary buffer for this article buffer")
3172     (gnus-article-set-globals)
3173     (gnus-configure-windows 'article)
3174     (gnus-summary-goto-subject gnus-current-article)
3175     (gnus-summary-position-point)))
3176
3177 (defun gnus-article-describe-briefly ()
3178   "Describe article mode commands briefly."
3179   (interactive)
3180   (gnus-message 6 (substitute-command-keys "\\<gnus-article-mode-map>\\[gnus-article-goto-next-page]:Next page   \\[gnus-article-goto-prev-page]:Prev page  \\[gnus-article-show-summary]:Show summary  \\[gnus-info-find-node]:Run Info  \\[gnus-article-describe-briefly]:This help")))
3181
3182 (defun gnus-article-summary-command ()
3183   "Execute the last keystroke in the summary buffer."
3184   (interactive)
3185   (let ((obuf (current-buffer))
3186         (owin (current-window-configuration))
3187         func)
3188     (switch-to-buffer gnus-article-current-summary 'norecord)
3189     (setq func (lookup-key (current-local-map) (this-command-keys)))
3190     (call-interactively func)
3191     (set-buffer obuf)
3192     (set-window-configuration owin)
3193     (set-window-point (get-buffer-window (current-buffer)) (point))))
3194
3195 (defun gnus-article-summary-command-nosave ()
3196   "Execute the last keystroke in the summary buffer."
3197   (interactive)
3198   (let (func)
3199     (pop-to-buffer gnus-article-current-summary 'norecord)
3200     (setq func (lookup-key (current-local-map) (this-command-keys)))
3201     (call-interactively func)))
3202
3203 (defun gnus-article-check-buffer ()
3204   "Beep if not in an article buffer."
3205   (unless (equal major-mode 'gnus-article-mode)
3206     (error "Command invoked outside of a Gnus article buffer")))
3207
3208 (defun gnus-article-read-summary-keys (&optional arg key not-restore-window)
3209   "Read a summary buffer key sequence and execute it from the article buffer."
3210   (interactive "P")
3211   (gnus-article-check-buffer)
3212   (let ((nosaves
3213          '("q" "Q"  "c" "r" "R" "\C-c\C-f" "m"  "a" "f" "F"
3214            "Zc" "ZC" "ZE" "ZQ" "ZZ" "Zn" "ZR" "ZG" "ZN" "ZP"
3215            "=" "^" "\M-^" "|"))
3216         (nosave-but-article
3217          '("A\r"))
3218         (nosave-in-article
3219          '("\C-d"))
3220         (up-to-top
3221          '("n" "Gn" "p" "Gp"))
3222         keys new-sum-point)
3223     (save-excursion
3224       (set-buffer gnus-article-current-summary)
3225       (let (gnus-pick-mode)
3226         (push (or key last-command-event) unread-command-events)
3227         (setq keys (read-key-sequence nil))))
3228     (message "")
3229
3230     (if (or (member keys nosaves)
3231             (member keys nosave-but-article)
3232             (member keys nosave-in-article))
3233         (let (func)
3234           (save-window-excursion
3235             (pop-to-buffer gnus-article-current-summary 'norecord)
3236             ;; We disable the pick minor mode commands.
3237             (let (gnus-pick-mode)
3238               (setq func (lookup-key (current-local-map) keys))))
3239           (if (not func)
3240               (ding)
3241             (unless (member keys nosave-in-article)
3242               (set-buffer gnus-article-current-summary))
3243             (call-interactively func)
3244             (setq new-sum-point (point)))
3245           (when (member keys nosave-but-article)
3246             (pop-to-buffer gnus-article-buffer 'norecord)))
3247       ;; These commands should restore window configuration.
3248       (let ((obuf (current-buffer))
3249             (owin (current-window-configuration))
3250             (opoint (point))
3251             (summary gnus-article-current-summary)
3252             func in-buffer selected)
3253         (if not-restore-window
3254             (pop-to-buffer summary 'norecord)
3255           (switch-to-buffer summary 'norecord))
3256         (setq in-buffer (current-buffer))
3257         ;; We disable the pick minor mode commands.
3258         (if (setq func (let (gnus-pick-mode)
3259                          (lookup-key (current-local-map) keys)))
3260             (progn
3261               (call-interactively func)
3262               (setq new-sum-point (point)))
3263           (ding))
3264         (when (eq in-buffer (current-buffer))
3265           (setq selected (gnus-summary-select-article))
3266           (set-buffer obuf)
3267           (unless not-restore-window
3268             (set-window-configuration owin))
3269           (unless (or (not (eq selected 'old)) (member keys up-to-top))
3270             (set-window-point (get-buffer-window (current-buffer))
3271                               opoint))
3272           (let ((win (get-buffer-window gnus-article-current-summary)))
3273             (when win
3274               (set-window-point win new-sum-point))))))))
3275
3276 (defun gnus-article-hide (&optional arg force)
3277   "Hide all the gruft in the current article.
3278 This means that PGP stuff, signatures, cited text and (some)
3279 headers will be hidden.
3280 If given a prefix, show the hidden text instead."
3281   (interactive (append (gnus-article-hidden-arg) (list 'force)))
3282   (gnus-article-hide-headers arg)
3283   (gnus-article-hide-pgp arg)
3284   (gnus-article-hide-citation-maybe arg force)
3285   (gnus-article-hide-signature arg))
3286
3287 (defun gnus-article-maybe-highlight ()
3288   "Do some article highlighting if article highlighting is requested."
3289   (when (gnus-visual-p 'article-highlight 'highlight)
3290     (gnus-article-highlight-some)))
3291
3292 (defun gnus-check-group-server ()
3293   ;; Make sure the connection to the server is alive.
3294   (unless (gnus-server-opened
3295            (gnus-find-method-for-group gnus-newsgroup-name))
3296     (gnus-check-server (gnus-find-method-for-group gnus-newsgroup-name))
3297     (gnus-request-group gnus-newsgroup-name t)))
3298
3299 (defun gnus-request-article-this-buffer (article group)
3300   "Get an article and insert it into this buffer."
3301   (let (do-update-line sparse-header)
3302     (prog1
3303         (save-excursion
3304           (erase-buffer)
3305           (gnus-kill-all-overlays)
3306           (setq group (or group gnus-newsgroup-name))
3307
3308           ;; Using `gnus-request-article' directly will insert the article into
3309           ;; `nntp-server-buffer' - so we'll save some time by not having to
3310           ;; copy it from the server buffer into the article buffer.
3311
3312           ;; We only request an article by message-id when we do not have the
3313           ;; headers for it, so we'll have to get those.
3314           (when (stringp article)
3315             (let ((gnus-override-method gnus-refer-article-method))
3316               (gnus-read-header article)))
3317
3318           ;; If the article number is negative, that means that this article
3319           ;; doesn't belong in this newsgroup (possibly), so we find its
3320           ;; message-id and request it by id instead of number.
3321           (when (and (numberp article)
3322                      gnus-summary-buffer
3323                      (get-buffer gnus-summary-buffer)
3324                      (gnus-buffer-exists-p gnus-summary-buffer))
3325             (save-excursion
3326               (set-buffer gnus-summary-buffer)
3327               (let ((header (gnus-summary-article-header article)))
3328                 (when (< article 0)
3329                   (cond
3330                    ((memq article gnus-newsgroup-sparse)
3331                     ;; This is a sparse gap article.
3332                     (setq do-update-line article)
3333                     (setq article (mail-header-id header))
3334                     (let ((gnus-override-method gnus-refer-article-method))
3335                       (setq sparse-header (gnus-read-header article)))
3336                     (setq gnus-newsgroup-sparse
3337                           (delq article gnus-newsgroup-sparse)))
3338                    ((vectorp header)
3339                     ;; It's a real article.
3340                     (setq article (mail-header-id header)))
3341                    (t
3342                     ;; It is an extracted pseudo-article.
3343                     (setq article 'pseudo)
3344                     (gnus-request-pseudo-article header))))
3345
3346                 (let ((method (gnus-find-method-for-group
3347                                gnus-newsgroup-name)))
3348                   (when (and (eq (car method) 'nneething)
3349                              (vectorp header))
3350                     (let ((dir (concat
3351                                 (file-name-as-directory
3352                                  (or (cadr (assq 'nneething-address method))
3353                                      (nth 1 method)))
3354                                 (mail-header-subject header))))
3355                       (when (file-directory-p dir)
3356                         (setq article 'nneething)
3357                         (gnus-group-enter-directory dir))))))))
3358
3359           (cond
3360            ;; Refuse to select canceled articles.
3361            ((and (numberp article)
3362                  gnus-summary-buffer
3363                  (get-buffer gnus-summary-buffer)
3364                  (gnus-buffer-exists-p gnus-summary-buffer)
3365                  (eq (cdr (save-excursion
3366                             (set-buffer gnus-summary-buffer)
3367                             (assq article gnus-newsgroup-reads)))
3368                      gnus-canceled-mark))
3369             nil)
3370            ;; We first check `gnus-original-article-buffer'.
3371            ((and (get-buffer gnus-original-article-buffer)
3372                  (numberp article)
3373                  (save-excursion
3374                    (set-buffer gnus-original-article-buffer)
3375                    (and (equal (car gnus-original-article) group)
3376                         (eq (cdr gnus-original-article) article))))
3377             (insert-buffer-substring gnus-original-article-buffer)
3378             'article)
3379            ;; Check the backlog.
3380            ((and gnus-keep-backlog
3381                  (gnus-backlog-request-article group article (current-buffer)))
3382             'article)
3383            ;; Check asynchronous pre-fetch.
3384            ((gnus-async-request-fetched-article group article (current-buffer))
3385             (gnus-async-prefetch-next group article gnus-summary-buffer)
3386             (when (and (numberp article) gnus-keep-backlog)
3387               (gnus-backlog-enter-article group article (current-buffer)))
3388             'article)
3389            ;; Check the cache.
3390            ((and gnus-use-cache
3391                  (numberp article)
3392                  (gnus-cache-request-article article group))
3393             'article)
3394            ;; Get the article and put into the article buffer.
3395            ((or (stringp article) (numberp article))
3396             (let ((gnus-override-method
3397                    (and (stringp article) gnus-refer-article-method))
3398                   (buffer-read-only nil))
3399               (erase-buffer)
3400               (gnus-kill-all-overlays)
3401               (gnus-check-group-server)
3402               (when (gnus-request-article article group (current-buffer))
3403                 (when (numberp article)
3404                   (gnus-async-prefetch-next group article gnus-summary-buffer)
3405                   (when gnus-keep-backlog
3406                     (gnus-backlog-enter-article
3407                      group article (current-buffer))))
3408                 'article)))
3409            ;; It was a pseudo.
3410            (t article)))
3411
3412       ;; Associate this article with the current summary buffer.
3413       (setq gnus-article-current-summary gnus-summary-buffer)
3414
3415       ;; Take the article from the original article buffer
3416       ;; and place it in the buffer it's supposed to be in.
3417       (when (and (get-buffer gnus-article-buffer)
3418                  (equal (buffer-name (current-buffer))
3419                         (buffer-name (get-buffer gnus-article-buffer))))
3420         (save-excursion
3421           (if (get-buffer gnus-original-article-buffer)
3422               (set-buffer gnus-original-article-buffer)
3423             (set-buffer (gnus-get-buffer-create gnus-original-article-buffer))
3424             (buffer-disable-undo)
3425             (setq major-mode 'gnus-original-article-mode)
3426             (setq buffer-read-only t))
3427           (let (buffer-read-only)
3428             (erase-buffer)
3429             (insert-buffer-substring gnus-article-buffer))
3430           (setq gnus-original-article (cons group article)))
3431
3432         ;; Decode charsets.
3433         (run-hooks 'gnus-article-decode-hook)
3434         ;; Mark article as decoded or not.
3435         (setq gnus-article-decoded-p gnus-article-decode-hook))
3436
3437       ;; Update sparse articles.
3438       (when (and do-update-line
3439                  (or (numberp article)
3440                      (stringp article)))
3441         (let ((buf (current-buffer)))
3442           (set-buffer gnus-summary-buffer)
3443           (gnus-summary-update-article do-update-line sparse-header)
3444           (gnus-summary-goto-subject do-update-line nil t)
3445           (set-window-point (get-buffer-window (current-buffer) t)
3446                             (point))
3447           (set-buffer buf))))))
3448
3449 ;;;
3450 ;;; Article editing
3451 ;;;
3452
3453 (defcustom gnus-article-edit-mode-hook nil
3454   "Hook run in article edit mode buffers."
3455   :group 'gnus-article-various
3456   :type 'hook)
3457
3458 (defvar gnus-article-edit-done-function nil)
3459
3460 (defvar gnus-article-edit-mode-map nil)
3461
3462 ;; Should we be using derived.el for this?
3463 (unless gnus-article-edit-mode-map
3464   (setq gnus-article-edit-mode-map (make-sparse-keymap))
3465   (set-keymap-parent gnus-article-edit-mode-map text-mode-map)
3466
3467   (gnus-define-keys gnus-article-edit-mode-map
3468     "\C-c\C-c" gnus-article-edit-done
3469     "\C-c\C-k" gnus-article-edit-exit)
3470
3471   (gnus-define-keys (gnus-article-edit-wash-map
3472                      "\C-c\C-w" gnus-article-edit-mode-map)
3473     "f" gnus-article-edit-full-stops))
3474
3475 (defun gnus-article-edit-mode ()
3476   "Major mode for editing articles.
3477 This is an extended text-mode.
3478
3479 \\{gnus-article-edit-mode-map}"
3480   (interactive)
3481   (setq major-mode 'gnus-article-edit-mode)
3482   (setq mode-name "Article Edit")
3483   (use-local-map gnus-article-edit-mode-map)
3484   (make-local-variable 'gnus-article-edit-done-function)
3485   (make-local-variable 'gnus-prev-winconf)
3486   (setq buffer-read-only nil)
3487   (buffer-enable-undo)
3488   (widen)
3489   (gnus-run-hooks 'text-mode-hook 'gnus-article-edit-mode-hook))
3490
3491 (defun gnus-article-edit (&optional force)
3492   "Edit the current article.
3493 This will have permanent effect only in mail groups.
3494 If FORCE is non-nil, allow editing of articles even in read-only
3495 groups."
3496   (interactive "P")
3497   (when (and (not force)
3498              (gnus-group-read-only-p))
3499     (error "The current newsgroup does not support article editing"))
3500   (gnus-article-date-original)
3501   (gnus-article-edit-article
3502    `(lambda (no-highlight)
3503       (gnus-summary-edit-article-done
3504        ,(or (mail-header-references gnus-current-headers) "")
3505        ,(gnus-group-read-only-p) ,gnus-summary-buffer no-highlight))))
3506
3507 (defun gnus-article-edit-article (exit-func)
3508   "Start editing the contents of the current article buffer."
3509   (let ((winconf (current-window-configuration)))
3510     (set-buffer gnus-article-buffer)
3511     (gnus-article-edit-mode)
3512     (gnus-article-delete-text-of-type 'annotation)
3513     (gnus-set-text-properties (point-min) (point-max) nil)
3514     (gnus-configure-windows 'edit-article)
3515     (setq gnus-article-edit-done-function exit-func)
3516     (setq gnus-prev-winconf winconf)
3517     (gnus-message 6 "C-c C-c to end edits")))
3518
3519 (defun gnus-article-edit-done (&optional arg)
3520   "Update the article edits and exit."
3521   (interactive "P")
3522   (save-excursion
3523     (save-restriction
3524       (widen)
3525       (when (article-goto-body)
3526         (let ((lines (count-lines (point) (point-max)))
3527               (length (- (point-max) (point)))
3528               (case-fold-search t)
3529               (body (copy-marker (point))))
3530           (goto-char (point-min))
3531           (when (re-search-forward "^content-length:[ \t]\\([0-9]+\\)" body t)
3532             (delete-region (match-beginning 1) (match-end 1))
3533             (insert (number-to-string length)))
3534           (goto-char (point-min))
3535           (when (re-search-forward
3536                  "^x-content-length:[ \t]\\([0-9]+\\)" body t)
3537             (delete-region (match-beginning 1) (match-end 1))
3538             (insert (number-to-string length)))
3539           (goto-char (point-min))
3540           (when (re-search-forward "^lines:[ \t]\\([0-9]+\\)" body t)
3541             (delete-region (match-beginning 1) (match-end 1))
3542             (insert (number-to-string lines)))))))
3543   (let ((func gnus-article-edit-done-function)
3544         (buf (current-buffer))
3545         (start (window-start)))
3546     (gnus-article-edit-exit)
3547     (save-excursion
3548       (set-buffer buf)
3549       (let ((buffer-read-only nil))
3550         (funcall func arg))
3551       ;; The cache and backlog have to be flushed somewhat.
3552       (when gnus-keep-backlog
3553         (gnus-backlog-remove-article
3554          (car gnus-article-current) (cdr gnus-article-current)))
3555       ;; Flush original article as well.
3556       (save-excursion
3557         (when (get-buffer gnus-original-article-buffer)
3558           (set-buffer gnus-original-article-buffer)
3559           (setq gnus-original-article nil)))
3560       (when gnus-use-cache
3561         (gnus-cache-update-article
3562          (car gnus-article-current) (cdr gnus-article-current))))
3563     (set-buffer buf)
3564     (set-window-start (get-buffer-window buf) start)
3565     (set-window-point (get-buffer-window buf) (point))))
3566
3567 (defun gnus-article-edit-exit ()
3568   "Exit the article editing without updating."
3569   (interactive)
3570   ;; We remove all text props from the article buffer.
3571   (let ((buf (format "%s" (buffer-string)))
3572         (curbuf (current-buffer))
3573         (p (point))
3574         (window-start (window-start)))
3575     (erase-buffer)
3576     (insert buf)
3577     (let ((winconf gnus-prev-winconf))
3578       (gnus-article-mode)
3579       (set-window-configuration winconf)
3580       ;; Tippy-toe some to make sure that point remains where it was.
3581       (save-current-buffer
3582         (set-buffer curbuf)
3583         (set-window-start (get-buffer-window (current-buffer)) window-start)
3584         (goto-char p)))))
3585
3586 (defun gnus-article-edit-full-stops ()
3587   "Interactively repair spacing at end of sentences."
3588   (interactive)
3589   (save-excursion
3590     (goto-char (point-min))
3591     (search-forward-regexp "^$" nil t)
3592     (let ((case-fold-search nil))
3593       (query-replace-regexp "\\([.!?][])}]* \\)\\([[({A-Z]\\)" "\\1 \\2"))))
3594
3595 ;;;
3596 ;;; Article highlights
3597 ;;;
3598
3599 ;; Written by Per Abrahamsen <abraham@iesd.auc.dk>.
3600
3601 ;;; Internal Variables:
3602
3603 (defcustom gnus-button-url-regexp "\\b\\(s?https?\\|ftp\\|file\\|gopher\\|news\\|telnet\\|wais\\|mailto\\):\\(//[-a-zA-Z0-9_.]+:[0-9]*\\)?\\([-a-zA-Z0-9_=!?#$@~`%&*+|\\/:;.,]\\|\\w\\)+\\([-a-zA-Z0-9_=#$@~`%&*+|\\/]\\|\\w\\)"
3604   "Regular expression that matches URLs."
3605   :group 'gnus-article-buttons
3606   :type 'regexp)
3607
3608 (defcustom gnus-button-alist
3609   `(("<\\(url:[>\n\t ]*?\\)?news:[>\n\t ]*\\([^>\n\t ]*@[^>\n\t ]*\\)>"
3610      0 t gnus-button-message-id 2)
3611     ("\\bnews:\\([^>\n\t ]*@[^>)!;:,\n\t ]*\\)" 0 t gnus-button-message-id 1)
3612     ("\\(\\b<\\(url:[>\n\t ]*\\)?news:[>\n\t ]*\\(//\\)?\\([^>\n\t ]*\\)>\\)"
3613      1 t
3614      gnus-button-fetch-group 4)
3615     ("\\bnews:\\(//\\)?\\([^'\">\n\t ]+\\)" 0 t gnus-button-fetch-group 2)
3616     ("\\bin\\( +article\\| +message\\)? +\\(<\\([^\n @<>]+@[^\n @<>]+\\)>\\)" 2
3617      t gnus-button-message-id 3)
3618     ("\\(<URL: *\\)mailto: *\\([^> \n\t]+\\)>" 0 t gnus-url-mailto 2)
3619     ("mailto:\\([-a-zA-Z.@_+0-9%]+\\)" 0 t gnus-url-mailto 1)
3620     ("\\bmailto:\\([^ \n\t]+\\)" 0 t gnus-url-mailto 1)
3621     ;; This is how URLs _should_ be embedded in text...
3622     ("<URL: *\\([^>]*\\)>" 0 t gnus-button-embedded-url 1)
3623     ;; Raw URLs.
3624     (,gnus-button-url-regexp 0 t gnus-button-url 0))
3625   "*Alist of regexps matching buttons in article bodies.
3626
3627 Each entry has the form (REGEXP BUTTON FORM CALLBACK PAR...), where
3628 REGEXP: is the string matching text around the button,
3629 BUTTON: is the number of the regexp grouping actually matching the button,
3630 FORM: is a lisp expression which must eval to true for the button to
3631 be added,
3632 CALLBACK: is the function to call when the user push this button, and each
3633 PAR: is a number of a regexp grouping whose text will be passed to CALLBACK.
3634
3635 CALLBACK can also be a variable, in that case the value of that
3636 variable it the real callback function."
3637   :group 'gnus-article-buttons
3638   :type '(repeat (list regexp
3639                        (integer :tag "Button")
3640                        (sexp :tag "Form")
3641                        (function :tag "Callback")
3642                        (repeat :tag "Par"
3643                                :inline t
3644                                (integer :tag "Regexp group")))))
3645
3646 (defcustom gnus-header-button-alist
3647   `(("^\\(References\\|Message-I[Dd]\\):" "<[^>]+>"
3648      0 t gnus-button-message-id 0)
3649     ("^\\(From\\|Reply-To\\):" ": *\\(.+\\)$" 1 t gnus-button-reply 1)
3650     ("^\\(Cc\\|To\\):" "[^ \t\n<>,()\"]+@[^ \t\n<>,()\"]+"
3651      0 t gnus-button-mailto 0)
3652     ("^X-[Uu][Rr][Ll]:" ,gnus-button-url-regexp 0 t gnus-button-url 0)
3653     ("^Subject:" ,gnus-button-url-regexp 0 t gnus-button-url 0)
3654     ("^[^:]+:" ,gnus-button-url-regexp 0 t gnus-button-url 0)
3655     ("^[^:]+:" "\\(<\\(url: \\)?news:\\([^>\n ]*\\)>\\)" 1 t
3656      gnus-button-message-id 3))
3657   "*Alist of headers and regexps to match buttons in article heads.
3658
3659 This alist is very similar to `gnus-button-alist', except that each
3660 alist has an additional HEADER element first in each entry:
3661
3662 \(HEADER REGEXP BUTTON FORM CALLBACK PAR)
3663
3664 HEADER is a regexp to match a header.  For a fuller explanation, see
3665 `gnus-button-alist'."
3666   :group 'gnus-article-buttons
3667   :group 'gnus-article-headers
3668   :type '(repeat (list (regexp :tag "Header")
3669                        regexp
3670                        (integer :tag "Button")
3671                        (sexp :tag "Form")
3672                        (function :tag "Callback")
3673                        (repeat :tag "Par"
3674                                :inline t
3675                                (integer :tag "Regexp group")))))
3676
3677 (defvar gnus-button-regexp nil)
3678 (defvar gnus-button-marker-list nil)
3679 ;; Regexp matching any of the regexps from `gnus-button-alist'.
3680
3681 (defvar gnus-button-last nil)
3682 ;; The value of `gnus-button-alist' when `gnus-button-regexp' was build.
3683
3684 ;;; Commands:
3685
3686 (defun gnus-article-push-button (event)
3687   "Check text under the mouse pointer for a callback function.
3688 If the text under the mouse pointer has a `gnus-callback' property,
3689 call it with the value of the `gnus-data' text property."
3690   (interactive "e")
3691   (set-buffer (window-buffer (posn-window (event-start event))))
3692   (let* ((pos (posn-point (event-start event)))
3693          (data (get-text-property pos 'gnus-data))
3694          (fun (get-text-property pos 'gnus-callback)))
3695     (goto-char pos)
3696     (when fun
3697       (funcall fun data))))
3698
3699 (defun gnus-article-press-button ()
3700   "Check text at point for a callback function.
3701 If the text at point has a `gnus-callback' property,
3702 call it with the value of the `gnus-data' text property."
3703   (interactive)
3704   (let* ((data (get-text-property (point) 'gnus-data))
3705          (fun (get-text-property (point) 'gnus-callback)))
3706     (when fun
3707       (funcall fun data))))
3708
3709 (defun gnus-article-highlight (&optional force)
3710   "Highlight current article.
3711 This function calls `gnus-article-highlight-headers',
3712 `gnus-article-highlight-citation',
3713 `gnus-article-highlight-signature', and `gnus-article-add-buttons' to
3714 do the highlighting.  See the documentation for those functions."
3715   (interactive (list 'force))
3716   (gnus-article-highlight-headers)
3717   (gnus-article-highlight-citation force)
3718   (gnus-article-highlight-signature)
3719   (gnus-article-add-buttons force)
3720   (gnus-article-add-buttons-to-head))
3721
3722 (defun gnus-article-highlight-some (&optional force)
3723   "Highlight current article.
3724 This function calls `gnus-article-highlight-headers',
3725 `gnus-article-highlight-signature', and `gnus-article-add-buttons' to
3726 do the highlighting.  See the documentation for those functions."
3727   (interactive (list 'force))
3728   (gnus-article-highlight-headers)
3729   (gnus-article-highlight-signature)
3730   (gnus-article-add-buttons))
3731
3732 (defun gnus-article-highlight-headers ()
3733   "Highlight article headers as specified by `gnus-header-face-alist'."
3734   (interactive)
3735   (save-excursion
3736     (set-buffer gnus-article-buffer)
3737     (save-restriction
3738       (let ((alist gnus-header-face-alist)
3739             (buffer-read-only nil)
3740             (case-fold-search t)
3741             (inhibit-point-motion-hooks t)
3742             entry regexp header-face field-face from hpoints fpoints)
3743         (message-narrow-to-head)
3744         (while (setq entry (pop alist))
3745           (goto-char (point-min))
3746           (setq regexp (concat "^\\("
3747                                (if (string-equal "" (nth 0 entry))
3748                                    "[^\t ]"
3749                                  (nth 0 entry))
3750                                "\\)")
3751                 header-face (nth 1 entry)
3752                 field-face (nth 2 entry))
3753           (while (and (re-search-forward regexp nil t)
3754                       (not (eobp)))
3755             (beginning-of-line)
3756             (setq from (point))
3757             (unless (search-forward ":" nil t)
3758               (forward-char 1))
3759             (when (and header-face
3760                        (not (memq (point) hpoints)))
3761               (push (point) hpoints)
3762               (gnus-put-text-property from (point) 'face header-face))
3763             (when (and field-face
3764                        (not (memq (setq from (point)) fpoints)))
3765               (push from fpoints)
3766               (if (re-search-forward "^[^ \t]" nil t)
3767                   (forward-char -2)
3768                 (goto-char (point-max)))
3769               (gnus-put-text-property from (point) 'face field-face))))))))
3770
3771 (defun gnus-article-highlight-signature ()
3772   "Highlight the signature in an article.
3773 It does this by highlighting everything after
3774 `gnus-signature-separator' using `gnus-signature-face'."
3775   (interactive)
3776   (save-excursion
3777     (set-buffer gnus-article-buffer)
3778     (let ((buffer-read-only nil)
3779           (inhibit-point-motion-hooks t))
3780       (save-restriction
3781         (when (and gnus-signature-face
3782                    (gnus-article-narrow-to-signature))
3783           (gnus-overlay-put (gnus-make-overlay (point-min) (point-max))
3784                             'face gnus-signature-face)
3785           (widen)
3786           (gnus-article-search-signature)
3787           (let ((start (match-beginning 0))
3788                 (end (set-marker (make-marker) (1+ (match-end 0)))))
3789             (gnus-article-add-button start (1- end) 'gnus-signature-toggle
3790                                      end)))))))
3791
3792 (defun gnus-button-in-region-p (b e prop)
3793   "Say whether PROP exists in the region."
3794   (text-property-not-all b e prop nil))
3795
3796 (defun gnus-article-add-buttons (&optional force)
3797   "Find external references in the article and make buttons of them.
3798 \"External references\" are things like Message-IDs and URLs, as
3799 specified by `gnus-button-alist'."
3800   (interactive (list 'force))
3801   (save-excursion
3802     (set-buffer gnus-article-buffer)
3803     (let ((buffer-read-only nil)
3804           (inhibit-point-motion-hooks t)
3805           (case-fold-search t)
3806           (alist gnus-button-alist)
3807           beg entry regexp)
3808       ;; Remove all old markers.
3809       (let (marker entry)
3810         (while (setq marker (pop gnus-button-marker-list))
3811           (goto-char marker)
3812           (when (setq entry (gnus-button-entry))
3813             (put-text-property (match-beginning (nth 1 entry))
3814                                (match-end (nth 1 entry))
3815                                'gnus-callback nil))
3816           (set-marker marker nil)))
3817       ;; We skip the headers.
3818       (article-goto-body)
3819       (setq beg (point))
3820       (while (setq entry (pop alist))
3821         (setq regexp (car entry))
3822         (goto-char beg)
3823         (while (re-search-forward regexp nil t)
3824           (let* ((start (and entry (match-beginning (nth 1 entry))))
3825                  (end (and entry (match-end (nth 1 entry))))
3826                  (from (match-beginning 0)))
3827             (when (and (or (eq t (nth 2 entry))
3828                            (eval (nth 2 entry)))
3829                        (not (gnus-button-in-region-p
3830                              start end 'gnus-callback)))
3831               ;; That optional form returned non-nil, so we add the
3832               ;; button.
3833               (gnus-article-add-button
3834                start end 'gnus-button-push
3835                (car (push (set-marker (make-marker) from)
3836                           gnus-button-marker-list))))))))))
3837
3838 ;; Add buttons to the head of an article.
3839 (defun gnus-article-add-buttons-to-head ()
3840   "Add buttons to the head of the article."
3841   (interactive)
3842   (save-excursion
3843     (set-buffer gnus-article-buffer)
3844     (let ((buffer-read-only nil)
3845           (inhibit-point-motion-hooks t)
3846           (case-fold-search t)
3847           (alist gnus-header-button-alist)
3848           entry beg end)
3849       (nnheader-narrow-to-headers)
3850       (while alist
3851         ;; Each alist entry.
3852         (setq entry (car alist)
3853               alist (cdr alist))
3854         (goto-char (point-min))
3855         (while (re-search-forward (car entry) nil t)
3856           ;; Each header matching the entry.
3857           (setq beg (match-beginning 0))
3858           (setq end (or (and (re-search-forward "^[^ \t]" nil t)
3859                              (match-beginning 0))
3860                         (point-max)))
3861           (goto-char beg)
3862           (while (re-search-forward (nth 1 entry) end t)
3863             ;; Each match within a header.
3864             (let* ((entry (cdr entry))
3865                    (start (match-beginning (nth 1 entry)))
3866                    (end (match-end (nth 1 entry)))
3867                    (form (nth 2 entry)))
3868               (goto-char (match-end 0))
3869               (when (eval form)
3870                 (gnus-article-add-button
3871                  start end (nth 3 entry)
3872                  (buffer-substring (match-beginning (nth 4 entry))
3873                                    (match-end (nth 4 entry)))))))
3874           (goto-char end))))
3875     (widen)))
3876
3877 ;;; External functions:
3878
3879 (defun gnus-article-add-button (from to fun &optional data)
3880   "Create a button between FROM and TO with callback FUN and data DATA."
3881   (when gnus-article-button-face
3882     (gnus-overlay-put (gnus-make-overlay from to)
3883                       'face gnus-article-button-face))
3884   (gnus-add-text-properties
3885    from to
3886    (nconc (and gnus-article-mouse-face
3887                (list gnus-mouse-face-prop gnus-article-mouse-face))
3888           (list 'gnus-callback fun)
3889           (and data (list 'gnus-data data))))
3890   (widget-convert-button 'link from to :action 'gnus-widget-press-button
3891                          :button-keymap gnus-widget-button-keymap))
3892
3893 ;;; Internal functions:
3894
3895 (defun gnus-article-set-globals ()
3896   (save-excursion
3897     (set-buffer gnus-summary-buffer)
3898     (gnus-set-global-variables)))
3899
3900 (defun gnus-signature-toggle (end)
3901   (save-excursion
3902     (set-buffer gnus-article-buffer)
3903     (let ((buffer-read-only nil)
3904           (inhibit-point-motion-hooks t))
3905       (if (get-text-property end 'invisible)
3906           (gnus-article-unhide-text end (point-max))
3907         (gnus-article-hide-text end (point-max) gnus-hidden-properties)))))
3908
3909 (defun gnus-button-entry ()
3910   ;; Return the first entry in `gnus-button-alist' matching this place.
3911   (let ((alist gnus-button-alist)
3912         (entry nil))
3913     (while alist
3914       (setq entry (pop alist))
3915       (if (looking-at (car entry))
3916           (setq alist nil)
3917         (setq entry nil)))
3918     entry))
3919
3920 (defun gnus-button-push (marker)
3921   ;; Push button starting at MARKER.
3922   (save-excursion
3923     (goto-char marker)
3924     (let* ((entry (gnus-button-entry))
3925            (inhibit-point-motion-hooks t)
3926            (fun (nth 3 entry))
3927            (args (mapcar (lambda (group)
3928                            (let ((string (match-string group)))
3929                              (gnus-set-text-properties
3930                               0 (length string) nil string)
3931                              string))
3932                          (nthcdr 4 entry))))
3933       (cond
3934        ((fboundp fun)
3935         (apply fun args))
3936        ((and (boundp fun)
3937              (fboundp (symbol-value fun)))
3938         (apply (symbol-value fun) args))
3939        (t
3940         (gnus-message 1 "You must define `%S' to use this button"
3941                       (cons fun args)))))))
3942
3943 (defun gnus-button-message-id (message-id)
3944   "Fetch MESSAGE-ID."
3945   (save-excursion
3946     (set-buffer gnus-summary-buffer)
3947     (gnus-summary-refer-article message-id)))
3948
3949 (defun gnus-button-fetch-group (address)
3950   "Fetch GROUP specified by ADDRESS."
3951   (if (not (string-match "[:/]" address))
3952       ;; This is just a simple group url.
3953       (gnus-group-read-ephemeral-group address gnus-select-method)
3954     (if (not (string-match "^\\([^:/]+\\)\\(:\\([^/]+\\)/\\)?\\(.*\\)$"
3955                            address))
3956         (error "Can't parse %s" address)
3957       (gnus-group-read-ephemeral-group
3958        (match-string 4 address)
3959        `(nntp ,(match-string 1 address)
3960               (nntp-address ,(match-string 1 address))
3961               (nntp-port-number ,(if (match-end 3)
3962                                      (match-string 3 address)
3963                                    "nntp")))))))
3964
3965 (defun gnus-url-parse-query-string (query &optional downcase)
3966   (let (retval pairs cur key val)
3967     (setq pairs (split-string query "&"))
3968     (while pairs
3969       (setq cur (car pairs)
3970             pairs (cdr pairs))
3971       (if (not (string-match "=" cur))
3972           nil                           ; Grace
3973         (setq key (gnus-url-unhex-string (substring cur 0 (match-beginning 0)))
3974               val (gnus-url-unhex-string (substring cur (match-end 0) nil)))
3975         (if downcase
3976             (setq key (downcase key)))
3977         (setq cur (assoc key retval))
3978         (if cur
3979             (setcdr cur (cons val (cdr cur)))
3980           (setq retval (cons (list key val) retval)))))
3981     retval))
3982
3983 (defun gnus-url-unhex (x)
3984   (if (> x ?9)
3985       (if (>= x ?a)
3986           (+ 10 (- x ?a))
3987         (+ 10 (- x ?A)))
3988     (- x ?0)))
3989
3990 (defun gnus-url-unhex-string (str &optional allow-newlines)
3991   "Remove %XXX embedded spaces, etc in a url.
3992 If optional second argument ALLOW-NEWLINES is non-nil, then allow the
3993 decoding of carriage returns and line feeds in the string, which is normally
3994 forbidden in URL encoding."
3995   (setq str (or str ""))
3996   (let ((tmp "")
3997         (case-fold-search t))
3998     (while (string-match "%[0-9a-f][0-9a-f]" str)
3999       (let* ((start (match-beginning 0))
4000              (ch1 (gnus-url-unhex (elt str (+ start 1))))
4001              (code (+ (* 16 ch1)
4002                       (gnus-url-unhex (elt str (+ start 2))))))
4003         (setq tmp (concat
4004                    tmp (substring str 0 start)
4005                    (cond
4006                     (allow-newlines
4007                      (char-to-string code))
4008                     ((or (= code ?\n) (= code ?\r))
4009                      " ")
4010                     (t (char-to-string code))))
4011               str (substring str (match-end 0)))))
4012     (setq tmp (concat tmp str))
4013     tmp))
4014
4015 (defun gnus-url-mailto (url)
4016   ;; Send mail to someone
4017   (when (string-match "mailto:/*\\(.*\\)" url)
4018     (setq url (substring url (match-beginning 1) nil)))
4019   (let (to args subject func)
4020     (if (string-match (regexp-quote "?") url)
4021         (setq to (gnus-url-unhex-string (substring url 0 (match-beginning 0)))
4022               args (gnus-url-parse-query-string
4023                     (substring url (match-end 0) nil) t))
4024       (setq to (gnus-url-unhex-string url)))
4025     (setq args (cons (list "to" to) args)
4026           subject (cdr-safe (assoc "subject" args)))
4027     (message-mail)
4028     (while args
4029       (setq func (intern-soft (concat "message-goto-" (downcase (caar args)))))
4030       (if (fboundp func)
4031           (funcall func)
4032         (message-position-on-field (caar args)))
4033       (insert (mapconcat 'identity (cdar args) ", "))
4034       (setq args (cdr args)))
4035     (if subject
4036         (message-goto-body)
4037       (message-goto-subject))))
4038
4039 (defun gnus-button-mailto (address)
4040   ;; Mail to ADDRESS.
4041   (set-buffer (gnus-copy-article-buffer))
4042   (message-reply address))
4043
4044 (defun gnus-button-reply (address)
4045   ;; Reply to ADDRESS.
4046   (message-reply address))
4047
4048 (defun gnus-button-url (address)
4049   "Browse ADDRESS."
4050   ;; In Emacs 20, `browse-url-browser-function' may be an alist.
4051   (if (listp browse-url-browser-function)
4052       (browse-url address)
4053     (funcall browse-url-browser-function address)))
4054
4055 (defun gnus-button-embedded-url (address)
4056   "Browse ADDRESS."
4057   ;; In Emacs 20, `browse-url-browser-function' may be an alist.
4058   (if (listp browse-url-browser-function)
4059       (browse-url (gnus-strip-whitespace address))
4060     (funcall browse-url-browser-function (gnus-strip-whitespace address))))
4061
4062 ;;; Next/prev buttons in the article buffer.
4063
4064 (defvar gnus-next-page-line-format "%{%(Next page...%)%}\n")
4065 (defvar gnus-prev-page-line-format "%{%(Previous page...%)%}\n")
4066
4067 (defvar gnus-prev-page-map nil)
4068 (unless gnus-prev-page-map
4069   (setq gnus-prev-page-map (make-sparse-keymap))
4070   (define-key gnus-prev-page-map gnus-mouse-2 'gnus-button-prev-page)
4071   (define-key gnus-prev-page-map "\r" 'gnus-button-prev-page))
4072
4073 (defun gnus-insert-prev-page-button ()
4074   (let ((buffer-read-only nil))
4075     (gnus-eval-format
4076      gnus-prev-page-line-format nil
4077      `(gnus-prev t local-map ,gnus-prev-page-map
4078                  gnus-callback gnus-article-button-prev-page
4079                  article-type annotation))))
4080
4081 (defvar gnus-next-page-map nil)
4082 (unless gnus-next-page-map
4083   (setq gnus-next-page-map (make-keymap))
4084   (suppress-keymap gnus-prev-page-map)
4085   (define-key gnus-next-page-map gnus-mouse-2 'gnus-button-next-page)
4086   (define-key gnus-next-page-map "\r" 'gnus-button-next-page))
4087
4088 (defun gnus-button-next-page ()
4089   "Go to the next page."
4090   (interactive)
4091   (let ((win (selected-window)))
4092     (select-window (get-buffer-window gnus-article-buffer t))
4093     (gnus-article-next-page)
4094     (select-window win)))
4095
4096 (defun gnus-button-prev-page ()
4097   "Go to the prev page."
4098   (interactive)
4099   (let ((win (selected-window)))
4100     (select-window (get-buffer-window gnus-article-buffer t))
4101     (gnus-article-prev-page)
4102     (select-window win)))
4103
4104 (defun gnus-insert-next-page-button ()
4105   (let ((buffer-read-only nil))
4106     (gnus-eval-format gnus-next-page-line-format nil
4107                       `(gnus-next
4108                         t local-map ,gnus-next-page-map
4109                         gnus-callback gnus-article-button-next-page
4110                         article-type annotation))))
4111
4112 (defun gnus-article-button-next-page (arg)
4113   "Go to the next page."
4114   (interactive "P")
4115   (let ((win (selected-window)))
4116     (select-window (get-buffer-window gnus-article-buffer t))
4117     (gnus-article-next-page)
4118     (select-window win)))
4119
4120 (defun gnus-article-button-prev-page (arg)
4121   "Go to the prev page."
4122   (interactive "P")
4123   (let ((win (selected-window)))
4124     (select-window (get-buffer-window gnus-article-buffer t))
4125     (gnus-article-prev-page)
4126     (select-window win)))
4127
4128 (defvar gnus-decode-header-methods
4129   '(mail-decode-encoded-word-region)
4130   "List of methods used to decode headers.
4131
4132 This variable is a list of FUNCTION or (REGEXP . FUNCTION). If item is
4133 FUNCTION, FUNCTION will be apply to all newsgroups. If item is a
4134 (REGEXP . FUNCTION), FUNCTION will be only apply to thes newsgroups
4135 whose names match REGEXP.
4136
4137 For example:
4138 ((\"chinese\" . gnus-decode-encoded-word-region-by-guess)
4139  mail-decode-encoded-word-region
4140  (\"chinese\" . rfc1843-decode-region))
4141 ")
4142
4143 (defvar gnus-decode-header-methods-cache nil)
4144
4145 (defun gnus-multi-decode-header (start end)
4146   "Apply the functions from `gnus-encoded-word-methods' that match."
4147   (unless (and gnus-decode-header-methods-cache
4148                (eq gnus-newsgroup-name
4149                    (car gnus-decode-header-methods-cache)))
4150     (setq gnus-decode-header-methods-cache (list gnus-newsgroup-name))
4151     (mapc '(lambda (x)
4152              (if (symbolp x)
4153                  (nconc gnus-decode-header-methods-cache (list x))
4154                (if (and gnus-newsgroup-name
4155                         (string-match (car x) gnus-newsgroup-name))
4156                    (nconc gnus-decode-header-methods-cache
4157                           (list (cdr x))))))
4158           gnus-decode-header-methods))
4159   (let ((xlist gnus-decode-header-methods-cache))
4160     (pop xlist)
4161     (save-restriction
4162       (narrow-to-region start end)
4163       (while xlist
4164         (funcall (pop xlist) (point-min) (point-max))))))
4165
4166 ;;;
4167 ;;; Treatment top-level handling.
4168 ;;;
4169
4170 (defun gnus-treat-article (condition &optional part-number total-parts type)
4171   (let ((length (- (point-max) (point-min)))
4172         (alist gnus-treatment-function-alist)
4173         (article-goto-body-goes-to-point-min-p t)
4174         (treated-type
4175          (or (not type)
4176              (catch 'found
4177                (let ((list gnus-article-treat-types))
4178                  (while list
4179                    (when (string-match (pop list) type)
4180                      (throw 'found t)))))))
4181         val elem)
4182     (when (gnus-visual-p 'article-highlight 'highlight)
4183       (gnus-run-hooks 'gnus-part-display-hook)
4184       (while (setq elem (pop alist))
4185         (setq val (symbol-value (car elem)))
4186         (when (and (or (consp val)
4187                        treated-type)
4188                    (gnus-treat-predicate val))
4189           (funcall (cadr elem)))))))
4190
4191 ;; Dynamic variables.
4192 (defvar part-number)
4193 (defvar total-parts)
4194 (defvar type)
4195 (defvar condition)
4196 (defvar length)
4197 (defun gnus-treat-predicate (val)
4198   (cond
4199    (condition
4200     (eq condition val))
4201    ((null val)
4202     nil)
4203    ((eq val t)
4204     t)
4205    ((eq val 'head)
4206     nil)
4207    ((eq val 'last)
4208     (eq part-number total-parts))
4209    ((numberp val)
4210     (< length val))
4211    ((listp val)
4212     (let ((pred (pop val)))
4213       (cond
4214        ((eq pred 'or)
4215         (apply 'gnus-or (mapcar 'gnus-treat-predicate val)))
4216        ((eq pred 'and)
4217         (apply 'gnus-and (mapcar 'gnus-tread-predicate val)))
4218        ((eq pred 'not)
4219         (not (gnus-treat-predicate val)))
4220        ((eq pred 'typep)
4221         (equal (cadr val) type))
4222        (t
4223         (error "%S is not a valid predicate" pred)))))
4224    (t
4225     (error "%S is not a valid value" val))))
4226
4227 (gnus-ems-redefine)
4228
4229 (provide 'gnus-art)
4230
4231 (run-hooks 'gnus-art-load-hook)
4232
4233 ;;; gnus-art.el ends here