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