Synch to Gnus 200308290458.
[elisp/gnus.git-] / lisp / gnus-art.el
1 ;;; gnus-art.el --- article mode commands for Semi-gnus
2 ;; Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003
3 ;;        Free Software Foundation, Inc.
4
5 ;; Author: Lars Magne Ingebrigtsen <larsi@gnus.org>
6 ;;      MORIOKA Tomohiko <morioka@jaist.ac.jp>
7 ;;      Katsumi Yamaoka <yamaoka@jpl.org>
8 ;; Keywords: mail, news, MIME
9
10 ;; This file is part of GNU Emacs.
11
12 ;; GNU Emacs is free software; you can redistribute it and/or modify
13 ;; it under the terms of the GNU General Public License as published by
14 ;; the Free Software Foundation; either version 2, or (at your option)
15 ;; any later version.
16
17 ;; GNU Emacs is distributed in the hope that it will be useful,
18 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
19 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20 ;; GNU General Public License for more details.
21
22 ;; You should have received a copy of the GNU General Public License
23 ;; along with GNU Emacs; see the file COPYING.  If not, write to the
24 ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
25 ;; Boston, MA 02111-1307, USA.
26
27 ;;; Commentary:
28
29 ;;; Code:
30
31 (eval-when-compile
32   (require 'cl)
33   (require 'static)
34   (defvar tool-bar-map))
35
36 (require 'path-util)
37 (require 'gnus)
38 (require 'gnus-sum)
39 (require 'gnus-spec)
40 (require 'gnus-int)
41 (require 'gnus-win)
42 (require 'alist)
43 (require 'mime-view)
44 (require 'wid-edit)
45
46 ;; Avoid byte-compile warnings.
47 (eval-when-compile
48   (require 'mm-bodies)
49   (require 'mail-parse)
50   (require 'mm-decode)
51   (require 'mm-view)
52   (require 'mm-uu))
53
54 (require 'message)
55
56 (autoload 'gnus-msg-mail "gnus-msg" nil t)
57 (autoload 'gnus-button-mailto "gnus-msg")
58 (autoload 'gnus-button-reply "gnus-msg" nil t)
59
60 (defgroup gnus-article nil
61   "Article display."
62   :link '(custom-manual "(gnus)The Article Buffer")
63   :group 'gnus)
64
65 (defgroup gnus-article-treat nil
66   "Treating article parts."
67   :link '(custom-manual "(gnus)Article Hiding")
68   :group 'gnus-article)
69
70 (defgroup gnus-article-hiding nil
71   "Hiding article parts."
72   :link '(custom-manual "(gnus)Article Hiding")
73   :group 'gnus-article)
74
75 (defgroup gnus-article-highlight nil
76   "Article highlighting."
77   :link '(custom-manual "(gnus)Article Highlighting")
78   :group 'gnus-article
79   :group 'gnus-visual)
80
81 (defgroup gnus-article-signature nil
82   "Article signatures."
83   :link '(custom-manual "(gnus)Article Signature")
84   :group 'gnus-article)
85
86 (defgroup gnus-article-headers nil
87   "Article headers."
88   :link '(custom-manual "(gnus)Hiding Headers")
89   :group 'gnus-article)
90
91 (defgroup gnus-article-washing nil
92   "Special commands on articles."
93   :link '(custom-manual "(gnus)Article Washing")
94   :group 'gnus-article)
95
96 (defgroup gnus-article-emphasis nil
97   "Fontisizing articles."
98   :link '(custom-manual "(gnus)Article Fontisizing")
99   :group 'gnus-article)
100
101 (defgroup gnus-article-saving nil
102   "Saving articles."
103   :link '(custom-manual "(gnus)Saving Articles")
104   :group 'gnus-article)
105
106 (defgroup gnus-article-mime nil
107   "Worshiping the MIME wonder."
108   :link '(custom-manual "(gnus)Using MIME")
109   :group 'gnus-article)
110
111 (defgroup gnus-article-buttons nil
112   "Pushable buttons in the article buffer."
113   :link '(custom-manual "(gnus)Article Buttons")
114   :group 'gnus-article)
115
116 (defgroup gnus-article-various nil
117   "Other article options."
118   :link '(custom-manual "(gnus)Misc Article")
119   :group 'gnus-article)
120
121 (defcustom gnus-ignored-headers
122   (mapcar
123    (lambda (header)
124      (concat "^" header ":"))
125    '("Path" "Expires" "Date-Received" "References" "Xref" "Lines"
126      "Relay-Version" "Message-ID" "Approved" "Sender" "Received"
127      "X-UIDL" "MIME-Version" "Return-Path" "In-Reply-To"
128      "Content-Type" "Content-Transfer-Encoding" "X-WebTV-Signature"
129      "X-MimeOLE" "X-MSMail-Priority" "X-Priority" "X-Loop"
130      "X-Authentication-Warning" "X-MIME-Autoconverted" "X-Face"
131      "X-Attribution" "X-Originating-IP" "Delivered-To"
132      "NNTP-[-A-Za-z]+" "Distribution" "X-no-archive" "X-Trace"
133      "X-Complaints-To" "X-NNTP-Posting-Host" "X-Orig.*"
134      "Abuse-Reports-To" "Cache-Post-Path" "X-Article-Creation-Date"
135      "X-Poster" "X-Mail2News-Path" "X-Server-Date" "X-Cache"
136      "Originator" "X-Problems-To" "X-Auth-User" "X-Post-Time"
137      "X-Admin" "X-UID" "Resent-[-A-Za-z]+" "X-Mailing-List"
138      "Precedence" "Original-[-A-Za-z]+" "X-filename" "X-Orcpt"
139      "Old-Received" "X-Pgp" "X-Auth" "X-From-Line"
140      "X-Gnus-Article-Number" "X-Majordomo" "X-Url" "X-Sender"
141      "MBOX-Line" "Priority" "X400-[-A-Za-z]+"
142      "Status" "X-Gnus-Mail-Source" "Cancel-Lock"
143      "X-FTN" "X-EXP32-SerialNo" "Encoding" "Importance"
144      "Autoforwarded" "Original-Encoded-Information-Types" "X-Ya-Pop3"
145      "X-Face-Version" "X-Vms-To" "X-ML-NAME" "X-ML-COUNT"
146      "Mailing-List" "X-finfo" "X-md5sum" "X-md5sum-Origin"
147      "X-Sun-Charset" "X-Accept-Language" "X-Envelope-Sender"
148      "List-[A-Za-z]+" "X-Listprocessor-Version"
149      "X-Received" "X-Distribute" "X-Sequence" "X-Juno-Line-Breaks"
150      "X-Notes-Item" "X-MS-TNEF-Correlator" "x-uunet-gateway"
151      "X-Received" "Content-length" "X-precedence"
152      "X-Authenticated-User" "X-Comment" "X-Report" "X-Abuse-Info"
153      "X-HTTP-Proxy" "X-Mydeja-Info" "X-Copyright" "X-No-Markup"
154      "X-Abuse-Info" "X-From_" "X-Accept-Language" "Errors-To"
155      "X-BeenThere" "X-Mailman-Version" "List-Help" "List-Post"
156      "List-Subscribe" "List-Id" "List-Unsubscribe" "List-Archive"
157      "X-Content-length" "X-Posting-Agent" "Original-Received"
158      "X-Request-PGP" "X-Fingerprint" "X-WRIEnvto" "X-WRIEnvfrom"
159      "X-Virus-Scanned" "X-Delivery-Agent" "Posted-Date" "X-Gateway"
160      "X-Local-Origin" "X-Local-Destination" "X-UserInfo1"
161      "X-Received-Date" "X-Hashcash" "Face" "X-DMCA-Notifications"
162      "X-Abuse-and-DMCA-Info" "X-Postfilter"))
163   "*All headers that start with this regexp will be hidden.
164 This variable can also be a list of regexps of headers to be ignored.
165 If `gnus-visible-headers' is non-nil, this variable will be ignored."
166   :type '(choice :custom-show nil
167                  regexp
168                  (repeat regexp))
169   :group 'gnus-article-hiding)
170
171 (defcustom gnus-visible-headers
172   "^From:\\|^Newsgroups:\\|^Subject:\\|^Date:\\|^Followup-To:\\|^Reply-To:\\|^Organization:\\|^Summary:\\|^Keywords:\\|^To:\\|^[BGF]?Cc:\\|^Posted-To:\\|^Mail-Copies-To:\\|^Mail-Followup-To:\\|^Apparently-To:\\|^Gnus-Warning:\\|^Resent-From:\\|^X-Sent:"
173   "*All headers that do not match this regexp will be hidden.
174 This variable can also be a list of regexp of headers to remain visible.
175 If this variable is non-nil, `gnus-ignored-headers' will be ignored."
176   :type '(repeat :value-to-internal (lambda (widget value)
177                                       (custom-split-regexp-maybe value))
178                  :match (lambda (widget value)
179                           (or (stringp value)
180                               (widget-editable-list-match widget value)))
181                  regexp)
182   :group 'gnus-article-hiding)
183
184 (defcustom gnus-sorted-header-list
185   '("^From:" "^Subject:" "^Summary:" "^Keywords:" "^Newsgroups:"
186     "^Followup-To:" "^To:" "^Cc:" "^Date:" "^Organization:")
187   "*This variable is a list of regular expressions.
188 If it is non-nil, headers that match the regular expressions will
189 be placed first in the article buffer in the sequence specified by
190 this list."
191   :type '(repeat regexp)
192   :group 'gnus-article-hiding)
193
194 (defcustom gnus-boring-article-headers '(empty followup-to reply-to)
195   "Headers that are only to be displayed if they have interesting data.
196 Possible values in this list are:
197
198   'empty       Headers with no content.
199   'newsgroups  Newsgroup identical to Gnus group.
200   'to-address  To identical to To-address.
201   'to-list     To identical to To-list.
202   'cc-list     CC identical to To-list.
203   'followup-to Followup-to identical to Newsgroups.
204   'reply-to    Reply-to identical to From.
205   'date        Date less than four days old.
206   'long-to     To and/or Cc longer than 1024 characters.
207   'many-to     Multiple To and/or Cc."
208   :type '(set (const :tag "Headers with no content." empty)
209               (const :tag "Newsgroups identical to Gnus group." newsgroups)
210               (const :tag "To identical to To-address." to-address)
211               (const :tag "To identical to To-list." to-list)
212               (const :tag "CC identical to To-list." cc-list)
213               (const :tag "Followup-to identical to Newsgroups." followup-to)
214               (const :tag "Reply-to identical to From." reply-to)
215               (const :tag "Date less than four days old." date)
216               (const :tag "To and/or Cc longer than 1024 characters." long-to)
217               (const :tag "Multiple To and/or Cc headers." many-to))
218   :group 'gnus-article-hiding)
219
220 (defcustom gnus-article-skip-boring nil
221   "Skip over text that is not worth reading.
222 By default, if you set this t, then Gnus will display citations and
223 signatures, but will never scroll down to show you a page consisting
224 only of boring text.  Boring text is controlled by
225 `gnus-article-boring-faces'."
226   :type 'boolean
227   :group 'gnus-article-hiding)
228
229 (defcustom gnus-signature-separator '("^-- $" "^-- *$")
230   "Regexp matching signature separator.
231 This can also be a list of regexps.  In that case, it will be checked
232 from head to tail looking for a separator.  Searches will be done from
233 the end of the buffer."
234   :type '(repeat string)
235   :group 'gnus-article-signature)
236
237 (defcustom gnus-signature-limit nil
238   "Provide a limit to what is considered a signature.
239 If it is a number, no signature may not be longer (in characters) than
240 that number.  If it is a floating point number, no signature may be
241 longer (in lines) than that number.  If it is a function, the function
242 will be called without any parameters, and if it returns nil, there is
243 no signature in the buffer.  If it is a string, it will be used as a
244 regexp.  If it matches, the text in question is not a signature."
245   :type '(choice (integer :value 200)
246                  (number :value 4.0)
247                  (function :value fun)
248                  (regexp :value ".*"))
249   :group 'gnus-article-signature)
250
251 (defcustom gnus-hidden-properties '(invisible t intangible t)
252   "Property list to use for hiding text."
253   :type 'sexp
254   :group 'gnus-article-hiding)
255
256 ;; Fixme: This isn't the right thing for mixed graphical and non-graphical
257 ;; frames in a session.
258 (defcustom gnus-article-x-face-command
259   (cond
260    (noninteractive
261     'ignore)
262    ((featurep 'xemacs)
263     (if (or (gnus-image-type-available-p 'xface)
264             (gnus-image-type-available-p 'pbm))
265         'gnus-display-x-face-in-from
266       "{ echo '/* Width=48, Height=48 */'; uncompface; } | icontopbm | ee -"))
267    ((and (fboundp 'image-type-available-p)
268          (module-installed-p 'x-face-e21))
269     'x-face-decode-message-header)
270    ((gnus-image-type-available-p 'pbm)
271     'gnus-display-x-face-in-from)
272    ((and window-system
273          (module-installed-p 'x-face-mule))
274     'x-face-mule-gnus-article-display-x-face)
275    (t
276     "{ echo '/* Width=48, Height=48 */'; uncompface; } | icontopbm | \
277 display -"))
278   "*String or function to be executed to display an X-Face header.
279 If it is a string, the command will be executed in a sub-shell
280 asynchronously.  The compressed face will be piped to this command."
281   :type `(choice
282           ,@(let (x-face-e21 x-face-mule)
283               (if (featurep 'xemacs)
284                   nil
285                 (setq x-face-e21 (module-installed-p 'x-face-e21)
286                       x-face-mule (module-installed-p 'x-face-mule)))
287               (delq nil
288                     (list
289                      'string
290                      (if (or (gnus-image-type-available-p 'xface)
291                              (gnus-image-type-available-p 'pbm))
292                          '(function-item gnus-display-x-face-in-from))
293                      (if (and x-face-e21
294                               (fboundp 'image-type-available-p))
295                          '(function-item
296                            :tag "x-face-decode-message-header (x-face-e21)"
297                            x-face-decode-message-header))
298                      (if x-face-mule
299                          '(function-item
300                            x-face-mule-gnus-article-display-x-face))
301                      'function))))
302   ;;:version "21.1"
303   :group 'gnus-picon
304   :group 'gnus-article-washing)
305
306 (defcustom gnus-article-x-face-too-ugly nil
307   "Regexp matching posters whose face shouldn't be shown automatically."
308   :type '(choice regexp (const nil))
309   :group 'gnus-article-washing)
310
311 (defcustom gnus-article-banner-alist nil
312   "Banner alist for stripping.
313 For example,
314      ((egroups . \"^[ \\t\\n]*-------------------+\\\\( \\\\(e\\\\|Yahoo! \\\\)Groups Sponsor -+\\\\)?....\\n\\\\(.+\\n\\\\)+\"))"
315   :version "21.1"
316   :type '(repeat (cons symbol regexp))
317   :group 'gnus-article-washing)
318
319 (gnus-define-group-parameter
320  banner
321  :variable-document
322  "Alist of regexps (to match group names) and banner."
323  :variable-group gnus-article-washing
324  :parameter-type
325  '(choice :tag "Banner"
326           :value nil
327           (const :tag "Remove signature" signature)
328           (symbol :tag "Item in `gnus-article-banner-alist'" none)
329           regexp
330           (const :tag "None" nil))
331  :parameter-document
332  "If non-nil, specify how to remove `banners' from articles.
333
334 Symbol `signature' means to remove signatures delimited by
335 `gnus-signature-separator'.  Any other symbol is used to look up a
336 regular expression to match the banner in `gnus-article-banner-alist'.
337 A string is used as a regular expression to match the banner
338 directly.")
339
340 (defcustom gnus-article-address-banner-alist nil
341   "Alist of mail addresses and banners.
342 Each element has the form (ADDRESS . BANNER), where ADDRESS is a regexp
343 to match a mail address in the From: header, BANNER is one of a symbol
344 `signature', an item in `gnus-article-banner-alist', a regexp and nil.
345 If ADDRESS matches author's mail address, it will remove things like
346 advertisements.  For example:
347
348 \((\"@yoo-hoo\\\\.co\\\\.jp\\\\'\" . \"\\n_+\\nDo You Yoo-hoo!\\\\?\\n.*\\n.*\\n\"))
349 "
350   :type '(repeat
351           (cons
352            (regexp :tag "Address")
353            (choice :tag "Banner" :value nil
354                    (const :tag "Remove signature" signature)
355                    (symbol :tag "Item in `gnus-article-banner-alist'" none)
356                    regexp
357                    (const :tag "None" nil))))
358   :group 'gnus-article-washing)
359
360 (defcustom gnus-emphasis-alist
361   (let ((format
362          "\\(\\s-\\|^\\|\\=\\|[-\"]\\|\\s(\\)\\(%s\\(\\w+\\(\\s-+\\w+\\)*[.,]?\\)%s\\)\\(\\([-,.;:!?\"]\\|\\s)\\)+\\s-\\|[?!.]\\s-\\|\\s)\\|\\s-\\)")
363         (types
364          '(("\\*" "\\*" bold)
365            ("_" "_" underline)
366            ("/" "/" italic)
367            ("_/" "/_" underline-italic)
368            ("_\\*" "\\*_" underline-bold)
369            ("\\*/" "/\\*" bold-italic)
370            ("_\\*/" "/\\*_" underline-bold-italic))))
371     `(,@(mapcar
372          (lambda (spec)
373            (list
374             (format format (car spec) (car (cdr spec)))
375             2 3 (intern (format "gnus-emphasis-%s" (nth 2 spec)))))
376          types)
377         ("\\(\\s-\\|^\\)\\(-\\(\\(\\w\\|-[^-]\\)+\\)-\\)\\(\\s-\\|[?!.,;]\\)"
378          2 3 gnus-emphasis-strikethru)
379         ("\\(\\s-\\|^\\)\\(_\\(\\(\\w\\|_[^_]\\)+\\)_\\)\\(\\s-\\|[?!.,;]\\)"
380          2 3 gnus-emphasis-underline)))
381   "*Alist that says how to fontify certain phrases.
382 Each item looks like this:
383
384   (\"_\\\\(\\\\w+\\\\)_\" 0 1 'underline)
385
386 The first element is a regular expression to be matched.  The second
387 is a number that says what regular expression grouping used to find
388 the entire emphasized word.  The third is a number that says what
389 regexp grouping should be displayed and highlighted.  The fourth
390 is the face used for highlighting."
391   :type '(repeat (list :value ("" 0 0 default)
392                        regexp
393                        (integer :tag "Match group")
394                        (integer :tag "Emphasize group")
395                        face))
396   :group 'gnus-article-emphasis)
397
398 (defcustom gnus-emphasize-whitespace-regexp "^[ \t]+\\|[ \t]*\n"
399   "A regexp to describe whitespace which should not be emphasized.
400 Typical values are \"^[ \\t]+\\\\|[ \\t]*\\n\" and \"[ \\t]+\\\\|[ \\t]*\\n\".
401 The former avoids underlining of leading and trailing whitespace,
402 and the latter avoids underlining any whitespace at all."
403   :version "21.1"
404   :group 'gnus-article-emphasis
405   :type 'regexp)
406
407 (defface gnus-emphasis-bold '((t (:bold t)))
408   "Face used for displaying strong emphasized text (*word*)."
409   :group 'gnus-article-emphasis)
410
411 (defface gnus-emphasis-italic '((t (:italic t)))
412   "Face used for displaying italic emphasized text (/word/)."
413   :group 'gnus-article-emphasis)
414
415 (defface gnus-emphasis-underline '((t (:underline t)))
416   "Face used for displaying underlined emphasized text (_word_)."
417   :group 'gnus-article-emphasis)
418
419 (defface gnus-emphasis-underline-bold '((t (:bold t :underline t)))
420   "Face used for displaying underlined bold emphasized text (_*word*_)."
421   :group 'gnus-article-emphasis)
422
423 (defface gnus-emphasis-underline-italic '((t (:italic t :underline t)))
424   "Face used for displaying underlined italic emphasized text (_/word/_)."
425   :group 'gnus-article-emphasis)
426
427 (defface gnus-emphasis-bold-italic '((t (:bold t :italic t)))
428   "Face used for displaying bold italic emphasized text (/*word*/)."
429   :group 'gnus-article-emphasis)
430
431 (defface gnus-emphasis-underline-bold-italic
432   '((t (:bold t :italic t :underline t)))
433   "Face used for displaying underlined bold italic emphasized text.
434 Example: (_/*word*/_)."
435   :group 'gnus-article-emphasis)
436
437 (defface gnus-emphasis-strikethru '((t (:strikethru t)))
438   "Face used for displaying strike-through text (-word-)."
439   :group 'gnus-article-emphasis)
440
441 (defface gnus-emphasis-highlight-words
442   '((t (:background "black" :foreground "yellow")))
443   "Face used for displaying highlighted words."
444   :group 'gnus-article-emphasis)
445
446 (defcustom gnus-article-time-format "%a, %b %d %Y %T %Z"
447   "Format for display of Date headers in article bodies.
448 See `format-time-string' for the possible values.
449
450 The variable can also be function, which should return a complete Date
451 header.  The function is called with one argument, the time, which can
452 be fed to `format-time-string'."
453   :type '(choice string symbol)
454   :link '(custom-manual "(gnus)Article Date")
455   :group 'gnus-article-washing)
456
457 (eval-and-compile
458   (autoload 'mail-extract-address-components "mail-extr"))
459
460 (defcustom gnus-save-all-headers t
461   "*If non-nil, don't remove any headers before saving."
462   :group 'gnus-article-saving
463   :type 'boolean)
464
465 (defcustom gnus-prompt-before-saving 'always
466   "*This variable says how much prompting is to be done when saving articles.
467 If it is nil, no prompting will be done, and the articles will be
468 saved to the default files.  If this variable is `always', each and
469 every article that is saved will be preceded by a prompt, even when
470 saving large batches of articles.  If this variable is neither nil not
471 `always', there the user will be prompted once for a file name for
472 each invocation of the saving commands."
473   :group 'gnus-article-saving
474   :type '(choice (item always)
475                  (item :tag "never" nil)
476                  (sexp :tag "once" :format "%t\n" :value t)))
477
478 (defcustom gnus-saved-headers gnus-visible-headers
479   "Headers to keep if `gnus-save-all-headers' is nil.
480 If `gnus-save-all-headers' is non-nil, this variable will be ignored.
481 If that variable is nil, however, all headers that match this regexp
482 will be kept while the rest will be deleted before saving."
483   :group 'gnus-article-saving
484   :type 'regexp)
485
486 (defcustom gnus-default-article-saver 'gnus-summary-save-in-rmail
487   "A function to save articles in your favourite format.
488 The function must be interactively callable (in other words, it must
489 be an Emacs command).
490
491 Gnus provides the following functions:
492
493 * gnus-summary-save-in-rmail (Rmail format)
494 * gnus-summary-save-in-mail (Unix mail format)
495 * gnus-summary-save-in-folder (MH folder)
496 * gnus-summary-save-in-file (article format)
497 * gnus-summary-save-body-in-file (article body)
498 * gnus-summary-save-in-vm (use VM's folder format)
499 * gnus-summary-write-to-file (article format -- overwrite)."
500   :group 'gnus-article-saving
501   :type '(radio (function-item gnus-summary-save-in-rmail)
502                 (function-item gnus-summary-save-in-mail)
503                 (function-item gnus-summary-save-in-folder)
504                 (function-item gnus-summary-save-in-file)
505                 (function-item gnus-summary-save-body-in-file)
506                 (function-item gnus-summary-save-in-vm)
507                 (function-item gnus-summary-write-to-file)))
508
509 (defcustom gnus-rmail-save-name 'gnus-plain-save-name
510   "A function generating a file name to save articles in Rmail format.
511 The function is called with NEWSGROUP, HEADERS, and optional LAST-FILE."
512   :group 'gnus-article-saving
513   :type 'function)
514
515 (defcustom gnus-mail-save-name 'gnus-plain-save-name
516   "A function generating a file name to save articles in Unix mail format.
517 The function is called with NEWSGROUP, HEADERS, and optional LAST-FILE."
518   :group 'gnus-article-saving
519   :type 'function)
520
521 (defcustom gnus-folder-save-name 'gnus-folder-save-name
522   "A function generating a file name to save articles in MH folder.
523 The function is called with NEWSGROUP, HEADERS, and optional LAST-FOLDER."
524   :group 'gnus-article-saving
525   :type 'function)
526
527 (defcustom gnus-file-save-name 'gnus-numeric-save-name
528   "A function generating a file name to save articles in article format.
529 The function is called with NEWSGROUP, HEADERS, and optional
530 LAST-FILE."
531   :group 'gnus-article-saving
532   :type 'function)
533
534 (defcustom gnus-split-methods
535   '((gnus-article-archive-name)
536     (gnus-article-nndoc-name))
537   "*Variable used to suggest where articles are to be saved.
538 For instance, if you would like to save articles related to Gnus in
539 the file \"gnus-stuff\", and articles related to VM in \"vm-stuff\",
540 you could set this variable to something like:
541
542  '((\"^Subject:.*gnus\\|^Newsgroups:.*gnus\" \"gnus-stuff\")
543    (\"^Subject:.*vm\\|^Xref:.*vm\" \"vm-stuff\"))
544
545 This variable is an alist where the where the key is the match and the
546 value is a list of possible files to save in if the match is non-nil.
547
548 If the match is a string, it is used as a regexp match on the
549 article.  If the match is a symbol, that symbol will be funcalled
550 from the buffer of the article to be saved with the newsgroup as the
551 parameter.  If it is a list, it will be evaled in the same buffer.
552
553 If this form or function returns a string, this string will be used as
554 a possible file name; and if it returns a non-nil list, that list will
555 be used as possible file names."
556   :group 'gnus-article-saving
557   :type '(repeat (choice (list :value (fun) function)
558                          (cons :value ("" "") regexp (repeat string))
559                          (sexp :value nil))))
560
561 (defcustom gnus-article-display-method-for-mime
562   'gnus-article-display-mime-message
563   "Function to display a MIME message.
564 The function is called from the article buffer."
565   :group 'gnus-article-mime
566   :type 'function)
567
568 (defcustom gnus-article-display-method-for-traditional
569   'gnus-article-display-traditional-message
570   "*Function to display a traditional message.
571 The function is called from the article buffer."
572   :group 'gnus-article-mime
573   :type 'function)
574
575 (defcustom gnus-page-delimiter "^\^L"
576   "*Regexp describing what to use as article page delimiters.
577 The default value is \"^\^L\", which is a form linefeed at the
578 beginning of a line."
579   :type 'regexp
580   :group 'gnus-article-various)
581
582 (defcustom gnus-article-mode-line-format "Gnus: %g [%w] %S%m"
583   "*The format specification for the article mode line.
584 See `gnus-summary-mode-line-format' for a closer description.
585
586 The following additional specs are available:
587
588 %w  The article washing status.
589 %m  The number of MIME parts in the article."
590   :type 'string
591   :group 'gnus-article-various)
592
593 (defcustom gnus-article-mode-hook nil
594   "*A hook for Gnus article mode."
595   :type 'hook
596   :group 'gnus-article-various)
597
598 (when (featurep 'xemacs)
599   ;; Extracted from gnus-xmas-define in order to preserve user settings
600   (when (fboundp 'turn-off-scroll-in-place)
601     (add-hook 'gnus-article-mode-hook 'turn-off-scroll-in-place))
602   ;; Extracted from gnus-xmas-redefine in order to preserve user settings
603   (add-hook 'gnus-article-mode-hook 'gnus-xmas-article-menu-add))
604
605 (defcustom gnus-article-menu-hook nil
606   "*Hook run after the creation of the article mode menu."
607   :type 'hook
608   :group 'gnus-article-various)
609
610 (defcustom gnus-article-prepare-hook nil
611   "*A hook called after an article has been prepared in the article buffer."
612   :type 'hook
613   :group 'gnus-article-various)
614
615 (make-obsolete-variable 'gnus-article-hide-pgp-hook
616                         "This variable is obsolete in Gnus 5.10.")
617
618 (defcustom gnus-article-button-face 'bold
619   "Face used for highlighting buttons in the article buffer.
620
621 An article button is a piece of text that you can activate by pressing
622 `RET' or `mouse-2' above it."
623   :type 'face
624   :group 'gnus-article-buttons)
625
626 (defcustom gnus-article-mouse-face 'highlight
627   "Face used for mouse highlighting in the article buffer.
628
629 Article buttons will be displayed in this face when the cursor is
630 above them."
631   :type 'face
632   :group 'gnus-article-buttons)
633
634 (defcustom gnus-signature-face 'gnus-signature-face
635   "Face used for highlighting a signature in the article buffer.
636 Obsolete; use the face `gnus-signature-face' for customizations instead."
637   :type 'face
638   :group 'gnus-article-highlight
639   :group 'gnus-article-signature)
640
641 (defface gnus-signature-face
642   '((t
643      (:italic t)))
644   "Face used for highlighting a signature in the article buffer."
645   :group 'gnus-article-highlight
646   :group 'gnus-article-signature)
647
648 (defface gnus-header-from-face
649   '((((class color)
650       (background dark))
651      (:foreground "spring green"))
652     (((class color)
653       (background light))
654      (:foreground "red3"))
655     (t
656      (:italic t)))
657   "Face used for displaying from headers."
658   :group 'gnus-article-headers
659   :group 'gnus-article-highlight)
660
661 (defface gnus-header-subject-face
662   '((((class color)
663       (background dark))
664      (:foreground "SeaGreen3"))
665     (((class color)
666       (background light))
667      (:foreground "red4"))
668     (t
669      (:bold t :italic t)))
670   "Face used for displaying subject headers."
671   :group 'gnus-article-headers
672   :group 'gnus-article-highlight)
673
674 (defface gnus-header-newsgroups-face
675   '((((class color)
676       (background dark))
677      (:foreground "yellow" :italic t))
678     (((class color)
679       (background light))
680      (:foreground "MidnightBlue" :italic t))
681     (t
682      (:italic t)))
683   "Face used for displaying newsgroups headers.
684 In the default setup this face is only used for crossposted
685 articles."
686   :group 'gnus-article-headers
687   :group 'gnus-article-highlight)
688
689 (defface gnus-header-name-face
690   '((((class color)
691       (background dark))
692      (:foreground "SeaGreen"))
693     (((class color)
694       (background light))
695      (:foreground "maroon"))
696     (t
697      (:bold t)))
698   "Face used for displaying header names."
699   :group 'gnus-article-headers
700   :group 'gnus-article-highlight)
701
702 (defface gnus-header-content-face
703   '((((class color)
704       (background dark))
705      (:foreground "forest green" :italic t))
706     (((class color)
707       (background light))
708      (:foreground "indianred4" :italic t))
709     (t
710      (:italic t)))
711   "Face used for displaying header content."
712   :group 'gnus-article-headers
713   :group 'gnus-article-highlight)
714
715 (defcustom gnus-header-face-alist
716   '(("From" nil gnus-header-from-face)
717     ("Subject" nil gnus-header-subject-face)
718     ("Newsgroups:.*," nil gnus-header-newsgroups-face)
719     ("" gnus-header-name-face gnus-header-content-face))
720   "*Controls highlighting of article headers.
721
722 An alist of the form (HEADER NAME CONTENT).
723
724 HEADER is a regular expression which should match the name of a
725 header and NAME and CONTENT are either face names or nil.
726
727 The name of each header field will be displayed using the face
728 specified by the first element in the list where HEADER matches
729 the header name and NAME is non-nil.  Similarly, the content will
730 be displayed by the first non-nil matching CONTENT face."
731   :group 'gnus-article-headers
732   :group 'gnus-article-highlight
733   :type '(repeat (list (regexp :tag "Header")
734                        (choice :tag "Name"
735                                (item :tag "skip" nil)
736                                (face :value default))
737                        (choice :tag "Content"
738                                (item :tag "skip" nil)
739                                (face :value default)))))
740
741 (defcustom gnus-article-decode-hook
742   '(article-decode-group-name article-decode-idna-rhs)
743   "*Hook run to decode charsets in articles."
744   :group 'gnus-article-headers
745   :type 'hook)
746
747 (defcustom gnus-display-mime-function 'gnus-display-mime
748   "Function to display MIME articles."
749   :group 'gnus-article-mime
750   :type 'function)
751
752 (defvar gnus-decode-header-function 'mail-decode-encoded-word-region
753   "Function used to decode headers.")
754
755 (defvar gnus-article-dumbquotes-map
756   '(("\200" "EUR")
757     ("\202" ",")
758     ("\203" "f")
759     ("\204" ",,")
760     ("\205" "...")
761     ("\213" "<")
762     ("\214" "OE")
763     ("\221" "`")
764     ("\222" "'")
765     ("\223" "``")
766     ("\224" "\"")
767     ("\225" "*")
768     ("\226" "-")
769     ("\227" "--")
770     ("\230" "~")
771     ("\231" "(TM)")
772     ("\233" ">")
773     ("\234" "oe")
774     ("\264" "'"))
775   "Table for MS-to-Latin1 translation.")
776
777 (defcustom gnus-ignored-mime-types nil
778   "List of MIME types that should be ignored by Gnus."
779   :version "21.1"
780   :group 'gnus-article-mime
781   :type '(repeat regexp))
782
783 (defcustom gnus-unbuttonized-mime-types '(".*/.*")
784   "List of MIME types that should not be given buttons when rendered inline.
785 See also `gnus-buttonized-mime-types' which may override this variable.
786 This variable is only used when `gnus-inhibit-mime-unbuttonizing' is nil."
787   :version "21.1"
788   :group 'gnus-article-mime
789   :type '(repeat regexp))
790
791 (defcustom gnus-buttonized-mime-types nil
792   "List of MIME types that should be given buttons when rendered inline.
793 If set, this variable overrides `gnus-unbuttonized-mime-types'.
794 To see e.g. security buttons you could set this to
795 `(\"multipart/signed\")'.
796 This variable is only used when `gnus-inhibit-mime-unbuttonizing' is nil."
797   :version "21.1"
798   :group 'gnus-article-mime
799   :type '(repeat regexp))
800
801 (defcustom gnus-inhibit-mime-unbuttonizing nil
802   "If non-nil, all MIME parts get buttons.
803 When nil (the default value), then some MIME parts do not get buttons,
804 as described by the variables `gnus-buttonized-mime-types' and
805 `gnus-unbuttonized-mime-types'."
806   :version "21.3"
807   :type 'boolean)
808
809 (defcustom gnus-body-boundary-delimiter "_"
810   "String used to delimit header and body.
811 This variable is used by `gnus-article-treat-body-boundary' which can
812 be controlled by `gnus-treat-body-boundary'."
813   :group 'gnus-article-various
814   :type '(choice (item :tag "None" :value nil)
815                  string))
816
817 (defcustom gnus-picon-databases '("/usr/lib/picon" "/usr/local/faces")
818   "Defines the location of the faces database.
819 For information on obtaining this database of pretty pictures, please
820 see http://www.cs.indiana.edu/picons/ftp/index.html"
821   :type '(repeat directory)
822   :link '(url-link :tag "download"
823                    "http://www.cs.indiana.edu/picons/ftp/index.html")
824   :link '(custom-manual "(gnus)Picons")
825   :group 'gnus-picon)
826
827 (defun gnus-picons-installed-p ()
828   "Say whether picons are installed on your machine."
829   (let ((installed nil))
830     (dolist (database gnus-picon-databases)
831       (when (file-exists-p database)
832         (setq installed t)))
833     installed))
834
835 (defcustom gnus-article-mime-part-function nil
836   "Function called with a MIME handle as the argument.
837 This is meant for people who want to do something automatic based
838 on parts -- for instance, adding Vcard info to a database."
839   :group 'gnus-article-mime
840   :type 'function)
841
842 (defcustom gnus-mime-multipart-functions nil
843   "An alist of MIME types to functions to display them."
844   :version "21.1"
845   :group 'gnus-article-mime
846   :type 'alist)
847
848 (defcustom gnus-article-date-lapsed-new-header nil
849   "Whether the X-Sent and Date headers can coexist.
850 When using `gnus-treat-date-lapsed', the \"X-Sent:\" header will
851 either replace the old \"Date:\" header (if this variable is nil), or
852 be added below it (otherwise)."
853   :version "21.1"
854   :group 'gnus-article-headers
855   :type 'boolean)
856
857 (defcustom gnus-article-mime-match-handle-function 'undisplayed-alternative
858   "Function called with a MIME handle as the argument.
859 This is meant for people who want to view first matched part.
860 For `undisplayed-alternative' (default), the first undisplayed
861 part or alternative part is used.  For `undisplayed', the first
862 undisplayed part is used.  For a function, the first part which
863 the function return t is used.  For nil, the first part is
864 used."
865   :version "21.1"
866   :group 'gnus-article-mime
867   :type '(choice
868           (item :tag "first" :value nil)
869           (item :tag "undisplayed" :value undisplayed)
870           (item :tag "undisplayed or alternative"
871                 :value undisplayed-alternative)
872           (function)))
873
874 (defcustom gnus-mime-action-alist
875   '(("save to file" . gnus-mime-save-part)
876     ("save and strip" . gnus-mime-save-part-and-strip)
877     ("delete part" . gnus-mime-delete-part)
878     ("display as text" . gnus-mime-inline-part)
879     ("view the part" . gnus-mime-view-part)
880     ("pipe to command" . gnus-mime-pipe-part)
881     ("toggle display" . gnus-article-press-button)
882     ("toggle display" . gnus-article-view-part-as-charset)
883     ("view as type" . gnus-mime-view-part-as-type)
884     ("view internally" . gnus-mime-view-part-internally)
885     ("view externally" . gnus-mime-view-part-externally))
886   "An alist of actions that run on the MIME attachment."
887   :group 'gnus-article-mime
888   :type '(repeat (cons (string :tag "name")
889                        (function))))
890
891 ;;;
892 ;;; The treatment variables
893 ;;;
894
895 (defvar gnus-part-display-hook nil
896   "Hook called on parts that are to receive treatment.")
897
898 (defvar gnus-article-treat-custom
899   '(choice (const :tag "Off" nil)
900            (const :tag "On" t)
901            (const :tag "Header" head)
902            (const :tag "Last" last)
903            (const :tag "Mime" mime)
904            (integer :tag "Less")
905            (repeat :tag "Groups" regexp)
906            (sexp :tag "Predicate")))
907
908 (defvar gnus-article-treat-head-custom
909   '(choice (const :tag "Off" nil)
910            (const :tag "Header" head)))
911
912 (defvar gnus-article-treat-types '("text/plain")
913   "Parts to treat.")
914
915 (defvar gnus-inhibit-treatment nil
916   "Whether to inhibit treatment.")
917
918 (defcustom gnus-treat-highlight-signature '(or t (typep "text/x-vcard"))
919   "Highlight the signature.
920 Valid values are nil, t, `head', `last', an integer or a predicate.
921 See Info node `(gnus)Customizing Articles'."
922   :group 'gnus-article-treat
923   :link '(custom-manual "(gnus)Customizing Articles")
924   :type gnus-article-treat-custom)
925 (put 'gnus-treat-highlight-signature 'highlight t)
926
927 (defcustom gnus-treat-buttonize 100000
928   "Add buttons.
929 Valid values are nil, t, `head', `last', an integer or a predicate.
930 See Info node `(gnus)Customizing Articles'."
931   :group 'gnus-article-treat
932   :link '(custom-manual "(gnus)Customizing Articles")
933   :type gnus-article-treat-custom)
934 (put 'gnus-treat-buttonize 'highlight t)
935
936 (defcustom gnus-treat-buttonize-head 'head
937   "Add buttons to the head.
938 Valid values are nil, t, `head', `last', an integer or a predicate.
939 See Info node `(gnus)Customizing Articles' for details."
940   :group 'gnus-article-treat
941   :link '(custom-manual "(gnus)Customizing Articles")
942   :type gnus-article-treat-head-custom)
943 (put 'gnus-treat-buttonize-head 'highlight t)
944
945 (defcustom gnus-treat-emphasize
946   (and (or window-system
947            (featurep 'xemacs)
948            (>= (string-to-number emacs-version) 21))
949        50000)
950   "Emphasize text.
951 Valid values are nil, t, `head', `last', an integer or a predicate.
952 See Info node `(gnus)Customizing Articles' for details."
953   :group 'gnus-article-treat
954   :link '(custom-manual "(gnus)Customizing Articles")
955   :type gnus-article-treat-custom)
956 (put 'gnus-treat-emphasize 'highlight t)
957
958 (defcustom gnus-treat-strip-cr nil
959   "Remove carriage returns.
960 Valid values are nil, t, `head', `last', an integer or a predicate.
961 See Info node `(gnus)Customizing Articles' for details."
962   :group 'gnus-article-treat
963   :link '(custom-manual "(gnus)Customizing Articles")
964   :type gnus-article-treat-custom)
965
966 (defcustom gnus-treat-unsplit-urls nil
967   "Remove newlines from within URLs.
968 Valid values are nil, t, `head', `last', an integer or a predicate.
969 See Info node `(gnus)Customizing Articles' for details."
970   :group 'gnus-article-treat
971   :link '(custom-manual "(gnus)Customizing Articles")
972   :type gnus-article-treat-custom)
973
974 (defcustom gnus-treat-leading-whitespace nil
975   "Remove leading whitespace in headers.
976 Valid values are nil, t, `head', `last', an integer or a predicate.
977 See Info node `(gnus)Customizing Articles' for details."
978   :group 'gnus-article-treat
979   :link '(custom-manual "(gnus)Customizing Articles")
980   :type gnus-article-treat-custom)
981
982 (defcustom gnus-treat-hide-headers 'head
983   "Hide headers.
984 Valid values are nil, t, `head', `last', an integer or a predicate.
985 See Info node `(gnus)Customizing Articles' for details."
986   :group 'gnus-article-treat
987   :link '(custom-manual "(gnus)Customizing Articles")
988   :type gnus-article-treat-head-custom)
989
990 (defcustom gnus-treat-hide-boring-headers nil
991   "Hide boring headers.
992 Valid values are nil, t, `head', `last', an integer or a predicate.
993 See Info node `(gnus)Customizing Articles' for details."
994   :group 'gnus-article-treat
995   :link '(custom-manual "(gnus)Customizing Articles")
996   :type gnus-article-treat-head-custom)
997
998 (defcustom gnus-treat-hide-signature nil
999   "Hide the signature.
1000 Valid values are nil, t, `head', `last', an integer or a predicate.
1001 See Info node `(gnus)Customizing Articles' for details."
1002   :group 'gnus-article-treat
1003   :link '(custom-manual "(gnus)Customizing Articles")
1004   :type gnus-article-treat-custom)
1005
1006 (defcustom gnus-treat-fill-article nil
1007   "Fill the article.
1008 Valid values are nil, t, `head', `last', an integer or a predicate.
1009 See Info node `(gnus)Customizing Articles' for details."
1010   :group 'gnus-article-treat
1011   :link '(custom-manual "(gnus)Customizing Articles")
1012   :type gnus-article-treat-custom)
1013
1014 (defcustom gnus-treat-hide-citation nil
1015   "Hide cited text.
1016 Valid values are nil, t, `head', `last', an integer or a predicate.
1017 See Info node `(gnus)Customizing Articles' for details."
1018   :group 'gnus-article-treat
1019   :link '(custom-manual "(gnus)Customizing Articles")
1020   :type gnus-article-treat-custom)
1021
1022 (defcustom gnus-treat-hide-citation-maybe nil
1023   "Hide cited text.
1024 Valid values are nil, t, `head', `last', an integer or a predicate.
1025 See Info node `(gnus)Customizing Articles' for details."
1026   :group 'gnus-article-treat
1027   :link '(custom-manual "(gnus)Customizing Articles")
1028   :type gnus-article-treat-custom)
1029
1030 (defcustom gnus-treat-strip-list-identifiers 'head
1031   "Strip list identifiers from `gnus-list-identifiers`.
1032 Valid values are nil, t, `head', `last', an integer or a predicate.
1033 See Info node `(gnus)Customizing Articles' for details."
1034   :version "21.1"
1035   :group 'gnus-article-treat
1036   :link '(custom-manual "(gnus)Customizing Articles")
1037   :type gnus-article-treat-custom)
1038
1039 (make-obsolete-variable 'gnus-treat-strip-pgp
1040                         "This option is obsolete in Gnus 5.10.")
1041
1042 (defcustom gnus-treat-strip-pem nil
1043   "Strip PEM signatures.
1044 Valid values are nil, t, `head', `last', an integer or a predicate.
1045 See Info node `(gnus)Customizing Articles' for details."
1046   :group 'gnus-article-treat
1047   :link '(custom-manual "(gnus)Customizing Articles")
1048   :type gnus-article-treat-custom)
1049
1050 (defcustom gnus-treat-strip-banner t
1051   "Strip banners from articles.
1052 The banner to be stripped is specified in the `banner' group parameter.
1053 Valid values are nil, t, `head', `last', an integer or a predicate.
1054 See Info node `(gnus)Customizing Articles' for details."
1055   :group 'gnus-article-treat
1056   :link '(custom-manual "(gnus)Customizing Articles")
1057   :type gnus-article-treat-custom)
1058
1059 (defcustom gnus-treat-highlight-headers 'head
1060   "Highlight the headers.
1061 Valid values are nil, t, `head', `last', an integer or a predicate.
1062 See Info node `(gnus)Customizing Articles' for details."
1063   :group 'gnus-article-treat
1064   :link '(custom-manual "(gnus)Customizing Articles")
1065   :type gnus-article-treat-head-custom)
1066 (put 'gnus-treat-highlight-headers 'highlight t)
1067
1068 (defcustom gnus-treat-highlight-citation t
1069   "Highlight cited text.
1070 Valid values are nil, t, `head', `last', an integer or a predicate.
1071 See Info node `(gnus)Customizing Articles' for details."
1072   :group 'gnus-article-treat
1073   :link '(custom-manual "(gnus)Customizing Articles")
1074   :type gnus-article-treat-custom)
1075 (put 'gnus-treat-highlight-citation 'highlight t)
1076
1077 (defcustom gnus-treat-date-ut nil
1078   "Display the Date in UT (GMT).
1079 Valid values are nil, t, `head', `last', an integer or a predicate.
1080 See Info node `(gnus)Customizing Articles' for details."
1081   :group 'gnus-article-treat
1082   :link '(custom-manual "(gnus)Customizing Articles")
1083   :type gnus-article-treat-head-custom)
1084
1085 (defcustom gnus-treat-date-local nil
1086   "Display the Date in the local timezone.
1087 Valid values are nil, t, `head', `last', an integer or a predicate.
1088 See Info node `(gnus)Customizing Articles' for details."
1089   :group 'gnus-article-treat
1090   :link '(custom-manual "(gnus)Customizing Articles")
1091   :type gnus-article-treat-head-custom)
1092
1093 (defcustom gnus-treat-date-english nil
1094   "Display the Date in a format that can be read aloud in English.
1095 Valid values are nil, t, `head', `last', an integer or a predicate.
1096 See Info node `(gnus)Customizing Articles' for details."
1097   :group 'gnus-article-treat
1098   :link '(custom-manual "(gnus)Customizing Articles")
1099   :type gnus-article-treat-head-custom)
1100
1101 (defcustom gnus-treat-date-lapsed nil
1102   "Display the Date header in a way that says how much time has elapsed.
1103 Valid values are nil, t, `head', `last', an integer or a predicate.
1104 See Info node `(gnus)Customizing Articles' for details."
1105   :group 'gnus-article-treat
1106   :link '(custom-manual "(gnus)Customizing Articles")
1107   :type gnus-article-treat-head-custom)
1108
1109 (defcustom gnus-treat-date-original nil
1110   "Display the date in the original timezone.
1111 Valid values are nil, t, `head', `last', an integer or a predicate.
1112 See Info node `(gnus)Customizing Articles' for details."
1113   :group 'gnus-article-treat
1114   :link '(custom-manual "(gnus)Customizing Articles")
1115   :type gnus-article-treat-head-custom)
1116
1117 (defcustom gnus-treat-date-iso8601 nil
1118   "Display the date in the ISO8601 format.
1119 Valid values are nil, t, `head', `last', an integer or a predicate.
1120 See Info node `(gnus)Customizing Articles' for details."
1121   :version "21.1"
1122   :group 'gnus-article-treat
1123   :link '(custom-manual "(gnus)Customizing Articles")
1124   :type gnus-article-treat-head-custom)
1125
1126 (defcustom gnus-treat-date-user-defined nil
1127   "Display the date in a user-defined format.
1128 The format is defined by the `gnus-article-time-format' variable.
1129 Valid values are nil, t, `head', `last', an integer or a predicate.
1130 See Info node `(gnus)Customizing Articles' for details."
1131   :group 'gnus-article-treat
1132   :link '(custom-manual "(gnus)Customizing Articles")
1133   :type gnus-article-treat-head-custom)
1134
1135 (defcustom gnus-treat-strip-headers-in-body t
1136   "Strip the X-No-Archive header line from the beginning of the body.
1137 Valid values are nil, t, `head', `last', an integer or a predicate.
1138 See Info node `(gnus)Customizing Articles' for details."
1139   :version "21.1"
1140   :group 'gnus-article-treat
1141   :link '(custom-manual "(gnus)Customizing Articles")
1142   :type gnus-article-treat-custom)
1143
1144 (defcustom gnus-treat-strip-trailing-blank-lines nil
1145   "Strip trailing blank lines.
1146 Valid values are nil, t, `head', `last', an integer or a predicate.
1147 See Info node `(gnus)Customizing Articles' for details."
1148   :group 'gnus-article-treat
1149   :link '(custom-manual "(gnus)Customizing Articles")
1150   :type gnus-article-treat-custom)
1151
1152 (defcustom gnus-treat-strip-leading-blank-lines nil
1153   "Strip leading blank lines.
1154 Valid values are nil, t, `head', `last', an integer or a predicate.
1155 See Info node `(gnus)Customizing Articles' for details."
1156   :group 'gnus-article-treat
1157   :link '(custom-manual "(gnus)Customizing Articles")
1158   :type gnus-article-treat-custom)
1159
1160 (defcustom gnus-treat-strip-multiple-blank-lines nil
1161   "Strip multiple blank lines.
1162 Valid values are nil, t, `head', `last', an integer or a predicate.
1163 See Info node `(gnus)Customizing Articles' for details."
1164   :group 'gnus-article-treat
1165   :link '(custom-manual "(gnus)Customizing Articles")
1166   :type gnus-article-treat-custom)
1167
1168 (defcustom gnus-treat-unfold-headers 'head
1169   "Unfold folded header lines.
1170 Valid values are nil, t, `head', `last', an integer or a predicate.
1171 See Info node `(gnus)Customizing Articles' for details."
1172   :group 'gnus-article-treat
1173   :link '(custom-manual "(gnus)Customizing Articles")
1174   :type gnus-article-treat-custom)
1175
1176 (defcustom gnus-treat-fold-headers nil
1177   "Fold headers.
1178 Valid values are nil, t, `head', `last', an integer or a predicate.
1179 See Info node `(gnus)Customizing Articles' for details."
1180   :group 'gnus-article-treat
1181   :link '(custom-manual "(gnus)Customizing Articles")
1182   :type gnus-article-treat-custom)
1183
1184 (defcustom gnus-treat-fold-newsgroups 'head
1185   "Fold the Newsgroups and Followup-To headers.
1186 Valid values are nil, t, `head', `last', an integer or a predicate.
1187 See Info node `(gnus)Customizing Articles' for details."
1188   :group 'gnus-article-treat
1189   :link '(custom-manual "(gnus)Customizing Articles")
1190   :type gnus-article-treat-custom)
1191
1192 (defcustom gnus-treat-overstrike t
1193   "Treat overstrike highlighting.
1194 Valid values are nil, t, `head', `last', an integer or a predicate.
1195 See Info node `(gnus)Customizing Articles' for details."
1196   :group 'gnus-article-treat
1197   :link '(custom-manual "(gnus)Customizing Articles")
1198   :type gnus-article-treat-custom)
1199 (put 'gnus-treat-overstrike 'highlight t)
1200
1201 (make-obsolete-variable 'gnus-treat-display-xface
1202                         'gnus-treat-display-x-face)
1203
1204 (defcustom gnus-treat-display-x-face
1205   (and (not noninteractive)
1206        (or (memq gnus-article-x-face-command
1207                  '(x-face-decode-message-header
1208                    x-face-mule-gnus-article-display-x-face))
1209            (and (fboundp 'image-type-available-p)
1210                 (image-type-available-p 'xbm)
1211                 (string-match "^0x" (shell-command-to-string "uncompface"))
1212                 (executable-find "icontopbm"))
1213            (and (featurep 'xemacs)
1214                 (featurep 'xface)))
1215        'head)
1216   "Display X-Face headers.
1217 Valid values are nil, t, `head', `last', an integer or a predicate.
1218 See Info node `(gnus)Customizing Articles' and Info node
1219 `(gnus)X-Face' for details."
1220   :group 'gnus-article-treat
1221   ;;:version "21.1"
1222   :link '(custom-manual "(gnus)Customizing Articles")
1223   :link '(custom-manual "(gnus)X-Face")
1224   :type gnus-article-treat-head-custom
1225   :set (lambda (symbol value)
1226          (set-default
1227           symbol
1228           (cond ((or (boundp symbol) (get symbol 'saved-value))
1229                  value)
1230                 ((boundp 'gnus-treat-display-xface)
1231                  (message "\
1232 ** gnus-treat-display-xface is an obsolete variable;\
1233  use gnus-treat-display-x-face instead")
1234                  (default-value 'gnus-treat-display-xface))
1235                 ((get 'gnus-treat-display-xface 'saved-value)
1236                  (message "\
1237 ** gnus-treat-display-xface is an obsolete variable;\
1238  use gnus-treat-display-x-face instead")
1239                  (eval (car (get 'gnus-treat-display-xface 'saved-value))))
1240                 (t
1241                  value)))))
1242 (put 'gnus-treat-display-x-face 'highlight t)
1243
1244 (defcustom gnus-article-should-use-smiley-mule
1245   (not (or (featurep 'xemacs)
1246            (gnus-image-type-available-p 'xpm)
1247            (gnus-image-type-available-p 'pbm)))
1248   "If non-nil, gnus uses `smiley-mule' for displaying smileys rather than
1249 `smiley'.  It defaults to t when Emacs 20 or earlier is running.
1250 `smiley-mule' is boundled in BITMAP-MULE package.  You can set it to t
1251 even if you are using Emacs 21+.  It has no effect on XEmacs."
1252   :group 'gnus-article-various
1253   :type 'boolean
1254   :get (lambda (symbol)
1255          (and (not noninteractive)
1256               (default-value symbol)
1257               (not (featurep 'xemacs))
1258               (module-installed-p 'smiley-mule)
1259               t))
1260   :set (lambda (symbol value)
1261          (set-default symbol (and (not noninteractive)
1262                                   value
1263                                   (not (featurep 'xemacs))
1264                                   (module-installed-p 'smiley-mule)
1265                                   t))))
1266
1267 (defvar gnus-article-smiley-mule-loaded-p nil
1268   "Internal variable used to say whether `smiley-mule' is loaded (whether
1269 smiley functions are not overridden by `smiley').")
1270
1271 (defcustom gnus-treat-display-face
1272   (and (not noninteractive)
1273        ;; x-face-e21 handles both X-Face and Face headers.
1274        (not (and (eq gnus-article-x-face-command 'x-face-decode-message-header)
1275                  (module-installed-p 'x-face-e21)))
1276        (or (and (fboundp 'image-type-available-p)
1277                 (image-type-available-p 'png))
1278            (and (featurep 'xemacs)
1279                 (featurep 'png)))
1280        'head)
1281   "Display Face headers.
1282 Valid values are nil, t, `head', `last', an integer or a predicate.
1283 See Info node `(gnus)Customizing Articles' and Info node
1284 `(gnus)X-Face' for details."
1285   :group 'gnus-article-treat
1286   :version "21.1"
1287   :link '(custom-manual "(gnus)Customizing Articles")
1288   :link '(custom-manual "(gnus)X-Face")
1289   :type gnus-article-treat-head-custom)
1290 (put 'gnus-treat-display-face 'highlight t)
1291
1292 (defcustom gnus-treat-display-smileys
1293   (if (and (not noninteractive)
1294            (or (and (featurep 'xemacs)
1295                     (featurep 'xpm))
1296                (gnus-image-type-available-p 'xpm)
1297                (gnus-image-type-available-p 'pbm)
1298                (and (not (featurep 'xemacs))
1299                     window-system
1300                     (module-installed-p 'smiley-mule))))
1301       t
1302     nil)
1303   "Display smileys.
1304 Valid values are nil, t, `head', `last', an integer or a predicate.
1305 See Info node `(gnus)Customizing Articles' and Info node
1306 `(gnus)Smileys' for details."
1307   :group 'gnus-article-treat
1308   ;;:version "21.1"
1309   :link '(custom-manual "(gnus)Customizing Articles")
1310   :link '(custom-manual "(gnus)Smileys")
1311   :type gnus-article-treat-custom)
1312 (put 'gnus-treat-display-smileys 'highlight t)
1313
1314 (defcustom gnus-treat-from-picon
1315   (if (and (gnus-image-type-available-p 'xpm)
1316            (gnus-picons-installed-p))
1317       'head nil)
1318   "Display picons in the From header.
1319 Valid values are nil, t, `head', `last', an integer or a predicate.
1320 See Info node `(gnus)Customizing Articles' and Info node
1321 `(gnus)Picons' for details."
1322   :group 'gnus-article-treat
1323   :group 'gnus-picon
1324   :link '(custom-manual "(gnus)Customizing Articles")
1325   :link '(custom-manual "(gnus)Picons")
1326   :type gnus-article-treat-head-custom)
1327 (put 'gnus-treat-from-picon 'highlight t)
1328
1329 (defcustom gnus-treat-mail-picon
1330   (if (and (gnus-image-type-available-p 'xpm)
1331            (gnus-picons-installed-p))
1332       'head nil)
1333   "Display picons in To and Cc headers.
1334 Valid values are nil, t, `head', `last', an integer or a predicate.
1335 See Info node `(gnus)Customizing Articles' and Info node
1336 `(gnus)Picons' for details."
1337   :group 'gnus-article-treat
1338   :group 'gnus-picon
1339   :link '(custom-manual "(gnus)Customizing Articles")
1340   :link '(custom-manual "(gnus)Picons")
1341   :type gnus-article-treat-head-custom)
1342 (put 'gnus-treat-mail-picon 'highlight t)
1343
1344 (defcustom gnus-treat-newsgroups-picon
1345   (if (and (gnus-image-type-available-p 'xpm)
1346            (gnus-picons-installed-p))
1347       'head nil)
1348   "Display picons in the Newsgroups and Followup-To headers.
1349 Valid values are nil, t, `head', `last', an integer or a predicate.
1350 See Info node `(gnus)Customizing Articles' and Info node
1351 `(gnus)Picons' for details."
1352   :group 'gnus-article-treat
1353   :group 'gnus-picon
1354   :link '(custom-manual "(gnus)Customizing Articles")
1355   :link '(custom-manual "(gnus)Picons")
1356   :type gnus-article-treat-head-custom)
1357 (put 'gnus-treat-newsgroups-picon 'highlight t)
1358
1359 (defcustom gnus-treat-body-boundary
1360   (if (or gnus-treat-newsgroups-picon
1361           gnus-treat-mail-picon
1362           gnus-treat-from-picon)
1363       'head nil)
1364   "Draw a boundary at the end of the headers.
1365 Valid values are nil, t, `head', `last', an integer or a predicate.
1366 See Info node `(gnus)Customizing Articles' for details."
1367   :version "21.1"
1368   :group 'gnus-article-treat
1369   :link '(custom-manual "(gnus)Customizing Articles")
1370   :type gnus-article-treat-custom)
1371
1372 (defcustom gnus-treat-capitalize-sentences nil
1373   "Capitalize sentence-starting words.
1374 Valid values are nil, t, `head', `last', an integer or a predicate.
1375 See Info node `(gnus)Customizing Articles' for details."
1376   :version "21.1"
1377   :group 'gnus-article-treat
1378   :link '(custom-manual "(gnus)Customizing Articles")
1379   :type gnus-article-treat-custom)
1380
1381 (defcustom gnus-treat-wash-html nil
1382   "Format as HTML.
1383 Valid values are nil, t, `head', `last', an integer or a predicate.
1384 See Info node `(gnus)Customizing Articles' for details."
1385   :group 'gnus-article-treat
1386   :link '(custom-manual "(gnus)Customizing Articles")
1387   :type gnus-article-treat-custom)
1388
1389 (defcustom gnus-treat-fill-long-lines nil
1390   "Fill long lines.
1391 Valid values are nil, t, `head', `last', an integer or a predicate.
1392 See Info node `(gnus)Customizing Articles' for details."
1393   :group 'gnus-article-treat
1394   :link '(custom-manual "(gnus)Customizing Articles")
1395   :type gnus-article-treat-custom)
1396
1397 (defcustom gnus-treat-play-sounds nil
1398   "Play sounds.
1399 Valid values are nil, t, `head', `last', an integer or a predicate.
1400 See Info node `(gnus)Customizing Articles' for details."
1401   :version "21.1"
1402   :group 'gnus-article-treat
1403   :link '(custom-manual "(gnus)Customizing Articles")
1404   :type gnus-article-treat-custom)
1405
1406 (defcustom gnus-treat-decode-article-as-default-mime-charset nil
1407   "Decode an article as `default-mime-charset'.  For instance, if you want to
1408 attempt to decode an article even if the value of `gnus-show-mime' is nil,
1409 you could set this variable to something like: nil for don't decode, t for
1410 decode the body, '(or header t) for the whole article, etc."
1411   :group 'gnus-article-treat
1412   :type '(radio (const :tag "Off" nil)
1413                 (const :tag "Decode body" t)
1414                 (const :tag "Decode all" (or head t))))
1415
1416 (defcustom gnus-treat-translate nil
1417   "Translate articles from one language to another.
1418 Valid values are nil, t, `head', `last', an integer or a predicate.
1419 See Info node `(gnus)Customizing Articles' for details."
1420   :version "21.1"
1421   :group 'gnus-article-treat
1422   :link '(custom-manual "(gnus)Customizing Articles")
1423   :type gnus-article-treat-custom)
1424
1425 (defcustom gnus-treat-x-pgp-sig nil
1426   "Verify X-PGP-Sig.
1427 To automatically treat X-PGP-Sig, set it to head.
1428 Valid values are nil, t, `head', `last', an integer or a predicate.
1429 See Info node `(gnus)Customizing Articles' for details."
1430   :group 'gnus-article-treat
1431   :group 'mime-security
1432   :type gnus-article-treat-custom)
1433
1434 (defcustom gnus-treat-monafy nil
1435   "Display body part with mona font.
1436 Valid values are nil, t, `head', `last', an integer or a predicate.
1437 See Info node `(gnus)Customizing Articles' for details."
1438   :group 'gnus-article-treat
1439   :group 'mime-security
1440   :link '(custom-manual "(gnus)Customizing Articles")
1441   :type gnus-article-treat-custom)
1442
1443 (defvar gnus-article-encrypt-protocol-alist
1444   '(("PGP" . mml2015-self-encrypt)))
1445
1446 ;; Set to nil if more than one protocol added to
1447 ;; gnus-article-encrypt-protocol-alist.
1448 (defcustom gnus-article-encrypt-protocol "PGP"
1449   "The protocol used for encrypt articles.
1450 It is a string, such as \"PGP\". If nil, ask user."
1451   :type 'string
1452   :group 'mime-security)
1453
1454 (defvar gnus-article-wash-function nil
1455   "Function used for converting HTML into text.")
1456
1457 (defcustom gnus-use-idna (and (condition-case nil (require 'idna) (file-error))
1458                               (mm-coding-system-p 'utf-8)
1459                               (executable-find idna-program))
1460   "Whether IDNA decoding of headers is used when viewing messages.
1461 This requires GNU Libidn, and by default only enabled if it is found."
1462   :group 'gnus-article-headers
1463   :type 'boolean)
1464
1465 (defcustom gnus-article-over-scroll nil
1466   "If non-nil, allow scrolling the article buffer even when there no more text."
1467   :group 'gnus-article
1468   :type 'boolean)
1469
1470 ;;; Internal variables
1471
1472 (defvar gnus-english-month-names
1473   '("January" "February" "March" "April" "May" "June" "July" "August"
1474     "September" "October" "November" "December"))
1475
1476 (defvar article-goto-body-goes-to-point-min-p nil)
1477 (defvar gnus-article-wash-types nil)
1478 (defvar gnus-article-emphasis-alist nil)
1479 (defvar gnus-article-image-alist nil)
1480
1481 (defvar gnus-article-mime-handle-alist-1 nil)
1482 (defvar gnus-treatment-function-alist
1483   '((gnus-treat-decode-article-as-default-mime-charset
1484      gnus-article-decode-article-as-default-mime-charset)
1485     (gnus-treat-x-pgp-sig gnus-article-verify-x-pgp-sig)
1486     (gnus-treat-monafy gnus-article-monafy)
1487     (gnus-treat-strip-banner gnus-article-strip-banner)
1488     (gnus-treat-strip-headers-in-body gnus-article-strip-headers-in-body)
1489     (gnus-treat-buttonize gnus-article-add-buttons)
1490     (gnus-treat-fill-article gnus-article-fill-cited-article)
1491     (gnus-treat-fill-long-lines gnus-article-fill-long-lines)
1492     (gnus-treat-strip-cr gnus-article-remove-cr)
1493     (gnus-treat-unsplit-urls gnus-article-unsplit-urls)
1494     (gnus-treat-date-ut gnus-article-date-ut)
1495     (gnus-treat-date-local gnus-article-date-local)
1496     (gnus-treat-date-english gnus-article-date-english)
1497     (gnus-treat-date-lapsed gnus-article-date-lapsed)
1498     (gnus-treat-date-original gnus-article-date-original)
1499     (gnus-treat-date-user-defined gnus-article-date-user)
1500     (gnus-treat-date-iso8601 gnus-article-date-iso8601)
1501     (gnus-treat-display-face gnus-article-display-face)
1502     (gnus-treat-hide-headers gnus-article-maybe-hide-headers)
1503     (gnus-treat-hide-boring-headers gnus-article-hide-boring-headers)
1504     (gnus-treat-hide-signature gnus-article-hide-signature)
1505     (gnus-treat-strip-list-identifiers gnus-article-hide-list-identifiers)
1506     (gnus-treat-leading-whitespace gnus-article-remove-leading-whitespace)
1507     (gnus-treat-strip-pem gnus-article-hide-pem)
1508     (gnus-treat-from-picon gnus-treat-from-picon)
1509     (gnus-treat-mail-picon gnus-treat-mail-picon)
1510     (gnus-treat-newsgroups-picon gnus-treat-newsgroups-picon)
1511     (gnus-treat-highlight-headers gnus-article-highlight-headers)
1512     (gnus-treat-highlight-signature gnus-article-highlight-signature)
1513     (gnus-treat-strip-trailing-blank-lines
1514      gnus-article-remove-trailing-blank-lines)
1515     (gnus-treat-strip-leading-blank-lines
1516      gnus-article-strip-leading-blank-lines)
1517     (gnus-treat-strip-multiple-blank-lines
1518      gnus-article-strip-multiple-blank-lines)
1519     (gnus-treat-overstrike gnus-article-treat-overstrike)
1520     (gnus-treat-unfold-headers gnus-article-treat-unfold-headers)
1521     (gnus-treat-fold-headers gnus-article-treat-fold-headers)
1522     ;; Displaying X-Face should be done after unfolding headers
1523     ;; to protect bitmap lines.
1524     (gnus-treat-display-x-face gnus-article-display-x-face)
1525     (gnus-treat-fold-newsgroups gnus-article-treat-fold-newsgroups)
1526     (gnus-treat-buttonize-head gnus-article-add-buttons-to-head)
1527     (gnus-treat-display-smileys gnus-treat-smiley)
1528     (gnus-treat-capitalize-sentences gnus-article-capitalize-sentences)
1529     (gnus-treat-wash-html gnus-article-wash-html)
1530     (gnus-treat-emphasize gnus-article-emphasize)
1531     (gnus-treat-hide-citation gnus-article-hide-citation)
1532     (gnus-treat-hide-citation-maybe gnus-article-hide-citation-maybe)
1533     (gnus-treat-highlight-citation gnus-article-highlight-citation)
1534     (gnus-treat-body-boundary gnus-article-treat-body-boundary)
1535     (gnus-treat-play-sounds gnus-earcon-display)))
1536
1537 (defvar gnus-article-mime-handle-alist nil)
1538 (defvar article-lapsed-timer nil)
1539 (defvar gnus-article-current-summary nil)
1540
1541 (defvar gnus-article-mode-syntax-table
1542   (let ((table (copy-syntax-table text-mode-syntax-table)))
1543     ;; This causes the citation match run O(2^n).
1544     ;; (modify-syntax-entry ?- "w" table)
1545     (modify-syntax-entry ?> ")<" table)
1546     (modify-syntax-entry ?< "(>" table)
1547     ;; make M-. in article buffers work for `foo' strings
1548     (modify-syntax-entry ?' " " table)
1549     (modify-syntax-entry ?` " " table)
1550     table)
1551   "Syntax table used in article mode buffers.
1552 Initialized from `text-mode-syntax-table.")
1553
1554 (defvar gnus-save-article-buffer nil)
1555
1556 (defvar gnus-article-mode-line-format-alist
1557   (nconc '((?w (gnus-article-wash-status) ?s)
1558            (?m (gnus-article-mime-part-status) ?s))
1559          gnus-summary-mode-line-format-alist))
1560
1561 (defvar gnus-number-of-articles-to-be-saved nil)
1562
1563 (defvar gnus-inhibit-hiding nil)
1564
1565 ;;; Macros for dealing with the article buffer.
1566
1567 (defmacro gnus-with-article-headers (&rest forms)
1568   `(save-excursion
1569      (set-buffer gnus-article-buffer)
1570      (save-restriction
1571        (let ((buffer-read-only nil)
1572              (inhibit-point-motion-hooks t)
1573              (case-fold-search t))
1574          (article-narrow-to-head)
1575          ,@forms))))
1576
1577 (put 'gnus-with-article-headers 'lisp-indent-function 0)
1578 (put 'gnus-with-article-headers 'edebug-form-spec '(body))
1579
1580 (defmacro gnus-with-article-buffer (&rest forms)
1581   `(save-excursion
1582      (set-buffer gnus-article-buffer)
1583      (let ((buffer-read-only nil))
1584        ,@forms)))
1585
1586 (put 'gnus-with-article-buffer 'lisp-indent-function 0)
1587 (put 'gnus-with-article-buffer 'edebug-form-spec '(body))
1588
1589 (defun gnus-article-goto-header (header)
1590   "Go to HEADER, which is a regular expression."
1591   (re-search-forward (concat "^\\(" header "\\):") nil t))
1592
1593 (defsubst gnus-article-hide-text (b e props)
1594   "Set text PROPS on the B to E region, extending `intangible' 1 past B."
1595   (gnus-add-text-properties-when 'article-type nil b e props)
1596   (when (memq 'intangible props)
1597     (put-text-property
1598      (max (1- b) (point-min))
1599      b 'intangible (cddr (memq 'intangible props)))))
1600
1601 (defsubst gnus-article-unhide-text (b e)
1602   "Remove hidden text properties from region between B and E."
1603   (remove-text-properties b e gnus-hidden-properties)
1604   (when (memq 'intangible gnus-hidden-properties)
1605     (put-text-property (max (1- b) (point-min))
1606                        b 'intangible nil)))
1607
1608 (defun gnus-article-hide-text-type (b e type)
1609   "Hide text of TYPE between B and E."
1610   (gnus-add-wash-type type)
1611   (gnus-article-hide-text
1612    b e (cons 'article-type (cons type gnus-hidden-properties))))
1613
1614 (defun gnus-article-unhide-text-type (b e type)
1615   "Unhide text of TYPE between B and E."
1616   (gnus-delete-wash-type type)
1617   (remove-text-properties
1618    b e (cons 'article-type (cons type gnus-hidden-properties)))
1619   (when (memq 'intangible gnus-hidden-properties)
1620     (put-text-property (max (1- b) (point-min))
1621                        b 'intangible nil)))
1622
1623 (defun gnus-article-hide-text-of-type (type)
1624   "Hide text of TYPE in the current buffer."
1625   (save-excursion
1626     (let ((b (point-min))
1627           (e (point-max)))
1628       (while (setq b (text-property-any b e 'article-type type))
1629         (add-text-properties b (incf b) gnus-hidden-properties)))))
1630
1631 (defun gnus-article-delete-text-of-type (type)
1632   "Delete text of TYPE in the current buffer."
1633   (save-excursion
1634     (let ((b (point-min)))
1635       (while (setq b (text-property-any b (point-max) 'article-type type))
1636         (delete-region
1637          b (or (text-property-not-all b (point-max) 'article-type type)
1638                (point-max)))))))
1639
1640 (defun gnus-article-delete-invisible-text ()
1641   "Delete all invisible text in the current buffer."
1642   (save-excursion
1643     (let ((b (point-min)))
1644       (while (setq b (text-property-any b (point-max) 'invisible t))
1645         (delete-region
1646          b (or (text-property-not-all b (point-max) 'invisible t)
1647                (point-max)))))))
1648
1649 (defun gnus-article-text-type-exists-p (type)
1650   "Say whether any text of type TYPE exists in the buffer."
1651   (text-property-any (point-min) (point-max) 'article-type type))
1652
1653 (defsubst gnus-article-header-rank ()
1654   "Give the rank of the string HEADER as given by `gnus-sorted-header-list'."
1655   (let ((list gnus-sorted-header-list)
1656         (i 1))
1657     (while list
1658       (if (looking-at (car list))
1659           (setq list nil)
1660         (setq list (cdr list))
1661         (incf i)))
1662       i))
1663
1664 (defun article-hide-headers (&optional arg delete)
1665   "Hide unwanted headers and possibly sort them as well."
1666   (interactive (gnus-article-hidden-arg))
1667   ;; Lars said that this function might be inhibited.
1668   (if (gnus-article-check-hidden-text 'headers arg)
1669       (progn
1670         ;; Show boring headers as well.
1671         (gnus-article-show-hidden-text 'boring-headers)
1672         (when (eq 1 (point-min))
1673           (set-window-start (get-buffer-window (current-buffer)) 1)))
1674     (unless gnus-inhibit-hiding
1675       (save-excursion
1676         (save-restriction
1677           (let ((inhibit-read-only t)
1678                 (case-fold-search t)
1679                 (max (1+ (length gnus-sorted-header-list)))
1680                 (ignored (when (not gnus-visible-headers)
1681                            (cond ((stringp gnus-ignored-headers)
1682                                   gnus-ignored-headers)
1683                                  ((listp gnus-ignored-headers)
1684                                   (mapconcat 'identity gnus-ignored-headers
1685                                              "\\|")))))
1686                 (visible
1687                  (cond ((stringp gnus-visible-headers)
1688                         gnus-visible-headers)
1689                        ((and gnus-visible-headers
1690                              (listp gnus-visible-headers))
1691                         (mapconcat 'identity gnus-visible-headers "\\|"))))
1692                 (inhibit-point-motion-hooks t)
1693                 beg)
1694             ;; First we narrow to just the headers.
1695             (article-narrow-to-head)
1696             ;; Hide any "From " lines at the beginning of (mail) articles.
1697             (while (looking-at "From ")
1698               (forward-line 1))
1699             (unless (bobp)
1700               (if delete
1701                   (delete-region (point-min) (point))
1702                 (gnus-article-hide-text (point-min) (point)
1703                                         (nconc (list 'article-type 'headers)
1704                                                gnus-hidden-properties))))
1705             ;; Then treat the rest of the header lines.
1706             ;; Then we use the two regular expressions
1707             ;; `gnus-ignored-headers' and `gnus-visible-headers' to
1708             ;; select which header lines is to remain visible in the
1709             ;; article buffer.
1710             (while (re-search-forward "^[^ \t:]*:" nil t)
1711               (beginning-of-line)
1712               ;; Mark the rank of the header.
1713               (put-text-property
1714                (point) (1+ (point)) 'message-rank
1715                (if (or (and visible (looking-at visible))
1716                        (and ignored
1717                             (not (looking-at ignored))))
1718                    (gnus-article-header-rank)
1719                  (+ 2 max)))
1720               (forward-line 1))
1721             (message-sort-headers-1)
1722             (when (setq beg (text-property-any
1723                              (point-min) (point-max) 'message-rank (+ 2 max)))
1724               ;; We delete or make invisible the unwanted headers.
1725               (gnus-add-wash-type 'headers)
1726               (if delete
1727                   (progn
1728                     (add-text-properties
1729                      (point-min) (+ 5 (point-min))
1730                      '(article-type headers dummy-invisible t))
1731                     (delete-region beg (point-max)))
1732                 (gnus-article-hide-text-type beg (point-max) 'headers)))))))))
1733
1734 (defun article-hide-boring-headers (&optional arg)
1735   "Toggle hiding of headers that aren't very interesting.
1736 If given a negative prefix, always show; if given a positive prefix,
1737 always hide."
1738   (interactive (gnus-article-hidden-arg))
1739   (when (and (not (gnus-article-check-hidden-text 'boring-headers arg))
1740              (not gnus-show-all-headers))
1741     (save-excursion
1742       (save-restriction
1743         (let ((buffer-read-only nil)
1744               (list gnus-boring-article-headers)
1745               (inhibit-point-motion-hooks t)
1746               elem)
1747           (article-narrow-to-head)
1748           (while list
1749             (setq elem (pop list))
1750             (goto-char (point-min))
1751             (cond
1752              ;; Hide empty headers.
1753              ((eq elem 'empty)
1754               (while (re-search-forward "^[^: \t]+:[ \t]*\n[^ \t]" nil t)
1755                 (forward-line -1)
1756                 (gnus-article-hide-text-type
1757                  (gnus-point-at-bol)
1758                  (progn
1759                    (end-of-line)
1760                    (if (re-search-forward "^[^ \t]" nil t)
1761                        (match-beginning 0)
1762                      (point-max)))
1763                  'boring-headers)))
1764              ;; Hide boring Newsgroups header.
1765              ((eq elem 'newsgroups)
1766               (when (gnus-string-equal
1767                      (gnus-fetch-field "newsgroups")
1768                      (gnus-group-real-name
1769                       (if (boundp 'gnus-newsgroup-name)
1770                           gnus-newsgroup-name
1771                         "")))
1772                 (gnus-article-hide-header "newsgroups")))
1773              ((eq elem 'to-address)
1774               (let ((to (message-fetch-field "to"))
1775                     (to-address
1776                      (gnus-parameter-to-address
1777                       (if (boundp 'gnus-newsgroup-name)
1778                           gnus-newsgroup-name ""))))
1779                 (when (and to to-address
1780                            (ignore-errors
1781                              (gnus-string-equal
1782                               ;; only one address in To
1783                               (nth 1 (mail-extract-address-components to))
1784                               to-address)))
1785                   (gnus-article-hide-header "to"))))
1786              ((eq elem 'to-list)
1787               (let ((to (message-fetch-field "to"))
1788                     (to-list
1789                      (gnus-parameter-to-list
1790                       (if (boundp 'gnus-newsgroup-name)
1791                           gnus-newsgroup-name ""))))
1792                 (when (and to to-list
1793                            (ignore-errors
1794                              (gnus-string-equal
1795                               ;; only one address in To
1796                               (nth 1 (mail-extract-address-components to))
1797                               to-list)))
1798                   (gnus-article-hide-header "to"))))
1799              ((eq elem 'cc-list)
1800               (let ((cc (message-fetch-field "cc"))
1801                     (to-list
1802                      (gnus-parameter-to-list
1803                       (if (boundp 'gnus-newsgroup-name)
1804                           gnus-newsgroup-name ""))))
1805                 (when (and cc to-list
1806                            (ignore-errors
1807                              (gnus-string-equal
1808                               ;; only one address in CC
1809                               (nth 1 (mail-extract-address-components cc))
1810                               to-list)))
1811                   (gnus-article-hide-header "cc"))))
1812              ((eq elem 'followup-to)
1813               (when (gnus-string-equal
1814                      (message-fetch-field "followup-to")
1815                      (message-fetch-field "newsgroups"))
1816                 (gnus-article-hide-header "followup-to")))
1817              ((eq elem 'reply-to)
1818               (if (gnus-group-find-parameter
1819                    gnus-newsgroup-name 'broken-reply-to)
1820                   (gnus-article-hide-header "reply-to")
1821                 (let ((from (message-fetch-field "from"))
1822                       (reply-to (message-fetch-field "reply-to")))
1823                   (when (and
1824                          from reply-to
1825                          (ignore-errors
1826                            (gnus-string-equal
1827                             (nth 1 (mail-extract-address-components from))
1828                             (nth 1 (mail-extract-address-components reply-to)))))
1829                     (gnus-article-hide-header "reply-to")))))
1830              ((eq elem 'date)
1831               (let ((date (message-fetch-field "date")))
1832                 (when (and date
1833                            (< (days-between (current-time-string) date)
1834                               4))
1835                   (gnus-article-hide-header "date"))))
1836              ((eq elem 'long-to)
1837               (let ((to (message-fetch-field "to"))
1838                     (cc (message-fetch-field "cc")))
1839                 (when (> (length to) 1024)
1840                   (gnus-article-hide-header "to"))
1841                 (when (> (length cc) 1024)
1842                   (gnus-article-hide-header "cc"))))
1843              ((eq elem 'many-to)
1844               (let ((to-count 0)
1845                     (cc-count 0))
1846                 (goto-char (point-min))
1847                 (while (re-search-forward "^to:" nil t)
1848                   (setq to-count (1+ to-count)))
1849                 (when (> to-count 1)
1850                   (while (> to-count 0)
1851                     (goto-char (point-min))
1852                     (save-restriction
1853                       (re-search-forward "^to:" nil nil to-count)
1854                       (forward-line -1)
1855                       (narrow-to-region (point) (point-max))
1856                       (gnus-article-hide-header "to"))
1857                     (setq to-count (1- to-count))))
1858                 (goto-char (point-min))
1859                 (while (re-search-forward "^cc:" nil t)
1860                   (setq cc-count (1+ cc-count)))
1861                 (when (> cc-count 1)
1862                   (while (> cc-count 0)
1863                     (goto-char (point-min))
1864                     (save-restriction
1865                       (re-search-forward "^cc:" nil nil cc-count)
1866                       (forward-line -1)
1867                       (narrow-to-region (point) (point-max))
1868                       (gnus-article-hide-header "cc"))
1869                     (setq cc-count (1- cc-count)))))))))))))
1870
1871 (defun gnus-article-hide-header (header)
1872   (save-excursion
1873     (goto-char (point-min))
1874     (when (re-search-forward (concat "^" header ":") nil t)
1875       (gnus-article-hide-text-type
1876        (gnus-point-at-bol)
1877        (progn
1878          (end-of-line)
1879          (if (re-search-forward "^[^ \t]" nil t)
1880              (match-beginning 0)
1881            (point-max)))
1882        'boring-headers))))
1883
1884 (defvar gnus-article-normalized-header-length 40
1885   "Length of normalized headers.")
1886
1887 (defun article-normalize-headers ()
1888   "Make all header lines 40 characters long."
1889   (interactive)
1890   (let ((buffer-read-only nil)
1891         column)
1892     (save-excursion
1893       (save-restriction
1894         (article-narrow-to-head)
1895         (while (not (eobp))
1896           (cond
1897            ((< (setq column (- (gnus-point-at-eol) (point)))
1898                gnus-article-normalized-header-length)
1899             (end-of-line)
1900             (insert (make-string
1901                      (- gnus-article-normalized-header-length column)
1902                      ? )))
1903            ((> column gnus-article-normalized-header-length)
1904             (gnus-put-text-property
1905              (progn
1906                (forward-char gnus-article-normalized-header-length)
1907                (point))
1908              (gnus-point-at-eol)
1909              'invisible t))
1910            (t
1911             ;; Do nothing.
1912             ))
1913           (forward-line 1))))))
1914
1915 (defun article-treat-dumbquotes ()
1916   "Translate M****s*** sm*rtq**t*s into proper text.
1917 Note that this function guesses whether a character is a sm*rtq**t* or
1918 not, so it should only be used interactively.
1919
1920 Sm*rtq**t*s are M****s***'s unilateral extension to the character map
1921 in an attempt to provide more quoting characters.  If you see
1922 something like \\222 or \\264 where you're expecting some kind of
1923 apostrophe or quotation mark, then try this wash."
1924   (interactive)
1925   (article-translate-strings gnus-article-dumbquotes-map))
1926
1927 (defun article-translate-characters (from to)
1928   "Translate all characters in the body of the article according to FROM and TO.
1929 FROM is a string of characters to translate from; to is a string of
1930 characters to translate to."
1931   (save-excursion
1932     (when (article-goto-body)
1933       (let ((buffer-read-only nil)
1934             (x (make-string 225 ?x))
1935             (i -1))
1936         (while (< (incf i) (length x))
1937           (aset x i i))
1938         (setq i 0)
1939         (while (< i (length from))
1940           (aset x (aref from i) (aref to i))
1941           (incf i))
1942         (translate-region (point) (point-max) x)))))
1943
1944 (defun article-translate-strings (map)
1945   "Translate all string in the body of the article according to MAP.
1946 MAP is an alist where the elements are on the form (\"from\" \"to\")."
1947   (save-excursion
1948     (when (article-goto-body)
1949       (let ((buffer-read-only nil)
1950             elem)
1951         (while (setq elem (pop map))
1952           (save-excursion
1953             (while (search-forward (car elem) nil t)
1954               (replace-match (cadr elem)))))))))
1955
1956 (defun article-treat-overstrike ()
1957   "Translate overstrikes into bold text."
1958   (interactive)
1959   (save-excursion
1960     (when (article-goto-body)
1961       (let ((buffer-read-only nil))
1962         (while (search-forward "\b" nil t)
1963           (let ((next (char-after))
1964                 start end previous)
1965             (backward-char 2)
1966             (setq start (point)
1967                   previous (char-after))
1968             (forward-char 3)
1969             (setq end (point))
1970             (backward-char)
1971             ;; We do the boldification/underlining by hiding the
1972             ;; overstrikes and putting the proper text property
1973             ;; on the letters.
1974             (cond
1975              ((eq next previous)
1976               (gnus-article-hide-text-type start (point) 'overstrike)
1977               (put-text-property (point) end 'face 'bold))
1978              ((eq next ?_)
1979               (gnus-article-hide-text-type
1980                (1- (point)) (1+ (point)) 'overstrike)
1981               (put-text-property
1982                start (1- (point)) 'face 'underline))
1983              ((eq previous ?_)
1984               (gnus-article-hide-text-type start (point) 'overstrike)
1985               (put-text-property
1986                (point) end 'face 'underline)))))))))
1987
1988 (defun gnus-article-treat-unfold-headers ()
1989   "Unfold folded message headers.
1990 Only the headers that fit into the current window width will be
1991 unfolded."
1992   (interactive)
1993   (gnus-with-article-headers
1994     (let (length)
1995       (while (not (eobp))
1996         (save-restriction
1997           (mail-header-narrow-to-field)
1998           (let ((header (buffer-string)))
1999             (with-temp-buffer
2000               (insert header)
2001               (goto-char (point-min))
2002               (while (re-search-forward "\n[\t ]" nil t)
2003                 (replace-match " " t t)))
2004             (setq length (- (point-max) (point-min) 1)))
2005           (when (< length (window-width))
2006             (while (re-search-forward "\n[\t ]" nil t)
2007               (replace-match " " t t)))
2008           (goto-char (point-max)))))))
2009
2010 (defun gnus-article-treat-fold-headers ()
2011   "Fold message headers."
2012   (interactive)
2013   (gnus-with-article-headers
2014     (while (not (eobp))
2015       (save-restriction
2016         (mail-header-narrow-to-field)
2017         (mail-header-fold-field)
2018         (goto-char (point-max))))))
2019
2020 (defun gnus-treat-smiley ()
2021   "Toggle display of textual emoticons (\"smileys\") as small graphical icons."
2022   (interactive)
2023   (unless (featurep 'xemacs)
2024     (when (and (>= emacs-major-version 21)
2025                (not gnus-article-should-use-smiley-mule)
2026                gnus-article-smiley-mule-loaded-p)
2027       (load "smiley" nil t)
2028       (setq gnus-article-smiley-mule-loaded-p nil))
2029     (when (and gnus-article-should-use-smiley-mule
2030                (not gnus-article-smiley-mule-loaded-p))
2031       (load "smiley-mule" nil t)
2032       (setq gnus-article-smiley-mule-loaded-p t)))
2033   (gnus-with-article-buffer
2034     (if (memq 'smiley gnus-article-wash-types)
2035         (gnus-delete-images 'smiley)
2036       (article-goto-body)
2037       (let ((images (smiley-region (point) (point-max))))
2038         (when images
2039           (gnus-add-wash-type 'smiley)
2040           (dolist (image images)
2041             (gnus-add-image 'smiley image)))))))
2042
2043 (defun gnus-article-remove-images ()
2044   "Remove all images from the article buffer."
2045   (interactive)
2046   (gnus-with-article-buffer
2047     (dolist (elem gnus-article-image-alist)
2048       (gnus-delete-images (car elem)))))
2049
2050 (defun gnus-article-treat-fold-newsgroups ()
2051   "Unfold folded message headers.
2052 Only the headers that fit into the current window width will be
2053 unfolded."
2054   (interactive)
2055   (gnus-with-article-headers
2056     (while (gnus-article-goto-header "newsgroups\\|followup-to")
2057       (save-restriction
2058         (mail-header-narrow-to-field)
2059         (while (re-search-forward ", *" nil t)
2060           (replace-match ", " t t))
2061         (mail-header-fold-field)
2062         (goto-char (point-max))))))
2063
2064 (defun gnus-article-treat-body-boundary ()
2065   "Place a boundary line at the end of the headers."
2066   (interactive)
2067   (when (and gnus-body-boundary-delimiter
2068              (> (length gnus-body-boundary-delimiter) 0))
2069     (gnus-with-article-headers
2070       (goto-char (point-max))
2071       (let ((start (point)))
2072         (insert "X-Boundary: ")
2073         (gnus-add-text-properties start (point) '(invisible t intangible t))
2074         (insert (let (str)
2075                   (while (>= (1- (window-width)) (length str))
2076                     (setq str (concat str gnus-body-boundary-delimiter)))
2077                   (substring str 0 (1- (window-width))))
2078                 "\n")
2079         (gnus-put-text-property start (point) 'gnus-decoration 'header)))))
2080
2081 (defun article-fill-long-lines ()
2082   "Fill lines that are wider than the window width."
2083   (interactive)
2084   (save-excursion
2085     (let ((buffer-read-only nil)
2086           (width (window-width (get-buffer-window (current-buffer)))))
2087       (save-restriction
2088         (article-goto-body)
2089         (let ((adaptive-fill-mode nil)) ;Why?  -sm
2090           (while (not (eobp))
2091             (end-of-line)
2092             (when (>= (current-column) (min fill-column width))
2093               (narrow-to-region (min (1+ (point)) (point-max))
2094                                 (gnus-point-at-bol))
2095               (let ((goback (point-marker)))
2096                 (fill-paragraph nil)
2097                 (goto-char (marker-position goback)))
2098               (widen))
2099             (forward-line 1)))))))
2100
2101 (defun article-capitalize-sentences ()
2102   "Capitalize the first word in each sentence."
2103   (interactive)
2104   (save-excursion
2105     (let ((buffer-read-only nil)
2106           (paragraph-start "^[\n\^L]"))
2107       (article-goto-body)
2108       (while (not (eobp))
2109         (capitalize-word 1)
2110         (forward-sentence)))))
2111
2112 (defun article-remove-cr ()
2113   "Remove trailing CRs and then translate remaining CRs into LFs."
2114   (interactive)
2115   (save-excursion
2116     (let ((buffer-read-only nil))
2117       (goto-char (point-min))
2118       (while (re-search-forward "\r+$" nil t)
2119         (replace-match "" t t))
2120       (goto-char (point-min))
2121       (while (search-forward "\r" nil t)
2122         (replace-match "\n" t t)))))
2123
2124 (defun article-remove-trailing-blank-lines ()
2125   "Remove all trailing blank lines from the article."
2126   (interactive)
2127   (save-excursion
2128     (let ((buffer-read-only nil))
2129       (goto-char (point-max))
2130       (delete-region
2131        (point)
2132        (progn
2133          (while (and (not (bobp))
2134                      (looking-at "^[ \t]*$")
2135                      (not (gnus-annotation-in-region-p
2136                            (point) (gnus-point-at-eol))))
2137            (forward-line -1))
2138          (forward-line 1)
2139          (point))))))
2140
2141 (defun article-display-face ()
2142   "Display any Face headers in the header."
2143   (interactive)
2144   (let ((wash-face-p buffer-read-only))
2145     (gnus-with-article-headers
2146       ;; When displaying parts, this function can be called several times on
2147       ;; the same article, without any intended toggle semantic (as typing `W
2148       ;; D d' would have). So face deletion must occur only when we come from
2149       ;; an interactive command, that is when the *Article* buffer is
2150       ;; read-only.
2151       (if (and wash-face-p (memq 'face gnus-article-wash-types))
2152           (gnus-delete-images 'face)
2153         (let (face faces)
2154           (save-excursion
2155             (when (and wash-face-p
2156                        (progn
2157                          (goto-char (point-min))
2158                          (not (re-search-forward "^Face:[\t ]*" nil t)))
2159                        (gnus-buffer-live-p gnus-original-article-buffer))
2160               (set-buffer gnus-original-article-buffer))
2161             (save-restriction
2162               (mail-narrow-to-head)
2163               (while (gnus-article-goto-header "Face")
2164                 (push (mail-header-field-value) faces))))
2165           (while (setq face (pop faces))
2166             (let ((png (gnus-convert-face-to-png face))
2167                   image)
2168               (when png
2169                 (setq image (gnus-create-image png 'png t))
2170                 (gnus-article-goto-header "from")
2171                 (when (bobp)
2172                   (insert "From: [no `from' set]\n")
2173                   (forward-char -17))
2174                 (gnus-add-wash-type 'face)
2175                 (gnus-add-image 'face image)
2176                 (gnus-put-image image nil 'face))))))
2177       )))
2178
2179 (defun article-display-x-face (&optional force)
2180   "Look for an X-Face header and display it if present."
2181   (interactive (list 'force))
2182   (let ((wash-face-p buffer-read-only)) ;; When type `W f'
2183     (gnus-with-article-headers
2184       ;; Delete the old process, if any.
2185       (when (process-status "article-x-face")
2186         (delete-process "article-x-face"))
2187       ;; See the comment in `article-display-face'.
2188       (if (and wash-face-p (memq 'xface gnus-article-wash-types))
2189           ;; We have already displayed X-Faces, so we remove them
2190           ;; instead.
2191           (gnus-delete-images 'xface)
2192         ;; Display X-Faces.
2193         (let (x-faces from face)
2194           (save-excursion
2195             (when (and wash-face-p
2196                        (progn
2197                          (goto-char (point-min))
2198                          (not (re-search-forward
2199                                "^X-Face\\(-[0-9]+\\)?:[\t ]*" nil t)))
2200                        (gnus-buffer-live-p gnus-original-article-buffer))
2201               ;; If type `W f', use gnus-original-article-buffer,
2202               ;; otherwise use the current buffer because displaying
2203               ;; RFC822 parts calls this function too.
2204               (set-buffer gnus-original-article-buffer))
2205             (save-restriction
2206               (mail-narrow-to-head)
2207               (while (gnus-article-goto-header "X-Face")
2208                 (push (mail-header-field-value) x-faces))
2209               (setq from (message-fetch-field "from"))))
2210           ;; Sending multiple EOFs to xv doesn't work, so we only do a
2211           ;; single external face.
2212           (when (stringp gnus-article-x-face-command)
2213             (setq x-faces (list (car x-faces))))
2214           (while (and (setq face (pop x-faces))
2215                       gnus-article-x-face-command
2216                       (or force
2217                           ;; Check whether this face is censored.
2218                           (not gnus-article-x-face-too-ugly)
2219                           (and gnus-article-x-face-too-ugly from
2220                                (not (string-match gnus-article-x-face-too-ugly
2221                                                   from)))))
2222             ;; We display the face.
2223             (cond ((stringp gnus-article-x-face-command)
2224                    ;; The command is a string, so we interpret the command
2225                    ;; as a, well, command, and fork it off.
2226                    (let ((process-connection-type nil))
2227                      (process-kill-without-query
2228                       (start-process
2229                        "article-x-face" nil shell-file-name
2230                        shell-command-switch gnus-article-x-face-command))
2231                      (with-temp-buffer
2232                        (insert face)
2233                        (process-send-region "article-x-face"
2234                                             (point-min) (point-max)))
2235                      (process-send-eof "article-x-face")))
2236                   ((functionp gnus-article-x-face-command)
2237                    ;; The command is a lisp function, so we call it.
2238                    (funcall gnus-article-x-face-command face))
2239                   (t
2240                    (error "%s is not a function"
2241                           gnus-article-x-face-command)))))))))
2242
2243 (defun article-decode-mime-words ()
2244   "Decode all MIME-encoded words in the article."
2245   (interactive)
2246   (save-excursion
2247     (set-buffer gnus-article-buffer)
2248     (let ((inhibit-point-motion-hooks t)
2249           buffer-read-only
2250           (mail-parse-charset gnus-newsgroup-charset)
2251           (mail-parse-ignored-charsets
2252            (save-excursion (set-buffer gnus-summary-buffer)
2253                            gnus-newsgroup-ignored-charsets)))
2254       (mail-decode-encoded-word-region (point-min) (point-max)))))
2255
2256 (defun article-decode-charset (&optional prompt)
2257   "Decode charset-encoded text in the article.
2258 If PROMPT (the prefix), prompt for a coding system to use."
2259   (interactive "P")
2260   (let ((inhibit-point-motion-hooks t) (case-fold-search t)
2261         buffer-read-only
2262         (mail-parse-charset gnus-newsgroup-charset)
2263         (mail-parse-ignored-charsets
2264          (save-excursion (condition-case nil
2265                              (set-buffer gnus-summary-buffer)
2266                            (error))
2267                          gnus-newsgroup-ignored-charsets))
2268         ct cte ctl charset format)
2269     (save-excursion
2270       (save-restriction
2271         (article-narrow-to-head)
2272         (setq ct (message-fetch-field "Content-Type" t)
2273               cte (message-fetch-field "Content-Transfer-Encoding" t)
2274               ctl (and ct (ignore-errors
2275                             (mail-header-parse-content-type ct)))
2276               charset (cond
2277                        (prompt
2278                         (mm-read-coding-system "Charset to decode: "))
2279                        (ctl
2280                         (mail-content-type-get ctl 'charset)))
2281               format (and ctl (mail-content-type-get ctl 'format)))
2282         (when cte
2283           (setq cte (mail-header-strip cte)))
2284         (if (and ctl (not (string-match "/" (car ctl))))
2285             (setq ctl nil))
2286         (goto-char (point-max)))
2287       (forward-line 1)
2288       (save-restriction
2289         (narrow-to-region (point) (point-max))
2290         (when (and (eq mail-parse-charset 'gnus-decoded)
2291                    (eq (mm-body-7-or-8) '8bit))
2292           ;; The text code could have been decoded.
2293           (setq charset mail-parse-charset))
2294         (when (and (or (not ctl)
2295                        (equal (car ctl) "text/plain"))
2296                    (not format)) ;; article with format will decode later.
2297           (mm-decode-body
2298            charset (and cte (intern (downcase
2299                                      (gnus-strip-whitespace cte))))
2300            (car ctl)))))))
2301
2302 (defun article-decode-encoded-words ()
2303   "Remove encoded-word encoding from headers."
2304   (let (buffer-read-only)
2305     (let ((charset (save-excursion
2306                      (set-buffer gnus-summary-buffer)
2307                      default-mime-charset)))
2308       (mime-decode-header-in-buffer charset))))
2309
2310 (defun article-decode-group-name ()
2311   "Decode group names in `Newsgroups:'."
2312   (let ((inhibit-point-motion-hooks t)
2313         buffer-read-only
2314         (method (gnus-find-method-for-group gnus-newsgroup-name)))
2315     (when (and (or gnus-group-name-charset-method-alist
2316                    gnus-group-name-charset-group-alist)
2317                (gnus-buffer-live-p gnus-original-article-buffer))
2318       (save-restriction
2319         (article-narrow-to-head)
2320         (with-current-buffer gnus-original-article-buffer
2321           (goto-char (point-min)))
2322         (while (re-search-forward
2323                 "^Newsgroups:\\(\\(.\\|\n[\t ]\\)*\\)\n[^\t ]" nil t)
2324           (replace-match (save-match-data
2325                            (gnus-decode-newsgroups
2326                             ;; XXX how to use data in article buffer?
2327                             (with-current-buffer gnus-original-article-buffer
2328                               (re-search-forward
2329                                "^Newsgroups:\\(\\(.\\|\n[\t ]\\)*\\)\n[^\t ]"
2330                                nil t)
2331                               (match-string 1))
2332                             gnus-newsgroup-name method))
2333                          t t nil 1))
2334         (goto-char (point-min))
2335         (with-current-buffer gnus-original-article-buffer
2336           (goto-char (point-min)))
2337         (while (re-search-forward
2338                 "^Followup-To:\\(\\(.\\|\n[\t ]\\)*\\)\n[^\t ]" nil t)
2339           (replace-match (save-match-data
2340                            (gnus-decode-newsgroups
2341                             ;; XXX how to use data in article buffer?
2342                             (with-current-buffer gnus-original-article-buffer
2343                               (re-search-forward
2344                                "^Followup-To:\\(\\(.\\|\n[\t ]\\)*\\)\n[^\t ]"
2345                                nil t)
2346                               (match-string 1))
2347                             gnus-newsgroup-name method))
2348                          t t nil 1))))))
2349
2350 (autoload 'idna-to-unicode "idna")
2351
2352 (defun article-decode-idna-rhs ()
2353   "Decode IDNA strings in RHS in From:, To: and Cc: headers in current buffer."
2354   (when gnus-use-idna
2355     (save-restriction
2356       (let ((inhibit-point-motion-hooks t)
2357             buffer-read-only)
2358         (article-narrow-to-head)
2359         (goto-char (point-min))
2360         (while (re-search-forward "\\(xn--[-A-Za-z0-9.]*\\)[ \t\n\r,>]" nil t)
2361           (let (ace unicode)
2362             (when (save-match-data
2363                     (and (setq ace (match-string 1))
2364                          (save-excursion
2365                            (and (re-search-backward "^[^ \t]" nil t)
2366                                 (looking-at "From\\|To\\|Cc")))
2367                          (save-excursion (backward-char)
2368                                          (message-idna-inside-rhs-p))
2369                          (setq unicode (idna-to-unicode ace))))
2370               (unless (string= ace unicode)
2371                 (replace-match unicode nil nil nil 1)))))))))
2372
2373 (defun article-de-quoted-unreadable (&optional force read-charset)
2374   "Translate a quoted-printable-encoded article.
2375 If FORCE, decode the article whether it is marked as quoted-printable
2376 or not.
2377 If READ-CHARSET, ask for a coding system."
2378   (interactive (list 'force current-prefix-arg))
2379   (save-excursion
2380     (let ((buffer-read-only nil) type charset)
2381       (if (gnus-buffer-live-p gnus-original-article-buffer)
2382           (with-current-buffer gnus-original-article-buffer
2383             (setq type
2384                   (gnus-fetch-field "content-transfer-encoding"))
2385             (let* ((ct (gnus-fetch-field "content-type"))
2386                    (ctl (and ct
2387                              (ignore-errors
2388                                (mail-header-parse-content-type ct)))))
2389               (setq charset (and ctl
2390                                  (mail-content-type-get ctl 'charset)))
2391               (if (stringp charset)
2392                   (setq charset (intern (downcase charset)))))))
2393       (if read-charset
2394           (setq charset (mm-read-coding-system "Charset: " charset)))
2395       (unless charset
2396         (setq charset gnus-newsgroup-charset))
2397       (when (or force
2398                 (and type (let ((case-fold-search t))
2399                             (string-match "quoted-printable" type))))
2400         (article-goto-body)
2401         (quoted-printable-decode-region
2402          (point) (point-max) (mm-charset-to-coding-system charset))))))
2403
2404 (defun article-de-base64-unreadable (&optional force read-charset)
2405   "Translate a base64 article.
2406 If FORCE, decode the article whether it is marked as base64 not.
2407 If READ-CHARSET, ask for a coding system."
2408   (interactive (list 'force current-prefix-arg))
2409   (save-excursion
2410     (let ((buffer-read-only nil) type charset)
2411       (if (gnus-buffer-live-p gnus-original-article-buffer)
2412           (with-current-buffer gnus-original-article-buffer
2413             (setq type
2414                   (gnus-fetch-field "content-transfer-encoding"))
2415             (let* ((ct (gnus-fetch-field "content-type"))
2416                    (ctl (and ct
2417                              (ignore-errors
2418                                (mail-header-parse-content-type ct)))))
2419               (setq charset (and ctl
2420                                  (mail-content-type-get ctl 'charset)))
2421               (if (stringp charset)
2422                   (setq charset (intern (downcase charset)))))))
2423       (if read-charset
2424           (setq charset (mm-read-coding-system "Charset: " charset)))
2425       (unless charset
2426         (setq charset gnus-newsgroup-charset))
2427       (when (or force
2428                 (and type (let ((case-fold-search t))
2429                             (string-match "base64" type))))
2430         (article-goto-body)
2431         (save-restriction
2432           (narrow-to-region (point) (point-max))
2433           (base64-decode-region (point-min) (point-max))
2434           (mm-decode-coding-region
2435            (point-min) (point-max) (mm-charset-to-coding-system charset)))))))
2436
2437 (eval-when-compile
2438   (require 'rfc1843))
2439
2440 (defun article-decode-HZ ()
2441   "Translate a HZ-encoded article."
2442   (interactive)
2443   (require 'rfc1843)
2444   (save-excursion
2445     (let ((buffer-read-only nil))
2446       (rfc1843-decode-region (point-min) (point-max)))))
2447
2448 (defun article-unsplit-urls ()
2449   "Remove the newlines that some other mailers insert into URLs."
2450   (interactive)
2451   (save-excursion
2452     (let ((buffer-read-only nil))
2453       (goto-char (point-min))
2454       (while (re-search-forward
2455               "^\\(\\(https?\\|ftp\\)://\\S-+\\) *\n\\(\\S-+\\)" nil t)
2456         (replace-match "\\1\\3" t)))
2457     (when (interactive-p)
2458       (gnus-treat-article nil))))
2459
2460
2461 (defun article-wash-html (&optional read-charset)
2462   "Format an HTML article.
2463 If READ-CHARSET, ask for a coding system."
2464   (interactive "P")
2465   (save-excursion
2466     (let ((buffer-read-only nil)
2467           charset)
2468       (when (gnus-buffer-live-p gnus-original-article-buffer)
2469         (with-current-buffer gnus-original-article-buffer
2470           (let* ((ct (gnus-fetch-field "content-type"))
2471                  (ctl (and ct
2472                            (ignore-errors
2473                              (mail-header-parse-content-type ct)))))
2474             (setq charset (and ctl
2475                                (mail-content-type-get ctl 'charset)))
2476             (when (stringp charset)
2477               (setq charset (intern (downcase charset)))))))
2478       (when read-charset
2479         (setq charset (mm-read-coding-system "Charset: " charset)))
2480       (unless charset
2481         (setq charset gnus-newsgroup-charset))
2482       (article-goto-body)
2483       (save-window-excursion
2484         (save-restriction
2485           (narrow-to-region (point) (point-max))
2486           (let* ((func (or gnus-article-wash-function mm-text-html-renderer))
2487                  (entry (assq func mm-text-html-washer-alist)))
2488             (when entry
2489               (setq func (cdr entry)))
2490             (cond
2491              ((functionp func)
2492               (funcall func))
2493              (t
2494               (apply (car func) (cdr func))))))))))
2495
2496 (defun gnus-article-wash-html-with-w3 ()
2497   "Wash the current buffer with w3."
2498   (mm-setup-w3)
2499   (let ((w3-strict-width (window-width))
2500         (url-standalone-mode t)
2501         (url-gateway-unplugged t)
2502         (w3-honor-stylesheets nil))
2503     (condition-case ()
2504         (w3-region (point-min) (point-max))
2505       (error))))
2506
2507 (defun gnus-article-wash-html-with-w3m ()
2508   "Wash the current buffer with emacs-w3m."
2509   (mm-setup-w3m)
2510   (save-restriction
2511     (narrow-to-region (point) (point-max))
2512     (let ((w3m-safe-url-regexp (if mm-inline-text-html-with-images
2513                                    nil
2514                                  "\\`cid:"))
2515           w3m-force-redisplay)
2516       (w3m-region (point-min) (point-max)))
2517     (when mm-inline-text-html-with-w3m-keymap
2518       (add-text-properties
2519        (point-min) (point-max)
2520        (nconc (mm-w3m-local-map-property)
2521               '(mm-inline-text-html-with-w3m t))))))
2522
2523 (defun article-hide-list-identifiers ()
2524   "Remove list identifies from the Subject header.
2525 The `gnus-list-identifiers' variable specifies what to do."
2526   (interactive)
2527   (let ((inhibit-point-motion-hooks t)
2528         (regexp (if (consp gnus-list-identifiers)
2529                     (mapconcat 'identity gnus-list-identifiers " *\\|")
2530                   gnus-list-identifiers))
2531         buffer-read-only)
2532     (when regexp
2533       (save-excursion
2534         (save-restriction
2535           (article-narrow-to-head)
2536           (goto-char (point-min))
2537           (while (re-search-forward
2538                   (concat "^Subject: +\\(R[Ee]: +\\)*\\(" regexp " *\\)")
2539                   nil t)
2540             (delete-region (match-beginning 2) (match-end 0))
2541             (beginning-of-line))
2542           (when (re-search-forward
2543                  "^Subject: +\\(\\(R[Ee]: +\\)+\\)R[Ee]: +" nil t)
2544             (delete-region (match-beginning 1) (match-end 1))))))))
2545
2546 (defun article-hide-pem (&optional arg)
2547   "Toggle hiding of any PEM headers and signatures in the current article.
2548 If given a negative prefix, always show; if given a positive prefix,
2549 always hide."
2550   (interactive (gnus-article-hidden-arg))
2551   (unless (gnus-article-check-hidden-text 'pem arg)
2552     (save-excursion
2553       (let (buffer-read-only end)
2554         (goto-char (point-min))
2555         ;; Hide the horrendously ugly "header".
2556         (when (and (search-forward
2557                     "\n-----BEGIN PRIVACY-ENHANCED MESSAGE-----\n"
2558                     nil t)
2559                    (setq end (1+ (match-beginning 0))))
2560           (gnus-add-wash-type 'pem)
2561           (gnus-article-hide-text-type
2562            end
2563            (if (search-forward "\n\n" nil t)
2564                (match-end 0)
2565              (point-max))
2566            'pem)
2567           ;; Hide the trailer as well
2568           (when (search-forward "\n-----END PRIVACY-ENHANCED MESSAGE-----\n"
2569                                 nil t)
2570             (gnus-article-hide-text-type
2571              (match-beginning 0) (match-end 0) 'pem)))))))
2572
2573 (defun article-strip-banner ()
2574   "Strip the banners specified by the `banner' group parameter and by
2575 `gnus-article-address-banner-alist'."
2576   (interactive)
2577   (save-excursion
2578     (save-restriction
2579       (let ((inhibit-point-motion-hooks t))
2580         (when (gnus-parameter-banner gnus-newsgroup-name)
2581           (article-really-strip-banner
2582            (gnus-parameter-banner gnus-newsgroup-name)))
2583         (when gnus-article-address-banner-alist
2584           (article-really-strip-banner
2585            (let ((from (save-restriction
2586                          (widen)
2587                          (article-narrow-to-head)
2588                          (mail-fetch-field "from"))))
2589              (when (and from
2590                         (setq from
2591                               (caar (mail-header-parse-addresses from))))
2592                (catch 'found
2593                  (dolist (pair gnus-article-address-banner-alist)
2594                    (when (string-match (car pair) from)
2595                      (throw 'found (cdr pair)))))))))))))
2596
2597 (defun article-really-strip-banner (banner)
2598   "Strip the banner specified by the argument."
2599   (save-excursion
2600     (save-restriction
2601       (let ((inhibit-point-motion-hooks t)
2602             (gnus-signature-limit nil)
2603             buffer-read-only)
2604         (article-goto-body)
2605         (cond
2606          ((eq banner 'signature)
2607           (when (gnus-article-narrow-to-signature)
2608             (widen)
2609             (forward-line -1)
2610             (delete-region (point) (point-max))))
2611          ((symbolp banner)
2612           (if (setq banner (cdr (assq banner gnus-article-banner-alist)))
2613               (while (re-search-forward banner nil t)
2614                 (delete-region (match-beginning 0) (match-end 0)))))
2615          ((stringp banner)
2616           (while (re-search-forward banner nil t)
2617             (delete-region (match-beginning 0) (match-end 0)))))))))
2618
2619 (defun article-babel ()
2620   "Translate article using an online translation service."
2621   (interactive)
2622   (require 'babel)
2623   (save-excursion
2624     (set-buffer gnus-article-buffer)
2625     (when (article-goto-body)
2626       (let* ((buffer-read-only nil)
2627              (start (point))
2628              (end (point-max))
2629              (orig (buffer-substring start end))
2630              (trans (babel-as-string orig)))
2631         (save-restriction
2632           (narrow-to-region start end)
2633           (delete-region start end)
2634           (insert trans))))))
2635
2636 (defun article-hide-signature (&optional arg)
2637   "Hide the signature in the current article.
2638 If given a negative prefix, always show; if given a positive prefix,
2639 always hide."
2640   (interactive (gnus-article-hidden-arg))
2641   (save-excursion
2642     (save-restriction
2643       (if (interactive-p)
2644           (progn
2645             (widen)
2646             (article-goto-body))
2647         (goto-char (point-min)))
2648       (unless (gnus-article-check-hidden-text 'signature arg)
2649         (let ((buffer-read-only nil)
2650               (button (point)))
2651           (while (setq button (text-property-any button (point-max)
2652                                                  'gnus-callback
2653                                                  'gnus-signature-toggle))
2654             (setq button (text-property-not-all button (point-max)
2655                                                 'gnus-callback
2656                                                 'gnus-signature-toggle))
2657             (when (and button (not (eobp)))
2658               (gnus-article-hide-text-type
2659                (1+ button)
2660                (next-single-property-change (1+ button) 'mime-view-entity
2661                                             nil (point-max))
2662                'signature)))))))
2663   (gnus-set-mode-line 'article))
2664
2665 (defun article-strip-headers-in-body ()
2666   "Strip offensive headers from bodies."
2667   (interactive)
2668   (save-excursion
2669     (article-goto-body)
2670     (let ((case-fold-search t))
2671       (when (looking-at "x-no-archive:")
2672         (gnus-delete-line)))))
2673
2674 (defun article-strip-leading-blank-lines ()
2675   "Remove all blank lines from the beginning of the article."
2676   (interactive)
2677   (save-excursion
2678     (let ((inhibit-point-motion-hooks t)
2679           buffer-read-only)
2680       (when (article-goto-body)
2681         (while (and (not (eobp))
2682                     (looking-at "[ \t]*$"))
2683           (gnus-delete-line))))))
2684
2685 (defun article-narrow-to-head ()
2686   "Narrow the buffer to the head of the message.
2687 Point is left at the beginning of the narrowed-to region."
2688   (narrow-to-region
2689    (goto-char (point-min))
2690    (if (search-forward "\n\n" nil 1)
2691        (1- (point))
2692      (point-max)))
2693   (goto-char (point-min)))
2694
2695 (defun article-goto-body ()
2696   "Place point at the start of the body."
2697   (goto-char (point-min))
2698   (cond
2699    ;; This variable is only bound when dealing with separate
2700    ;; MIME body parts.
2701    (article-goto-body-goes-to-point-min-p
2702     t)
2703    ((search-forward "\n\n" nil t)
2704     t)
2705    (t
2706     (goto-char (point-max))
2707     nil)))
2708
2709 (defun article-strip-multiple-blank-lines ()
2710   "Replace consecutive blank lines with one empty line."
2711   (interactive)
2712   (save-excursion
2713     (let ((inhibit-point-motion-hooks t)
2714           buffer-read-only)
2715       ;; First make all blank lines empty.
2716       (article-goto-body)
2717       (while (re-search-forward "^[ \t]+$" nil t)
2718         (unless (gnus-annotation-in-region-p
2719                  (match-beginning 0) (match-end 0))
2720           (replace-match "" nil t)))
2721       ;; Then replace multiple empty lines with a single empty line.
2722       (article-goto-body)
2723       (while (re-search-forward "\n\n\\(\n+\\)" nil t)
2724         (unless (gnus-annotation-in-region-p
2725                  (match-beginning 0) (match-end 0))
2726           (delete-region (match-beginning 1) (match-end 1)))))))
2727
2728 (defun article-strip-leading-space ()
2729   "Remove all white space from the beginning of the lines in the article."
2730   (interactive)
2731   (save-excursion
2732     (let ((inhibit-point-motion-hooks t)
2733           buffer-read-only)
2734       (article-goto-body)
2735       (while (re-search-forward "^[ \t]+" nil t)
2736         (replace-match "" t t)))))
2737
2738 (defun article-strip-trailing-space ()
2739   "Remove all white space from the end of the lines in the article."
2740   (interactive)
2741   (save-excursion
2742     (let ((inhibit-point-motion-hooks t)
2743           buffer-read-only)
2744       (article-goto-body)
2745       (while (re-search-forward "[ \t]+$" nil t)
2746         (replace-match "" t t)))))
2747
2748 (defun article-strip-blank-lines ()
2749   "Strip leading, trailing and multiple blank lines."
2750   (interactive)
2751   (article-strip-leading-blank-lines)
2752   (article-remove-trailing-blank-lines)
2753   (article-strip-multiple-blank-lines))
2754
2755 (defun article-strip-all-blank-lines ()
2756   "Strip all blank lines."
2757   (interactive)
2758   (save-excursion
2759     (let ((inhibit-point-motion-hooks t)
2760           buffer-read-only)
2761       (article-goto-body)
2762       (while (re-search-forward "^[ \t]*\n" nil t)
2763         (replace-match "" t t)))))
2764
2765 (defun gnus-article-narrow-to-signature ()
2766   "Narrow to the signature; return t if a signature is found, else nil."
2767   (let ((inhibit-point-motion-hooks t))
2768     (when (gnus-article-search-signature)
2769       (forward-line 1)
2770       ;; Check whether we have some limits to what we consider
2771       ;; to be a signature.
2772       (let ((limits (if (listp gnus-signature-limit) gnus-signature-limit
2773                       (list gnus-signature-limit)))
2774             limit limited)
2775         (while (setq limit (pop limits))
2776           (if (or (and (integerp limit)
2777                        (< (- (point-max) (point)) limit))
2778                   (and (floatp limit)
2779                        (< (count-lines (point) (point-max)) limit))
2780                   (and (functionp limit)
2781                        (funcall limit))
2782                   (and (stringp limit)
2783                        (not (re-search-forward limit nil t))))
2784               ()                        ; This limit did not succeed.
2785             (setq limited t
2786                   limits nil)))
2787         (unless limited
2788           (narrow-to-region (point) (point-max))
2789           t)))))
2790
2791 (defun gnus-article-search-signature ()
2792   "Search the current buffer for the signature separator.
2793 Put point at the beginning of the signature separator."
2794   (let ((cur (point)))
2795     (goto-char (point-max))
2796     (if (if (stringp gnus-signature-separator)
2797             (re-search-backward gnus-signature-separator nil t)
2798           (let ((seps gnus-signature-separator))
2799             (while (and seps
2800                         (not (re-search-backward (car seps) nil t)))
2801               (pop seps))
2802             seps))
2803         t
2804       (goto-char cur)
2805       nil)))
2806
2807 (defun gnus-article-hidden-arg ()
2808   "Return the current prefix arg as a number, or 0 if no prefix."
2809   (list (if current-prefix-arg
2810             (prefix-numeric-value current-prefix-arg)
2811           0)))
2812
2813 (defun gnus-article-check-hidden-text (type arg)
2814   "Return nil if hiding is necessary.
2815 Arg can be nil or a number.  nil and positive means hide, negative
2816 means show, 0 means toggle."
2817   (save-excursion
2818     (save-restriction
2819       (let ((hide (gnus-article-hidden-text-p type)))
2820         (cond
2821          ((or (null arg)
2822               (> arg 0))
2823           nil)
2824          ((< arg 0)
2825           (gnus-article-show-hidden-text type)
2826           t)
2827          (t
2828           (if (eq hide 'hidden)
2829               (progn
2830                 (gnus-article-show-hidden-text type)
2831                 t)
2832             nil)))))))
2833
2834 (defun gnus-article-hidden-text-p (type)
2835   "Say whether the current buffer contains hidden text of type TYPE."
2836   (let ((pos (text-property-any (point-min) (point-max) 'article-type type)))
2837     (while (and pos
2838                 (not (get-text-property pos 'invisible))
2839                 (not (get-text-property pos 'dummy-invisible)))
2840       (setq pos
2841             (text-property-any (1+ pos) (point-max) 'article-type type)))
2842     (if pos
2843         'hidden
2844       nil)))
2845
2846 (defun gnus-article-show-hidden-text (type &optional dummy)
2847   "Show all hidden text of type TYPE.
2848 Originally it is hide instead of DUMMY."
2849   (let ((buffer-read-only nil)
2850         (inhibit-point-motion-hooks t))
2851     (gnus-remove-text-properties-when
2852      'article-type type
2853      (point-min) (point-max)
2854      (cons 'article-type (cons type
2855                                gnus-hidden-properties)))
2856     (gnus-delete-wash-type type)))
2857
2858 (defconst article-time-units
2859   `((year . ,(* 365.25 24 60 60))
2860     (week . ,(* 7 24 60 60))
2861     (day . ,(* 24 60 60))
2862     (hour . ,(* 60 60))
2863     (minute . 60)
2864     (second . 1))
2865   "Mapping from time units to seconds.")
2866
2867 (defun gnus-article-forward-header ()
2868   "Move point to the start of the next header.
2869 If the current header is a continuation header, this can be several
2870 lines forward."
2871   (let ((ended nil))
2872     (while (not ended)
2873       (forward-line 1)
2874       (if (looking-at "[ \t]+[^ \t]")
2875           (forward-line 1)
2876         (setq ended t)))))
2877
2878 (defun article-date-ut (&optional type highlight header)
2879   "Convert DATE date to universal time in the current article.
2880 If TYPE is `local', convert to local time; if it is `lapsed', output
2881 how much time has lapsed since DATE.  For `lapsed', the value of
2882 `gnus-article-date-lapsed-new-header' says whether the \"X-Sent:\" header
2883 should replace the \"Date:\" one, or should be added below it."
2884   (interactive (list 'ut t))
2885   (let* ((header (or header
2886                      (and (eq 1 (point-min))
2887                           (mail-header-date (save-excursion
2888                                               (set-buffer gnus-summary-buffer)
2889                                               gnus-current-headers)))
2890                      (message-fetch-field "date")
2891                      ""))
2892          (date (if (vectorp header) (mail-header-date header)
2893                  header))
2894          (inhibit-point-motion-hooks t)
2895          bface eface date-pos)
2896     (when (and date (not (string= date "")))
2897       (save-excursion
2898         (save-restriction
2899           (article-narrow-to-head)
2900           (when (or (and (eq type 'lapsed)
2901                          gnus-article-date-lapsed-new-header
2902                          ;; Attempt to get the face of X-Sent first.
2903                          (re-search-forward "^X-Sent:[ \t]" nil t))
2904                     (re-search-forward "^Date:[ \t]" nil t)
2905                     ;; If Date is missing, try again for X-Sent.
2906                     (re-search-forward "^X-Sent:[ \t]" nil t))
2907             (setq bface (get-text-property (gnus-point-at-bol) 'face)
2908                   date (or (get-text-property (gnus-point-at-bol)
2909                                               'original-date)
2910                            date)
2911                   eface (get-text-property (1- (gnus-point-at-eol))
2912                                            'face)))
2913           (let ((buffer-read-only nil))
2914             ;; Delete any old X-Sent headers.
2915             (when (setq date-pos
2916                         (text-property-any (point-min) (point-max)
2917                                            'article-date-lapsed t))
2918               (goto-char (setq date-pos (set-marker (make-marker) date-pos)))
2919               (delete-region (match-beginning 0)
2920                              (progn (forward-line 1) (point))))
2921             (goto-char (point-min))
2922             ;; Delete any old Date headers.
2923             (while (re-search-forward "^Date:[ \t]" nil t)
2924               (unless date-pos
2925                 (setq date-pos (match-beginning 0)))
2926               (unless (and (eq type 'lapsed)
2927                            gnus-article-date-lapsed-new-header)
2928                 (delete-region (match-beginning 0)
2929                                (progn (message-next-header) (point)))))
2930             (if date-pos
2931                 (progn
2932                   (goto-char date-pos)
2933                   (unless (bolp)
2934                     ;; Possibly, Date has been deleted.
2935                     (insert "\n"))
2936                   (when (and (eq type 'lapsed)
2937                              gnus-article-date-lapsed-new-header
2938                              (looking-at "Date:"))
2939                     (forward-line 1)))
2940               (goto-char (point-min)))
2941             (insert (article-make-date-line date type))
2942             (when (eq type 'lapsed)
2943               (put-text-property (gnus-point-at-bol) (point)
2944                                  'article-date-lapsed t))
2945             (insert "\n")
2946             (forward-line -1)
2947             ;; Do highlighting.
2948             (when (looking-at "\\([^:]+\\): *\\(.*\\)$")
2949               (put-text-property (match-beginning 1) (1+ (match-end 1))
2950                                  'original-date date)
2951               (put-text-property (match-beginning 1) (1+ (match-end 1))
2952                                  'face bface)
2953               (put-text-property (match-beginning 2) (match-end 2)
2954                                  'face eface))))))))
2955
2956 (defun article-make-date-line (date type)
2957   "Return a DATE line of TYPE."
2958   (unless (memq type '(local ut original user iso8601 lapsed english))
2959     (error "Unknown conversion type: %s" type))
2960   (condition-case ()
2961       (let ((time (date-to-time date)))
2962         (cond
2963          ;; Convert to the local timezone.
2964          ((eq type 'local)
2965           (let ((tz (car (current-time-zone time))))
2966             (format "Date: %s %s%02d%02d" (current-time-string time)
2967                     (if (> tz 0) "+" "-") (/ (abs tz) 3600)
2968                     (/ (% (abs tz) 3600) 60))))
2969          ;; Convert to Universal Time.
2970          ((eq type 'ut)
2971           (concat "Date: "
2972                   (current-time-string
2973                    (let* ((e (parse-time-string date))
2974                           (tm (apply 'encode-time e))
2975                           (ms (car tm))
2976                           (ls (- (cadr tm) (car (current-time-zone time)))))
2977                      (cond ((< ls 0) (list (1- ms) (+ ls 65536)))
2978                            ((> ls 65535) (list (1+ ms) (- ls 65536)))
2979                            (t (list ms ls)))))
2980                   " UT"))
2981          ;; Get the original date from the article.
2982          ((eq type 'original)
2983           (concat "Date: " (if (string-match "\n+$" date)
2984                                (substring date 0 (match-beginning 0))
2985                              date)))
2986          ;; Let the user define the format.
2987          ((eq type 'user)
2988           (let ((format (or (condition-case nil
2989                                 (with-current-buffer gnus-summary-buffer
2990                                   gnus-article-time-format)
2991                               (error nil))
2992                             gnus-article-time-format)))
2993             (if (functionp format)
2994                 (funcall format time)
2995               (concat "Date: " (format-time-string format time)))))
2996          ;; ISO 8601.
2997          ((eq type 'iso8601)
2998           (let ((tz (car (current-time-zone time))))
2999             (concat
3000              "Date: "
3001              (format-time-string "%Y%m%dT%H%M%S" time)
3002              (format "%s%02d%02d"
3003                      (if (> tz 0) "+" "-") (/ (abs tz) 3600)
3004                      (/ (% (abs tz) 3600) 60)))))
3005          ;; Do an X-Sent lapsed format.
3006          ((eq type 'lapsed)
3007           ;; If the date is seriously mangled, the timezone functions are
3008           ;; liable to bug out, so we ignore all errors.
3009           (let* ((now (current-time))
3010                  (real-time (subtract-time now time))
3011                  (real-sec (and real-time
3012                                 (+ (* (float (car real-time)) 65536)
3013                                    (cadr real-time))))
3014                  (sec (and real-time (abs real-sec)))
3015                  num prev)
3016             (cond
3017              ((null real-time)
3018               "X-Sent: Unknown")
3019              ((zerop sec)
3020               "X-Sent: Now")
3021              (t
3022               (concat
3023                "X-Sent: "
3024                ;; This is a bit convoluted, but basically we go
3025                ;; through the time units for years, weeks, etc,
3026                ;; and divide things to see whether that results
3027                ;; in positive answers.
3028                (mapconcat
3029                 (lambda (unit)
3030                   (if (zerop (setq num (ffloor (/ sec (cdr unit)))))
3031                       ;; The (remaining) seconds are too few to
3032                       ;; be divided into this time unit.
3033                       ""
3034                     ;; It's big enough, so we output it.
3035                     (setq sec (- sec (* num (cdr unit))))
3036                     (prog1
3037                         (concat (if prev ", " "") (int-to-string
3038                                                    (floor num))
3039                                 " " (symbol-name (car unit))
3040                                 (if (> num 1) "s" ""))
3041                       (setq prev t))))
3042                 article-time-units "")
3043                ;; If dates are odd, then it might appear like the
3044                ;; article was sent in the future.
3045                (if (> real-sec 0)
3046                    " ago"
3047                  " in the future"))))))
3048          ;; Display the date in proper English
3049          ((eq type 'english)
3050           (let ((dtime (decode-time time)))
3051             (concat
3052              "Date: the "
3053              (number-to-string (nth 3 dtime))
3054              (let ((digit (% (nth 3 dtime) 10)))
3055                (cond
3056                 ((memq (nth 3 dtime) '(11 12 13)) "th")
3057                 ((= digit 1) "st")
3058                 ((= digit 2) "nd")
3059                 ((= digit 3) "rd")
3060                 (t "th")))
3061              " of "
3062              (nth (1- (nth 4 dtime)) gnus-english-month-names)
3063              " "
3064              (number-to-string (nth 5 dtime))
3065              " at "
3066              (format "%02d" (nth 2 dtime))
3067              ":"
3068              (format "%02d" (nth 1 dtime)))))))
3069     (error
3070      (format "Date: %s (from T-gnus)" date))))
3071
3072 (defun article-date-local (&optional highlight)
3073   "Convert the current article date to the local timezone."
3074   (interactive (list t))
3075   (article-date-ut 'local highlight))
3076
3077 (defun article-date-english (&optional highlight)
3078   "Convert the current article date to something that is proper English."
3079   (interactive (list t))
3080   (article-date-ut 'english highlight))
3081
3082 (defun article-date-original (&optional highlight)
3083   "Convert the current article date to what it was originally.
3084 This is only useful if you have used some other date conversion
3085 function and want to see what the date was before converting."
3086   (interactive (list t))
3087   (article-date-ut 'original highlight))
3088
3089 (defun article-date-lapsed (&optional highlight)
3090   "Convert the current article date to time lapsed since it was sent."
3091   (interactive (list t))
3092   (article-date-ut 'lapsed highlight))
3093
3094 (defun article-update-date-lapsed ()
3095   "Function to be run from a timer to update the lapsed time line."
3096   (let (deactivate-mark)
3097     (save-excursion
3098       (ignore-errors
3099         (walk-windows
3100          (lambda (w)
3101            (set-buffer (window-buffer w))
3102            (when (eq major-mode 'gnus-article-mode)
3103              (goto-char (point-min))
3104              (when (re-search-forward "^X-Sent:" nil t)
3105                (article-date-lapsed t))))
3106          nil 'visible)))))
3107
3108 (defun gnus-start-date-timer (&optional n)
3109   "Start a timer to update the X-Sent header in the article buffers.
3110 The numerical prefix says how frequently (in seconds) the function
3111 is to run."
3112   (interactive "p")
3113   (unless n
3114     (setq n 1))
3115   (gnus-stop-date-timer)
3116   (setq article-lapsed-timer
3117         (nnheader-run-at-time 1 n 'article-update-date-lapsed)))
3118
3119 (defun gnus-stop-date-timer ()
3120   "Stop the X-Sent timer."
3121   (interactive)
3122   (when article-lapsed-timer
3123     (nnheader-cancel-timer article-lapsed-timer)
3124     (setq article-lapsed-timer nil)))
3125
3126 (defun article-date-user (&optional highlight)
3127   "Convert the current article date to the user-defined format.
3128 This format is defined by the `gnus-article-time-format' variable."
3129   (interactive (list t))
3130   (article-date-ut 'user highlight))
3131
3132 (defun article-date-iso8601 (&optional highlight)
3133   "Convert the current article date to ISO8601."
3134   (interactive (list t))
3135   (article-date-ut 'iso8601 highlight))
3136
3137 ;; (defun article-show-all ()
3138 ;;   "Show all hidden text in the article buffer."
3139 ;;   (interactive)
3140 ;;   (save-excursion
3141 ;;     (widen)
3142 ;;     (let ((buffer-read-only nil))
3143 ;;       (gnus-article-unhide-text (point-min) (point-max))
3144 ;;       (gnus-remove-text-with-property 'gnus-prev)
3145 ;;       (gnus-remove-text-with-property 'gnus-next))))
3146
3147 (defun article-show-all-headers ()
3148   "Show all hidden headers in the article buffer."
3149   (interactive)
3150   (save-excursion
3151     (save-restriction
3152       (widen)
3153       (article-narrow-to-head)
3154       (let ((buffer-read-only nil))
3155         (gnus-article-unhide-text (point-min) (point-max))))))
3156
3157 (defun article-remove-leading-whitespace ()
3158   "Remove excessive whitespace from all headers."
3159   (interactive)
3160   (save-excursion
3161     (save-restriction
3162       (let ((buffer-read-only nil))
3163         (article-narrow-to-head)
3164         (goto-char (point-min))
3165         (while (re-search-forward "^[^ :]+: \\([ \t]+\\)" nil t)
3166           (delete-region (match-beginning 1) (match-end 1)))))))
3167
3168 (defun article-emphasize (&optional arg)
3169   "Emphasize text according to `gnus-emphasis-alist'."
3170   (interactive (gnus-article-hidden-arg))
3171   (unless (gnus-article-check-hidden-text 'emphasis arg)
3172     (save-excursion
3173       (let ((alist (or
3174                     (condition-case nil
3175                         (with-current-buffer gnus-summary-buffer
3176                           gnus-article-emphasis-alist)
3177                       (error))
3178                     gnus-emphasis-alist))
3179             (buffer-read-only nil)
3180             (props (append '(article-type emphasis)
3181                            gnus-hidden-properties))
3182             regexp elem beg invisible visible face)
3183         (article-goto-body)
3184         (setq beg (point))
3185         (while (setq elem (pop alist))
3186           (goto-char beg)
3187           (setq regexp (car elem)
3188                 invisible (nth 1 elem)
3189                 visible (nth 2 elem)
3190                 face (nth 3 elem))
3191           (while (re-search-forward regexp nil t)
3192             (when (and (match-beginning visible) (match-beginning invisible))
3193               (gnus-article-hide-text
3194                (match-beginning invisible) (match-end invisible) props)
3195               (gnus-article-unhide-text-type
3196                (match-beginning visible) (match-end visible) 'emphasis)
3197               (gnus-put-overlay-excluding-newlines
3198                (match-beginning visible) (match-end visible) 'face face)
3199               (gnus-add-wash-type 'emphasis)
3200               (goto-char (match-end invisible)))))))))
3201
3202 (defun gnus-article-setup-highlight-words (&optional highlight-words)
3203   "Setup newsgroup emphasis alist."
3204   (unless gnus-article-emphasis-alist
3205     (let ((name (and gnus-newsgroup-name
3206                      (gnus-group-real-name gnus-newsgroup-name))))
3207       (make-local-variable 'gnus-article-emphasis-alist)
3208       (setq gnus-article-emphasis-alist
3209             (nconc
3210              (let ((alist gnus-group-highlight-words-alist) elem highlight)
3211                (while (setq elem (pop alist))
3212                  (when (and name (string-match (car elem) name))
3213                    (setq alist nil
3214                          highlight (copy-sequence (cdr elem)))))
3215                highlight)
3216              (copy-sequence highlight-words)
3217              (if gnus-newsgroup-name
3218                  (copy-sequence (gnus-group-find-parameter
3219                                  gnus-newsgroup-name 'highlight-words t)))
3220              gnus-emphasis-alist)))))
3221
3222 (eval-when-compile
3223   (defvar gnus-summary-article-menu)
3224   (defvar gnus-summary-post-menu))
3225
3226 ;;; Saving functions.
3227
3228 (defun gnus-article-save (save-buffer file &optional num)
3229   "Save the currently selected article."
3230   (unless gnus-save-all-headers
3231     ;; Remove headers according to `gnus-saved-headers'.
3232     (let ((gnus-visible-headers
3233            (or gnus-saved-headers gnus-visible-headers))
3234           (gnus-article-buffer save-buffer))
3235       (save-excursion
3236         (set-buffer save-buffer)
3237         (article-hide-headers 1 t))))
3238   (save-window-excursion
3239     (if (not gnus-default-article-saver)
3240         (error "No default saver is defined")
3241       ;; !!! Magic!  The saving functions all save
3242       ;; `gnus-save-article-buffer' (or so they think), but we
3243       ;; bind that variable to our save-buffer.
3244       (set-buffer gnus-article-buffer)
3245       (let* ((gnus-save-article-buffer save-buffer)
3246              (filename
3247               (cond
3248                ((not gnus-prompt-before-saving) 'default)
3249                ((eq gnus-prompt-before-saving 'always) nil)
3250                (t file)))
3251              (gnus-number-of-articles-to-be-saved
3252               (when (eq gnus-prompt-before-saving t)
3253                 num)))                  ; Magic
3254         (set-buffer gnus-article-current-summary)
3255         (funcall gnus-default-article-saver filename)))))
3256
3257 (defun gnus-read-save-file-name (prompt &optional filename
3258                                         function group headers variable)
3259   (let ((default-name
3260           (funcall function group headers (symbol-value variable)))
3261         result)
3262     (setq result
3263           (expand-file-name
3264            (cond
3265             ((eq filename 'default)
3266              default-name)
3267             ((eq filename t)
3268              default-name)
3269             (filename filename)
3270             (t
3271              (let* ((split-name (gnus-get-split-value gnus-split-methods))
3272                     (prompt
3273                      (format prompt
3274                              (if (and gnus-number-of-articles-to-be-saved
3275                                       (> gnus-number-of-articles-to-be-saved 1))
3276                                  (format "these %d articles"
3277                                          gnus-number-of-articles-to-be-saved)
3278                                "this article")))
3279                     (file
3280                      ;; Let the split methods have their say.
3281                      (cond
3282                       ;; No split name was found.
3283                       ((null split-name)
3284                        (read-file-name
3285                         (concat prompt " (default "
3286                                 (file-name-nondirectory default-name) ") ")
3287                         (file-name-directory default-name)
3288                         default-name))
3289                       ;; A single group name is returned.
3290                       ((stringp split-name)
3291                        (setq default-name
3292                              (funcall function split-name headers
3293                                       (symbol-value variable)))
3294                        (read-file-name
3295                         (concat prompt " (default "
3296                                 (file-name-nondirectory default-name) ") ")
3297                         (file-name-directory default-name)
3298                         default-name))
3299                       ;; A single split name was found
3300                       ((= 1 (length split-name))
3301                        (let* ((name (expand-file-name
3302                                      (car split-name)
3303                                      gnus-article-save-directory))
3304                               (dir (cond ((file-directory-p name)
3305                                           (file-name-as-directory name))
3306                                          ((file-exists-p name) name)
3307                                          (t gnus-article-save-directory))))
3308                          (read-file-name
3309                           (concat prompt " (default " name ") ")
3310                           dir name)))
3311                       ;; A list of splits was found.
3312                       (t
3313                        (setq split-name (nreverse split-name))
3314                        (let (result)
3315                          (let ((file-name-history
3316                                 (nconc split-name file-name-history)))
3317                            (setq result
3318                                  (expand-file-name
3319                                   (read-file-name
3320                                    (concat prompt " (`M-p' for defaults) ")
3321                                    gnus-article-save-directory
3322                                    (car split-name))
3323                                   gnus-article-save-directory)))
3324                          (car (push result file-name-history)))))))
3325                ;; Create the directory.
3326                (gnus-make-directory (file-name-directory file))
3327                ;; If we have read a directory, we append the default file name.
3328                (when (file-directory-p file)
3329                  (setq file (expand-file-name (file-name-nondirectory
3330                                                default-name)
3331                                               (file-name-as-directory file))))
3332                ;; Possibly translate some characters.
3333                (nnheader-translate-file-chars file))))))
3334     (gnus-make-directory (file-name-directory result))
3335     (set variable result)))
3336
3337 (defun gnus-article-archive-name (group)
3338   "Return the first instance of an \"Archive-name\" in the current buffer."
3339   (let ((case-fold-search t))
3340     (when (re-search-forward "archive-name: *\\([^ \n\t]+\\)[ \t]*$" nil t)
3341       (nnheader-concat gnus-article-save-directory
3342                        (match-string 1)))))
3343
3344 (defun gnus-article-nndoc-name (group)
3345   "If GROUP is an nndoc group, return the name of the parent group."
3346   (when (eq (car (gnus-find-method-for-group group)) 'nndoc)
3347     (gnus-group-get-parameter group 'save-article-group)))
3348
3349 (defun gnus-summary-save-in-rmail (&optional filename)
3350   "Append this article to Rmail file.
3351 Optional argument FILENAME specifies file name.
3352 Directory to save to is default to `gnus-article-save-directory'."
3353   (setq filename (gnus-read-save-file-name
3354                   "Save %s in rmail file:" filename
3355                   gnus-rmail-save-name gnus-newsgroup-name
3356                   gnus-current-headers 'gnus-newsgroup-last-rmail))
3357   (gnus-eval-in-buffer-window gnus-save-article-buffer
3358     (save-excursion
3359       (save-restriction
3360         (widen)
3361         (gnus-output-to-rmail filename))))
3362   filename)
3363
3364 (defun gnus-summary-save-in-mail (&optional filename)
3365   "Append this article to Unix mail file.
3366 Optional argument FILENAME specifies file name.
3367 Directory to save to is default to `gnus-article-save-directory'."
3368   (setq filename (gnus-read-save-file-name
3369                   "Save %s in Unix mail file:" filename
3370                   gnus-mail-save-name gnus-newsgroup-name
3371                   gnus-current-headers 'gnus-newsgroup-last-mail))
3372   (gnus-eval-in-buffer-window gnus-save-article-buffer
3373     (save-excursion
3374       (save-restriction
3375         (widen)
3376         (if (and (file-readable-p filename)
3377                  (file-regular-p filename)
3378                  (mail-file-babyl-p filename))
3379             (rmail-output-to-rmail-file filename t)
3380           (gnus-output-to-mail filename)))))
3381   filename)
3382
3383 (defun gnus-summary-save-in-file (&optional filename overwrite)
3384   "Append this article to file.
3385 Optional argument FILENAME specifies file name.
3386 Directory to save to is default to `gnus-article-save-directory'."
3387   (setq filename (gnus-read-save-file-name
3388                   "Save %s in file:" filename
3389                   gnus-file-save-name gnus-newsgroup-name
3390                   gnus-current-headers 'gnus-newsgroup-last-file))
3391   (gnus-eval-in-buffer-window gnus-save-article-buffer
3392     (save-excursion
3393       (save-restriction
3394         (widen)
3395         (when (and overwrite
3396                    (file-exists-p filename))
3397           (delete-file filename))
3398         (gnus-output-to-file filename))))
3399   filename)
3400
3401 (defun gnus-summary-write-to-file (&optional filename)
3402   "Write this article to a file, overwriting it if the file exists.
3403 Optional argument FILENAME specifies file name.
3404 The directory to save in defaults to `gnus-article-save-directory'."
3405   (gnus-summary-save-in-file nil t))
3406
3407 (defun gnus-summary-save-body-in-file (&optional filename)
3408   "Append this article body to a file.
3409 Optional argument FILENAME specifies file name.
3410 The directory to save in defaults to `gnus-article-save-directory'."
3411   (setq filename (gnus-read-save-file-name
3412                   "Save %s body in file:" filename
3413                   gnus-file-save-name gnus-newsgroup-name
3414                   gnus-current-headers 'gnus-newsgroup-last-file))
3415   (gnus-eval-in-buffer-window gnus-save-article-buffer
3416     (save-excursion
3417       (save-restriction
3418         (widen)
3419         (when (article-goto-body)
3420           (narrow-to-region (point) (point-max)))
3421         (gnus-output-to-file filename))))
3422   filename)
3423
3424 (defun gnus-summary-save-in-pipe (&optional command)
3425   "Pipe this article to subprocess."
3426   (setq command
3427         (cond ((and (eq command 'default)
3428                     gnus-last-shell-command)
3429                gnus-last-shell-command)
3430               ((stringp command)
3431                command)
3432               (t (read-string
3433                   (format
3434                    "Shell command on %s: "
3435                    (if (and gnus-number-of-articles-to-be-saved
3436                             (> gnus-number-of-articles-to-be-saved 1))
3437                        (format "these %d articles"
3438                                gnus-number-of-articles-to-be-saved)
3439                      "this article"))
3440                   gnus-last-shell-command))))
3441   (when (string-equal command "")
3442     (if gnus-last-shell-command
3443         (setq command gnus-last-shell-command)
3444       (error "A command is required")))
3445   (gnus-eval-in-buffer-window gnus-article-buffer
3446     (save-restriction
3447       (widen)
3448       (shell-command-on-region (point-min) (point-max) command nil)))
3449   (setq gnus-last-shell-command command))
3450
3451 (defmacro gnus-read-string (prompt &optional initial-contents history
3452                             default-value)
3453   "Like `read-string' but allow for older XEmacsen that don't have the 5th arg."
3454   (if (and (featurep 'xemacs)
3455            (< emacs-minor-version 2))
3456       `(read-string ,prompt ,initial-contents ,history)
3457     `(read-string ,prompt ,initial-contents ,history ,default-value)))
3458
3459 (defun gnus-summary-pipe-to-muttprint (&optional command)
3460   "Pipe this article to muttprint."
3461   (setq command (gnus-read-string
3462                  "Print using command: " gnus-summary-muttprint-program
3463                  nil gnus-summary-muttprint-program))
3464   (gnus-summary-save-in-pipe command))
3465
3466 ;;; Article file names when saving.
3467
3468 (defun gnus-capitalize-newsgroup (newsgroup)
3469   "Capitalize NEWSGROUP name."
3470   (when (not (zerop (length newsgroup)))
3471     (concat (char-to-string (upcase (aref newsgroup 0)))
3472             (substring newsgroup 1))))
3473
3474 (defun gnus-Numeric-save-name (newsgroup headers &optional last-file)
3475   "Generate file name from NEWSGROUP, HEADERS, and optional LAST-FILE.
3476 If variable `gnus-use-long-file-name' is non-nil, it is ~/News/News.group/num.
3477 Otherwise, it is like ~/News/news/group/num."
3478   (let ((default
3479           (expand-file-name
3480            (concat (if (gnus-use-long-file-name 'not-save)
3481                        (gnus-capitalize-newsgroup newsgroup)
3482                      (gnus-newsgroup-directory-form newsgroup))
3483                    "/" (int-to-string (mail-header-number headers)))
3484            gnus-article-save-directory)))
3485     (if (and last-file
3486              (string-equal (file-name-directory default)
3487                            (file-name-directory last-file))
3488              (string-match "^[0-9]+$" (file-name-nondirectory last-file)))
3489         default
3490       (or last-file default))))
3491
3492 (defun gnus-numeric-save-name (newsgroup headers &optional last-file)
3493   "Generate file name from NEWSGROUP, HEADERS, and optional LAST-FILE.
3494 If variable `gnus-use-long-file-name' is non-nil, it is
3495 ~/News/news.group/num.  Otherwise, it is like ~/News/news/group/num."
3496   (let ((default
3497           (expand-file-name
3498            (concat (if (gnus-use-long-file-name 'not-save)
3499                        newsgroup
3500                      (gnus-newsgroup-directory-form newsgroup))
3501                    "/" (int-to-string (mail-header-number headers)))
3502            gnus-article-save-directory)))
3503     (if (and last-file
3504              (string-equal (file-name-directory default)
3505                            (file-name-directory last-file))
3506              (string-match "^[0-9]+$" (file-name-nondirectory last-file)))
3507         default
3508       (or last-file default))))
3509
3510 (defun gnus-plain-save-name (newsgroup headers &optional last-file)
3511   "Generate file name from NEWSGROUP, HEADERS, and optional LAST-FILE.
3512 If variable `gnus-use-long-file-name' is non-nil, it is
3513 ~/News/news.group.  Otherwise, it is like ~/News/news/group/news."
3514   (or last-file
3515       (expand-file-name
3516        (if (gnus-use-long-file-name 'not-save)
3517            newsgroup
3518          (file-relative-name
3519           (expand-file-name "news" (gnus-newsgroup-directory-form newsgroup))
3520           default-directory))
3521        gnus-article-save-directory)))
3522
3523 (defun gnus-sender-save-name (newsgroup headers &optional last-file)
3524   "Generate file name from sender."
3525   (let ((from (mail-header-from headers)))
3526     (expand-file-name
3527      (if (and from (string-match "\\([^ <]+\\)@" from))
3528          (match-string 1 from)
3529        "nobody")
3530      gnus-article-save-directory)))
3531
3532 (defun article-verify-x-pgp-sig ()
3533   "Verify X-PGP-Sig."
3534   (interactive)
3535   (if (gnus-buffer-live-p gnus-original-article-buffer)
3536       (let ((sig (with-current-buffer gnus-original-article-buffer
3537                    (gnus-fetch-field "X-PGP-Sig")))
3538             items info headers)
3539         (when (and sig
3540                    mml2015-use
3541                    (mml2015-clear-verify-function))
3542           (with-temp-buffer
3543             (insert-buffer-substring gnus-original-article-buffer)
3544             (setq items (split-string sig))
3545             (message-narrow-to-head)
3546             (let ((inhibit-point-motion-hooks t)
3547                   (case-fold-search t))
3548               ;; Don't verify multiple headers.
3549               (setq headers (mapconcat (lambda (header)
3550                                          (concat header ": "
3551                                                  (mail-fetch-field header)
3552                                                  "\n"))
3553                                        (split-string (nth 1 items) ",") "")))
3554             (delete-region (point-min) (point-max))
3555             (insert "-----BEGIN PGP SIGNED MESSAGE-----\n\n")
3556             (insert "X-Signed-Headers: " (nth 1 items) "\n")
3557             (insert headers)
3558             (widen)
3559             (forward-line)
3560             (while (not (eobp))
3561               (if (looking-at "^-")
3562                   (insert "- "))
3563               (forward-line))
3564             (insert "\n-----BEGIN PGP SIGNATURE-----\n")
3565             (insert "Version: " (car items) "\n\n")
3566             (insert (mapconcat 'identity (cddr items) "\n"))
3567             (insert "\n-----END PGP SIGNATURE-----\n")
3568             (let ((mm-security-handle (list (format "multipart/signed"))))
3569               (mml2015-clean-buffer)
3570               (let ((coding-system-for-write (or gnus-newsgroup-charset
3571                                                  'iso-8859-1)))
3572                 (funcall (mml2015-clear-verify-function)))
3573               (setq info
3574                     (or (mm-handle-multipart-ctl-parameter
3575                          mm-security-handle 'gnus-details)
3576                         (mm-handle-multipart-ctl-parameter
3577                          mm-security-handle 'gnus-info)))))
3578           (when info
3579             (let (buffer-read-only bface eface)
3580               (save-restriction
3581                 (message-narrow-to-head)
3582                 (goto-char (point-max))
3583                 (forward-line -1)
3584                 (setq bface (get-text-property (gnus-point-at-bol) 'face)
3585                       eface (get-text-property (1- (gnus-point-at-eol)) 'face))
3586                 (message-remove-header "X-Gnus-PGP-Verify")
3587                 (if (re-search-forward "^X-PGP-Sig:" nil t)
3588                     (forward-line)
3589                   (goto-char (point-max)))
3590                 (narrow-to-region (point) (point))
3591                 (insert "X-Gnus-PGP-Verify: " info "\n")
3592                 (goto-char (point-min))
3593                 (forward-line)
3594                 (while (not (eobp))
3595                   (if (not (looking-at "^[ \t]"))
3596                       (insert " "))
3597                   (forward-line))
3598                 ;; Do highlighting.
3599                 (goto-char (point-min))
3600                 (when (looking-at "\\([^:]+\\): *")
3601                   (put-text-property (match-beginning 1) (1+ (match-end 1))
3602                                      'face bface)
3603                   (put-text-property (match-end 0) (point-max)
3604                                      'face eface)))))))))
3605
3606 (defun article-verify-cancel-lock ()
3607   "Verify Cancel-Lock header."
3608   (interactive)
3609   (if (gnus-buffer-live-p gnus-original-article-buffer)
3610       (canlock-verify gnus-original-article-buffer)))
3611
3612 (defun article-monafy ()
3613   "Display body part with mona font."
3614   (interactive)
3615   (unless (if (featurep 'xemacs)
3616               (find-face 'gnus-mona-face)
3617             (facep 'gnus-mona-face))
3618     (require 'navi2ch-mona)
3619     (set-face-font (make-face 'gnus-mona-face) navi2ch-mona-font))
3620   (save-excursion
3621     (let ((buffer-read-only nil))
3622       (article-goto-body)
3623       (gnus-overlay-put
3624        (gnus-make-overlay (point) (point-max))
3625        'face 'gnus-mona-face))))
3626
3627 (eval-and-compile
3628   (mapcar
3629    (lambda (func)
3630      (let (afunc gfunc)
3631        (if (consp func)
3632            (setq afunc (car func)
3633                  gfunc (cdr func))
3634          (setq afunc func
3635                gfunc (intern (format "gnus-%s" func))))
3636        (defalias gfunc
3637          (when (fboundp afunc)
3638            `(lambda (&optional interactive &rest args)
3639               ,(documentation afunc t)
3640               (interactive (list t))
3641               (save-excursion
3642                 (set-buffer gnus-article-buffer)
3643                 (if interactive
3644                     (call-interactively ',afunc)
3645                   (apply ',afunc args))))))))
3646    '(article-hide-headers
3647      article-verify-x-pgp-sig
3648      article-verify-cancel-lock
3649      article-monafy
3650      article-hide-boring-headers
3651      article-treat-overstrike
3652      article-fill-long-lines
3653      article-capitalize-sentences
3654      article-remove-cr
3655      article-remove-leading-whitespace
3656      article-display-x-face
3657      article-display-face
3658      article-de-quoted-unreadable
3659      article-de-base64-unreadable
3660      article-decode-HZ
3661      article-wash-html
3662      article-unsplit-urls
3663      article-hide-list-identifiers
3664      article-strip-banner
3665      article-babel
3666      article-hide-pem
3667      article-hide-signature
3668      article-strip-headers-in-body
3669      article-remove-trailing-blank-lines
3670      article-strip-leading-blank-lines
3671      article-strip-multiple-blank-lines
3672      article-strip-leading-space
3673      article-strip-trailing-space
3674      article-strip-blank-lines
3675      article-strip-all-blank-lines
3676      article-date-local
3677      article-date-english
3678      article-date-iso8601
3679      article-date-original
3680      article-date-ut
3681      article-decode-mime-words
3682      article-decode-charset
3683      article-decode-encoded-words
3684      article-date-user
3685      article-date-lapsed
3686      article-emphasize
3687      article-treat-dumbquotes
3688      article-normalize-headers
3689      (article-show-all-headers . gnus-article-show-all-headers)
3690 ;;      (article-show-all . gnus-article-show-all)
3691      )))
3692 \f
3693 ;;;
3694 ;;; Gnus article mode
3695 ;;;
3696
3697 (put 'gnus-article-mode 'mode-class 'special)
3698
3699 (gnus-define-keys gnus-article-mode-map
3700   " " gnus-article-goto-next-page
3701   "\177" gnus-article-goto-prev-page
3702   [delete] gnus-article-goto-prev-page
3703   [backspace] gnus-article-goto-prev-page
3704   "\C-c^" gnus-article-refer-article
3705   "h" gnus-article-show-summary
3706   "s" gnus-article-show-summary
3707   "\C-c\C-m" gnus-article-mail
3708   "?" gnus-article-describe-briefly
3709   "e" gnus-summary-edit-article
3710   "<" beginning-of-buffer
3711   ">" end-of-buffer
3712   "\C-c\C-i" gnus-info-find-node
3713   "\C-c\C-b" gnus-bug
3714   "R" gnus-article-reply-with-original
3715   "F" gnus-article-followup-with-original
3716   "\C-hk" gnus-article-describe-key
3717   "\C-hc" gnus-article-describe-key-briefly
3718
3719   "\C-d" gnus-article-read-summary-keys
3720   "\M-*" gnus-article-read-summary-keys
3721   "\M-#" gnus-article-read-summary-keys
3722   "\M-^" gnus-article-read-summary-keys
3723   "\M-g" gnus-article-read-summary-keys)
3724
3725 ;; Define almost undefined keys to `gnus-article-read-summary-keys'.
3726 (let (keys)
3727   (let ((key 32))
3728     (while (<= key 127)
3729       (push (char-to-string key) keys)
3730       (incf key))
3731     (dolist (elem (accessible-keymaps gnus-summary-mode-map))
3732       (setq key (car elem))
3733       (when (and (> (length key) 0)
3734                  (not (eq 'menu-bar (aref key 0)))
3735                  (symbolp (lookup-key gnus-summary-mode-map key)))
3736         (push key keys))))
3737   (dolist (key keys)
3738     (unless (lookup-key gnus-article-mode-map key)
3739       (define-key gnus-article-mode-map key 'gnus-article-read-summary-keys))))
3740
3741 (defun gnus-article-make-menu-bar ()
3742   (unless (boundp 'gnus-article-commands-menu)
3743     (gnus-summary-make-menu-bar))
3744   (gnus-turn-off-edit-menu 'article)
3745   (unless (boundp 'gnus-article-article-menu)
3746     (easy-menu-define
3747      gnus-article-article-menu gnus-article-mode-map ""
3748      '("Article"
3749        ["Scroll forwards" gnus-article-goto-next-page t]
3750        ["Scroll backwards" gnus-article-goto-prev-page t]
3751        ["Show summary" gnus-article-show-summary t]
3752        ["Fetch Message-ID at point" gnus-article-refer-article t]
3753        ["Mail to address at point" gnus-article-mail t]
3754        ["Send a bug report" gnus-bug t]))
3755
3756     (easy-menu-define
3757      gnus-article-treatment-menu gnus-article-mode-map ""
3758      ;; Fixme: this should use :active (and maybe :visible).
3759      '("Treatment"
3760        ["Hide headers" gnus-article-hide-headers t]
3761        ["Hide signature" gnus-article-hide-signature t]
3762        ["Hide citation" gnus-article-hide-citation t]
3763        ["Treat overstrike" gnus-article-treat-overstrike t]
3764        ["Remove carriage return" gnus-article-remove-cr t]
3765        ["Remove leading whitespace" gnus-article-remove-leading-whitespace t]
3766        ["Treat html" gnus-article-wash-html t]
3767        ["Remove newlines from within URLs" gnus-article-unsplit-urls t]
3768        ["Decode HZ" gnus-article-decode-HZ t]))
3769
3770     ;; Note "Commands" menu is defined in gnus-sum.el for consistency
3771
3772     ;; Note "Post" menu is defined in gnus-sum.el for consistency
3773
3774     (gnus-run-hooks 'gnus-article-menu-hook)))
3775
3776 (defun gnus-article-mode ()
3777   "Major mode for displaying an article.
3778
3779 All normal editing commands are switched off.
3780
3781 The following commands are available in addition to all summary mode
3782 commands:
3783 \\<gnus-article-mode-map>
3784 \\[gnus-article-next-page]\t Scroll the article one page forwards
3785 \\[gnus-article-prev-page]\t Scroll the article one page backwards
3786 \\[gnus-article-refer-article]\t Go to the article referred to by an article id near point
3787 \\[gnus-article-show-summary]\t Display the summary buffer
3788 \\[gnus-article-mail]\t Send a reply to the address near point
3789 \\[gnus-article-describe-briefly]\t Describe the current mode briefly
3790 \\[gnus-info-find-node]\t Go to the Gnus info node"
3791   (interactive)
3792   (gnus-simplify-mode-line)
3793   (setq mode-name "Article")
3794   (setq major-mode 'gnus-article-mode)
3795   (make-local-variable 'minor-mode-alist)
3796   (unless (assq 'gnus-show-mime minor-mode-alist)
3797     (push (list 'gnus-show-mime " MIME") minor-mode-alist))
3798   (use-local-map gnus-article-mode-map)
3799   (when (gnus-visual-p 'article-menu 'menu)
3800     (gnus-article-make-menu-bar)
3801     (when gnus-summary-tool-bar-map
3802       (set (make-local-variable 'tool-bar-map) gnus-summary-tool-bar-map)))
3803   (gnus-update-format-specifications nil 'article-mode)
3804   (set (make-local-variable 'page-delimiter) gnus-page-delimiter)
3805   (make-local-variable 'gnus-page-broken)
3806   (make-local-variable 'gnus-button-marker-list)
3807   (make-local-variable 'gnus-article-current-summary)
3808   (make-local-variable 'gnus-article-mime-handles)
3809   (make-local-variable 'gnus-article-decoded-p)
3810   (make-local-variable 'gnus-article-mime-handle-alist)
3811   (make-local-variable 'gnus-article-wash-types)
3812   (make-local-variable 'gnus-article-image-alist)
3813   (make-local-variable 'gnus-article-charset)
3814   (make-local-variable 'gnus-article-ignored-charsets)
3815   (gnus-set-default-directory)
3816   (buffer-disable-undo)
3817   (setq buffer-read-only t)
3818   (set-syntax-table gnus-article-mode-syntax-table)
3819   (gnus-run-hooks 'gnus-article-mode-hook))
3820
3821 (defun gnus-article-setup-buffer ()
3822   "Initialize the article buffer."
3823   (let* ((name (if gnus-single-article-buffer "*Article*"
3824                  (concat "*Article " gnus-newsgroup-name "*")))
3825          (original
3826           (progn (string-match "\\*Article" name)
3827                  (concat " *Original Article"
3828                          (substring name (match-end 0))))))
3829     (setq gnus-article-buffer name)
3830     (setq gnus-original-article-buffer original)
3831     (setq gnus-article-mime-handle-alist nil)
3832     ;; This might be a variable local to the summary buffer.
3833     (unless gnus-single-article-buffer
3834       (save-excursion
3835         (set-buffer gnus-summary-buffer)
3836         (setq gnus-article-buffer name)
3837         (setq gnus-original-article-buffer original)
3838         (gnus-set-global-variables)))
3839     (gnus-article-setup-highlight-words)
3840     ;; Init original article buffer.
3841     (save-excursion
3842       (set-buffer (gnus-get-buffer-create gnus-original-article-buffer))
3843       (set-buffer-multibyte t)
3844       (setq major-mode 'gnus-original-article-mode)
3845       (make-local-variable 'gnus-original-article))
3846     (if (get-buffer name)
3847         (save-excursion
3848           (set-buffer name)
3849           (when (and gnus-article-edit-mode
3850                      (buffer-modified-p)
3851                      (not
3852                       (y-or-n-p "Article mode edit in progress; discard? ")))
3853             (error "Action aborted"))
3854           (set (make-local-variable 'gnus-article-edit-mode) nil)
3855           (buffer-disable-undo)
3856           (setq buffer-read-only t)
3857           ;; This list just keeps growing if we don't reset it.
3858           (setq gnus-button-marker-list nil)
3859           (unless (eq major-mode 'gnus-article-mode)
3860             (gnus-article-mode))
3861           (current-buffer))
3862       (save-excursion
3863         (set-buffer (gnus-get-buffer-create name))
3864         (gnus-article-mode)
3865         (make-local-variable 'gnus-summary-buffer)
3866         (gnus-summary-set-local-parameters gnus-newsgroup-name)
3867         (current-buffer)))))
3868
3869 ;; Set article window start at LINE, where LINE is the number of lines
3870 ;; from the head of the article.
3871 (defun gnus-article-set-window-start (&optional line)
3872   (set-window-start
3873    (gnus-get-buffer-window gnus-article-buffer t)
3874    (save-excursion
3875      (set-buffer gnus-article-buffer)
3876      (goto-char (point-min))
3877      (if (not line)
3878          (point-min)
3879        (gnus-message 6 "Moved to bookmark")
3880        (search-forward "\n\n" nil t)
3881        (forward-line line)
3882        (point)))))
3883
3884 ;;; @@ article filters
3885 ;;;
3886
3887 (defun gnus-article-display-mime-message ()
3888   "Article display method for MIME message."
3889   ;; called from `gnus-original-article-buffer'.
3890   (let (charset all-headers)
3891     (with-current-buffer gnus-summary-buffer
3892       (setq charset default-mime-charset
3893             all-headers gnus-have-all-headers))
3894     (make-local-variable 'default-mime-charset)
3895     (setq default-mime-charset charset)
3896     (with-current-buffer (get-buffer-create gnus-article-buffer)
3897       (make-local-variable 'default-mime-charset)
3898       (setq default-mime-charset charset))
3899     (mime-display-message mime-message-structure
3900                           gnus-article-buffer nil gnus-article-mode-map)
3901     (when all-headers
3902       (gnus-article-hide-headers nil -1)))
3903   (run-hooks 'gnus-mime-article-prepare-hook))
3904
3905 (defun gnus-article-display-traditional-message ()
3906   "Article display method for traditional message."
3907   (set-buffer gnus-article-buffer)
3908   (let (buffer-read-only)
3909     (erase-buffer)
3910     (insert-buffer-substring gnus-original-article-buffer)))
3911
3912 (defun gnus-article-make-full-mail-header (&optional number charset)
3913   "Create a new mail header structure in a raw article buffer."
3914   (unless (and number charset)
3915     (save-current-buffer
3916       (set-buffer gnus-summary-buffer)
3917       (unless number
3918         (setq number (or (cdr gnus-article-current) 0)))
3919       (unless charset
3920         (setq charset (or default-mime-charset 'x-ctext)))))
3921   (goto-char (point-min))
3922   (let ((header-end (if (search-forward "\n\n" nil t)
3923                         (1- (point))
3924                       (goto-char (point-max))))
3925         (chars (- (point-max) (point)))
3926         (lines (count-lines (point) (point-max)))
3927         (default-mime-charset charset)
3928         xref)
3929     (narrow-to-region (point-min) header-end)
3930     (setq xref (std11-fetch-field "xref"))
3931     (prog1
3932         (make-full-mail-header
3933          number
3934          (std11-fetch-field "subject")
3935          (std11-fetch-field "from")
3936          (std11-fetch-field "date")
3937          (std11-fetch-field "message-id")
3938          (std11-fetch-field "references")
3939          chars
3940          lines
3941          (when xref (concat "Xref: " xref)))
3942       (widen))))
3943
3944 (defun gnus-article-prepare (article &optional all-headers header)
3945   "Prepare ARTICLE in article mode buffer.
3946 ARTICLE should either be an article number or a Message-ID.
3947 If ARTICLE is an id, HEADER should be the article headers.
3948 If ALL-HEADERS is non-nil, no headers are hidden."
3949   (save-excursion
3950     ;; Make sure we start in a summary buffer.
3951     (unless (eq major-mode 'gnus-summary-mode)
3952       (set-buffer gnus-summary-buffer))
3953     (setq gnus-summary-buffer (current-buffer))
3954     (let* ((gnus-article (if header (mail-header-number header) article))
3955            (summary-buffer (current-buffer))
3956            (gnus-tmp-internal-hook gnus-article-internal-prepare-hook)
3957            (group gnus-newsgroup-name)
3958            result)
3959       (save-excursion
3960         (gnus-article-setup-buffer)
3961         (set-buffer gnus-article-buffer)
3962         ;; Deactivate active regions.
3963         (when (and (boundp 'transient-mark-mode)
3964                    transient-mark-mode)
3965           (setq mark-active nil))
3966         (if (not (setq result (let ((buffer-read-only nil))
3967                                 (gnus-request-article-this-buffer
3968                                  article group))))
3969             ;; There is no such article.
3970             (save-excursion
3971               (when (and (numberp article)
3972                          (not (memq article gnus-newsgroup-sparse)))
3973                 (setq gnus-article-current
3974                       (cons gnus-newsgroup-name article))
3975                 (set-buffer gnus-summary-buffer)
3976                 (setq gnus-current-article article)
3977                 (if (and (memq article gnus-newsgroup-undownloaded)
3978                          (not (gnus-online (gnus-find-method-for-group
3979                                             gnus-newsgroup-name))))
3980                     (progn
3981                       (gnus-summary-set-agent-mark article)
3982                       (message "Message marked for downloading"))
3983                   (gnus-summary-mark-article article gnus-canceled-mark)
3984                   (unless (memq article gnus-newsgroup-sparse)
3985                     (gnus-error 1 "No such article (may have expired or been canceled)")))))
3986           (if (or (eq result 'pseudo)
3987                   (eq result 'nneething))
3988               (progn
3989                 (save-excursion
3990                   (set-buffer summary-buffer)
3991                   (push article gnus-newsgroup-history)
3992                   (setq gnus-last-article gnus-current-article
3993                         gnus-current-article 0
3994                         gnus-current-headers nil
3995                         gnus-article-current nil)
3996                   (if (eq result 'nneething)
3997                       (gnus-configure-windows 'summary)
3998                     (gnus-configure-windows 'article))
3999                   (gnus-set-global-variables))
4000                 (let ((gnus-article-mime-handle-alist-1
4001                        gnus-article-mime-handle-alist))
4002                   (gnus-set-mode-line 'article)))
4003             ;; The result from the `request' was an actual article -
4004             ;; or at least some text that is now displayed in the
4005             ;; article buffer.
4006             (when (and (numberp article)
4007                        (not (eq article gnus-current-article)))
4008               ;; Seems like a new article has been selected.
4009               ;; `gnus-current-article' must be an article number.
4010               (save-excursion
4011                 (set-buffer summary-buffer)
4012                 (push article gnus-newsgroup-history)
4013                 (setq gnus-last-article gnus-current-article
4014                       gnus-current-article article
4015                       gnus-current-headers
4016                       (gnus-summary-article-header gnus-current-article)
4017                       gnus-article-current
4018                       (cons gnus-newsgroup-name gnus-current-article))
4019                 (unless (vectorp gnus-current-headers)
4020                   (setq gnus-current-headers nil))
4021                 (gnus-summary-goto-subject gnus-current-article)
4022                 (when (gnus-summary-show-thread)
4023                   ;; If the summary buffer really was folded, the
4024                   ;; previous goto may not actually have gone to
4025                   ;; the right article, but the thread root instead.
4026                   ;; So we go again.
4027                   (gnus-summary-goto-subject gnus-current-article))
4028                 (gnus-run-hooks 'gnus-mark-article-hook)
4029                 (gnus-set-mode-line 'summary)
4030                 (when (gnus-visual-p 'article-highlight 'highlight)
4031                   (gnus-run-hooks 'gnus-visual-mark-article-hook))
4032                 ;; Set the global newsgroup variables here.
4033                 (gnus-set-global-variables)
4034                 (setq gnus-have-all-headers
4035                       (or all-headers gnus-show-all-headers))))
4036             (save-excursion
4037               (gnus-configure-windows 'article))
4038             (when (or (numberp article)
4039                       (stringp article))
4040               (gnus-article-prepare-display)
4041               ;; Do page break.
4042               (goto-char (point-min))
4043               (setq gnus-page-broken
4044                     (when gnus-break-pages
4045                       (gnus-narrow-to-page)
4046                       t)))
4047             (let ((gnus-article-mime-handle-alist-1
4048                    gnus-article-mime-handle-alist))
4049               (gnus-set-mode-line 'article))
4050             (article-goto-body)
4051             (unless (bobp)
4052               (forward-line -1))
4053             (set-window-point (get-buffer-window (current-buffer)) (point))
4054             (gnus-configure-windows 'article)
4055             t))))))
4056
4057 (defun gnus-article-prepare-mime-display (&optional number)
4058   (goto-char (point-min))
4059   (when (re-search-forward "^[^\t ]+:" nil t)
4060     (goto-char (match-beginning 0)))
4061   (let ((entity (if (eq 1 (point-min))
4062                     (get-text-property 1 'mime-view-entity)
4063                   (get-text-property (point) 'mime-view-entity)))
4064         last-entity child-entity next type)
4065     (setq child-entity (mime-entity-children entity))
4066     (if child-entity
4067         (setq last-entity (nth (1- (length child-entity))
4068                                child-entity))
4069       (setq last-entity entity))
4070     (save-restriction
4071       (narrow-to-region (point)
4072                         (if (search-forward "\n\n" nil t)
4073                             (point)
4074                           (point-max)))
4075       (gnus-treat-article 'head)
4076       (put-text-property (point-min) (point-max) 'article-treated-header t)
4077       (goto-char (point-max)))
4078     (while (and (not (eobp)) entity)
4079       (setq next (set-marker
4080                   (make-marker)
4081                   (next-single-property-change (point) 'mime-view-entity
4082                                                nil (point-max))))
4083       (let ((types (mime-entity-content-type entity)))
4084         (while (eq 'multipart (mime-content-type-primary-type types))
4085           (setq entity (car (mime-entity-children entity))
4086                 types (mime-entity-content-type entity)))
4087         (when types
4088           (setq type (format "%s/%s"
4089                              (mime-content-type-primary-type types)
4090                              (mime-content-type-subtype types)))))
4091       (if (string-equal type "message/rfc822")
4092           (progn
4093             (setq next (point))
4094             (let ((children (mime-entity-children entity))
4095                   last-children)
4096               (when children
4097                 (setq last-children (nth (1- (length children)) children))
4098                 (while
4099                     (and
4100                      (not (eq last-children
4101                               (get-text-property next 'mime-view-entity)))
4102                      (setq next
4103                            (next-single-property-change next
4104                                                         'mime-view-entity
4105                                                         nil (point-max)))))))
4106             (setq next (next-single-property-change next 'mime-view-entity
4107                                                     nil (point-max)))
4108             (save-restriction
4109               (narrow-to-region (point) next)
4110               (gnus-article-prepare-mime-display)
4111               (goto-char (point-max)))
4112             (setq entity (get-text-property (point) 'mime-view-entity)))
4113         (save-restriction
4114           (narrow-to-region (point) next)
4115           ;; Kludge. We have to count true number, but for now,
4116           ;; part number is here only to achieve `last'.
4117           (gnus-treat-article nil 1
4118                               (if (eq entity last-entity)
4119                                   1 2)
4120                               type)
4121           (goto-char (point-max)))
4122         (setq entity (get-text-property next 'mime-view-entity))))))
4123
4124 ;;;###autoload
4125 (defun gnus-article-prepare-display ()
4126   "Make the current buffer look like a nice article."
4127   (let ((gnus-article-buffer (current-buffer))
4128         buffer-read-only)
4129     (unless (eq major-mode 'gnus-article-mode)
4130       (gnus-article-mode))
4131     (setq buffer-read-only nil
4132           gnus-button-marker-list nil
4133           gnus-article-wash-types nil
4134           gnus-article-image-alist nil)
4135     (save-restriction
4136       (widen)
4137       (static-if (featurep 'xemacs)
4138           (map-extents (lambda (extent maparg) (delete-extent extent)))
4139         (let ((lists (overlay-lists)))
4140           (dolist (overlay (nconc (car lists) (cdr lists)))
4141             (delete-overlay overlay)))))
4142     (gnus-run-hooks 'gnus-tmp-internal-hook))
4143   (let ((show-mime (unless (member gnus-newsgroup-name '("nndraft:delayed"
4144                                                          "nndraft:drafts"))
4145                      gnus-show-mime))
4146         (inhibit-read-only t))
4147     (set-buffer gnus-original-article-buffer)
4148     ;; Display message.
4149     (setq mime-message-structure gnus-current-headers)
4150     (mime-buffer-entity-set-buffer-internal mime-message-structure
4151                                             gnus-original-article-buffer)
4152     (mime-entity-set-representation-type-internal mime-message-structure
4153                                                   'mime-buffer-entity)
4154     (luna-send mime-message-structure 'initialize-instance
4155                mime-message-structure)
4156     (if show-mime
4157         (let (mime-display-header-hook mime-display-text/plain-hook)
4158           (funcall gnus-article-display-method-for-mime))
4159       (funcall gnus-article-display-method-for-traditional))
4160     ;; Call the treatment functions.
4161     (save-restriction
4162       (widen)
4163       (if show-mime
4164           (gnus-article-prepare-mime-display)
4165         (narrow-to-region (goto-char (point-min))
4166                           (if (search-forward "\n\n" nil t)
4167                               (point)
4168                             (point-max)))
4169         (gnus-treat-article 'head)
4170         (put-text-property (point-min) (point-max) 'article-treated-header t)
4171         (goto-char (point-max))
4172         (widen)
4173         (narrow-to-region (point) (point-max))
4174         (gnus-treat-article nil))
4175       (put-text-property (point-min) (point-max) 'read-only nil)))
4176   (gnus-run-hooks 'gnus-article-prepare-hook))
4177
4178 (defun gnus-article-decode-article-as-default-mime-charset ()
4179   "Decode an article as `default-mime-charset'.  It won't work if the
4180 value of the variable `gnus-show-mime' is non-nil."
4181   (unless (or gnus-show-mime
4182               (member gnus-newsgroup-name '("nndraft:delayed"
4183                                             "nndraft:drafts")))
4184     (set (make-local-variable 'default-mime-charset)
4185          (with-current-buffer gnus-summary-buffer
4186            default-mime-charset))
4187     (decode-mime-charset-region (point-min) (point-max)
4188                                 default-mime-charset)))
4189
4190 ;;;
4191 ;;; Gnus MIME viewing functions
4192 ;;;
4193
4194 (defvar gnus-mime-button-line-format "%{%([%p. %d%T]%)%}%e\n"
4195   "Format of the MIME buttons.
4196
4197 Valid specifiers include:
4198 %t  The MIME type
4199 %T  MIME type, along with additional info
4200 %n  The `name' parameter
4201 %d  The description, if any
4202 %l  The length of the encoded part
4203 %p  The part identifier number
4204 %e  Dots if the part isn't displayed
4205
4206 General format specifiers can also be used.  See Info node
4207 `(gnus)Formatting Variables'.")
4208
4209 (defvar gnus-mime-button-line-format-alist
4210   '((?t gnus-tmp-type ?s)
4211     (?T gnus-tmp-type-long ?s)
4212     (?n gnus-tmp-name ?s)
4213     (?d gnus-tmp-description ?s)
4214     (?p gnus-tmp-id ?s)
4215     (?l gnus-tmp-length ?d)
4216     (?e gnus-tmp-dots ?s)))
4217
4218 (defvar gnus-mime-button-commands
4219   '((gnus-article-press-button "\r" "Toggle Display")
4220     (gnus-mime-view-part "v" "View Interactively...")
4221     (gnus-mime-view-part-as-type "t" "View As Type...")
4222     (gnus-mime-view-part-as-charset "C" "View As charset...")
4223     (gnus-mime-save-part "o" "Save...")
4224     (gnus-mime-save-part-and-strip "\C-o" "Save and Strip")
4225     (gnus-mime-delete-part "d" "Delete part")
4226     (gnus-mime-copy-part "c" "View As Text, In Other Buffer")
4227     (gnus-mime-inline-part "i" "View As Text, In This Buffer")
4228     (gnus-mime-view-part-internally "E" "View Internally")
4229     (gnus-mime-view-part-externally "e" "View Externally")
4230     (gnus-mime-print-part "p" "Print")
4231     (gnus-mime-pipe-part "|" "Pipe To Command...")
4232     (gnus-mime-action-on-part "." "Take action on the part...")))
4233
4234 (defun gnus-article-mime-part-status ()
4235   (with-current-buffer gnus-article-buffer
4236     (let ((entity (get-text-property (point-min) 'mime-view-entity))
4237           children)
4238       (if (and entity
4239                (setq children (mime-entity-children entity))
4240                (setq children (length children)))
4241           (if (eq 1 children)
4242               " (1 part)"
4243             (format " (%d parts)" children))
4244         ""))))
4245
4246 (defvar gnus-mime-button-map
4247   (let ((map (make-sparse-keymap)))
4248     (unless (>= (string-to-number emacs-version) 21)
4249       ;; XEmacs doesn't care.
4250       (set-keymap-parent map gnus-article-mode-map))
4251     (define-key map gnus-mouse-2 'gnus-article-push-button)
4252     (define-key map gnus-down-mouse-3 'gnus-mime-button-menu)
4253     (dolist (c gnus-mime-button-commands)
4254       (define-key map (cadr c) (car c)))
4255     map))
4256
4257 (easy-menu-define
4258   gnus-mime-button-menu gnus-mime-button-map "MIME button menu."
4259   `("MIME Part"
4260     ,@(mapcar (lambda (c)
4261                 (vector (caddr c) (car c) :enable t))
4262               gnus-mime-button-commands)))
4263
4264 (eval-when-compile
4265   (define-compiler-macro popup-menu (&whole form
4266                                             menu &optional position prefix)
4267     (if (and (fboundp 'popup-menu)
4268              (not (memq 'popup-menu (assoc "lmenu" load-history))))
4269         form
4270       ;; Gnus is probably running under Emacs 20.
4271       `(let* ((menu (cdr ,menu))
4272               (response (x-popup-menu
4273                          t (list (car menu)
4274                                  (cons "" (mapcar (lambda (c)
4275                                                     (cons (caddr c) (car c)))
4276                                                   (cdr menu)))))))
4277          (if response
4278              (call-interactively (nth 3 (assq response menu))))))))
4279
4280 (defun gnus-mime-button-menu (event prefix)
4281  "Construct a context-sensitive menu of MIME commands."
4282  (interactive "e\nP")
4283  (save-window-excursion
4284    (let ((pos (event-start event)))
4285      (select-window (posn-window pos))
4286      (goto-char (posn-point pos))
4287      (gnus-article-check-buffer)
4288      (popup-menu gnus-mime-button-menu nil prefix))))
4289
4290 (defun gnus-mime-view-all-parts (&optional handles)
4291   "View all the MIME parts."
4292   (interactive)
4293   (save-current-buffer
4294     (set-buffer gnus-article-buffer)
4295     (let ((handles (or handles gnus-article-mime-handles))
4296           (mail-parse-charset gnus-newsgroup-charset)
4297           (mail-parse-ignored-charsets
4298            (with-current-buffer gnus-summary-buffer
4299              gnus-newsgroup-ignored-charsets)))
4300       (when handles
4301         (mm-remove-parts handles)
4302         (goto-char (point-min))
4303         (or (search-forward "\n\n") (goto-char (point-max)))
4304         (let (buffer-read-only)
4305           (delete-region (point) (point-max))
4306           (mm-display-parts handles))))))
4307
4308 (defun gnus-mime-save-part-and-strip ()
4309   "Save the MIME part under point then replace it with an external body."
4310   (interactive)
4311   (gnus-article-check-buffer)
4312   (let* ((data (get-text-property (point) 'gnus-data))
4313          file param
4314          (handles gnus-article-mime-handles))
4315     (if (mm-multiple-handles gnus-article-mime-handles)
4316         (error "This function is not implemented"))
4317     (setq file (and data (mm-save-part data)))
4318     (when file
4319       (with-current-buffer (mm-handle-buffer data)
4320         (erase-buffer)
4321         (insert "Content-Type: " (mm-handle-media-type data))
4322         (mml-insert-parameter-string (cdr (mm-handle-type data))
4323                                      '(charset))
4324         (insert "\n")
4325         (insert "Content-ID: " (message-make-message-id) "\n")
4326         (insert "Content-Transfer-Encoding: binary\n")
4327         (insert "\n"))
4328       (setcdr data
4329               (cdr (mm-make-handle nil
4330                                    `("message/external-body"
4331                                      (access-type . "LOCAL-FILE")
4332                                      (name . ,file)))))
4333       (set-buffer gnus-summary-buffer)
4334       (gnus-article-edit-article
4335        `(lambda ()
4336           (erase-buffer)
4337           (let ((mail-parse-charset (or gnus-article-charset
4338                                         ',gnus-newsgroup-charset))
4339                 (mail-parse-ignored-charsets
4340                  (or gnus-article-ignored-charsets
4341                      ',gnus-newsgroup-ignored-charsets))
4342                 (mbl mml-buffer-list))
4343             (setq mml-buffer-list nil)
4344             (insert-buffer gnus-original-article-buffer)
4345             (mime-to-mml ',handles)
4346             (setq gnus-article-mime-handles nil)
4347             (let ((mbl1 mml-buffer-list))
4348               (setq mml-buffer-list mbl)
4349               (set (make-local-variable 'mml-buffer-list) mbl1))
4350             (gnus-make-local-hook 'kill-buffer-hook)
4351             (add-hook 'kill-buffer-hook 'mml-destroy-buffers t t)))
4352        `(lambda (no-highlight)
4353           (let ((mail-parse-charset (or gnus-article-charset
4354                                         ',gnus-newsgroup-charset))
4355                 (message-options message-options)
4356                 (message-options-set-recipient)
4357                 (mail-parse-ignored-charsets
4358                  (or gnus-article-ignored-charsets
4359                      ',gnus-newsgroup-ignored-charsets)))
4360             (mml-to-mime)
4361             (mml-destroy-buffers)
4362             (remove-hook 'kill-buffer-hook
4363                          'mml-destroy-buffers t)
4364             (kill-local-variable 'mml-buffer-list))
4365           (gnus-summary-edit-article-done
4366            ,(or (mail-header-references gnus-current-headers) "")
4367            ,(gnus-group-read-only-p)
4368            ,gnus-summary-buffer no-highlight))))))
4369
4370 (defun gnus-mime-delete-part ()
4371   "Delete the MIME part under point.
4372 Replace it with some information about the removed part."
4373   (interactive)
4374   (gnus-article-check-buffer)
4375   (unless (and gnus-novice-user
4376                (not (gnus-yes-or-no-p
4377                      "Really delete attachment forever? ")))
4378     (let* ((data (get-text-property (point) 'gnus-data))
4379            (handles gnus-article-mime-handles)
4380            (none "(none)")
4381            (description
4382             (or
4383              (mail-decode-encoded-word-string (or (mm-handle-description data)
4384                                                   none))))
4385            (filename
4386             (or (mail-content-type-get (mm-handle-disposition data) 'filename)
4387                 none))
4388            (type (mm-handle-media-type data)))
4389       (if (mm-multiple-handles gnus-article-mime-handles)
4390           (error "This function is not implemented"))
4391       (with-current-buffer (mm-handle-buffer data)
4392         (let ((bsize (format "%s" (buffer-size))))
4393           (erase-buffer)
4394           (insert
4395            (concat
4396             "<#part type=text/plain nofile=yes disposition=attachment"
4397             " description=\"Deleted attachment (" bsize " Byte)\">"
4398             ",----\n"
4399             "| The following attachment has been deleted:\n"
4400             "|\n"
4401             "| Type:           " type "\n"
4402             "| Filename:       " filename "\n"
4403             "| Size (encoded): " bsize " Byte\n"
4404             "| Description:    " description "\n"
4405             "`----\n"
4406             "<#/part>"))
4407           (setcdr data
4408                   (cdr (mm-make-handle nil `("text/plain"))))))
4409       (set-buffer gnus-summary-buffer)
4410       ;; FIXME: maybe some of the following code (borrowed from
4411       ;; `gnus-mime-save-part-and-strip') isn't necessary?
4412       (gnus-article-edit-article
4413        `(lambda ()
4414           (erase-buffer)
4415           (let ((mail-parse-charset (or gnus-article-charset
4416                                         ',gnus-newsgroup-charset))
4417                 (mail-parse-ignored-charsets
4418                  (or gnus-article-ignored-charsets
4419                      ',gnus-newsgroup-ignored-charsets))
4420                 (mbl mml-buffer-list))
4421             (setq mml-buffer-list nil)
4422             (insert-buffer gnus-original-article-buffer)
4423             (mime-to-mml ',handles)
4424             (setq gnus-article-mime-handles nil)
4425             (let ((mbl1 mml-buffer-list))
4426               (setq mml-buffer-list mbl)
4427               (set (make-local-variable 'mml-buffer-list) mbl1))
4428             (gnus-make-local-hook 'kill-buffer-hook)
4429             (add-hook 'kill-buffer-hook 'mml-destroy-buffers t t)))
4430        `(lambda (no-highlight)
4431           (let ((mail-parse-charset (or gnus-article-charset
4432                                         ',gnus-newsgroup-charset))
4433                 (message-options message-options)
4434                 (message-options-set-recipient)
4435                 (mail-parse-ignored-charsets
4436                  (or gnus-article-ignored-charsets
4437                      ',gnus-newsgroup-ignored-charsets)))
4438             (mml-to-mime)
4439             (mml-destroy-buffers)
4440             (remove-hook 'kill-buffer-hook
4441                          'mml-destroy-buffers t)
4442             (kill-local-variable 'mml-buffer-list))
4443           (gnus-summary-edit-article-done
4444            ,(or (mail-header-references gnus-current-headers) "")
4445            ,(gnus-group-read-only-p)
4446            ,gnus-summary-buffer no-highlight)))))
4447   ;; Not in `gnus-mime-save-part-and-strip':
4448   (gnus-article-edit-done)
4449   (gnus-summary-expand-window)
4450   (gnus-summary-show-article))
4451
4452 (defun gnus-mime-save-part ()
4453   "Save the MIME part under point."
4454   (interactive)
4455   (gnus-article-check-buffer)
4456   (let ((data (get-text-property (point) 'gnus-data)))
4457     (when data
4458       (mm-save-part data))))
4459
4460 (defun gnus-mime-pipe-part ()
4461   "Pipe the MIME part under point to a process."
4462   (interactive)
4463   (gnus-article-check-buffer)
4464   (let ((data (get-text-property (point) 'gnus-data)))
4465     (when data
4466       (mm-pipe-part data))))
4467
4468 (defun gnus-mime-view-part ()
4469   "Interactively choose a viewing method for the MIME part under point."
4470   (interactive)
4471   (gnus-article-check-buffer)
4472   (let ((data (get-text-property (point) 'gnus-data)))
4473     (when data
4474       (setq gnus-article-mime-handles
4475             (mm-merge-handles
4476              gnus-article-mime-handles (setq data (copy-sequence data))))
4477       (mm-interactively-view-part data))))
4478
4479 (defun gnus-mime-view-part-as-type-internal ()
4480   (gnus-article-check-buffer)
4481   (let* ((name (mail-content-type-get
4482                 (mm-handle-type (get-text-property (point) 'gnus-data))
4483                 'name))
4484          (def-type (and name (mm-default-file-encoding name))))
4485     (and def-type (cons def-type 0))))
4486
4487 (defun gnus-mime-view-part-as-type (&optional mime-type)
4488   "Choose a MIME media type, and view the part as such."
4489   (interactive)
4490   (unless mime-type
4491     (setq mime-type (completing-read
4492                      "View as MIME type: "
4493                      (mapcar #'list (mailcap-mime-types))
4494                      nil nil
4495                      (gnus-mime-view-part-as-type-internal))))
4496   (gnus-article-check-buffer)
4497   (let ((handle (get-text-property (point) 'gnus-data)))
4498     (when handle
4499       (setq handle
4500             (mm-make-handle (mm-handle-buffer handle)
4501                             (cons mime-type (cdr (mm-handle-type handle)))
4502                             (mm-handle-encoding handle)
4503                             (mm-handle-undisplayer handle)
4504                             (mm-handle-disposition handle)
4505                             (mm-handle-description handle)
4506                             nil
4507                             (mm-handle-id handle)))
4508       (setq gnus-article-mime-handles
4509             (mm-merge-handles gnus-article-mime-handles handle))
4510       (gnus-mm-display-part handle))))
4511
4512 (eval-when-compile
4513   (require 'jka-compr))
4514
4515 ;; jka-compr.el uses a "sh -c" to direct stderr to err-file, but these days
4516 ;; emacs can do that itself.
4517 ;;
4518 (defun gnus-mime-jka-compr-maybe-uncompress ()
4519   "Uncompress the current buffer if `auto-compression-mode' is enabled.
4520 The uncompress method used is derived from `buffer-file-name'."
4521   (when (and (fboundp 'jka-compr-installed-p)
4522              (jka-compr-installed-p))
4523     (let ((info (jka-compr-get-compression-info buffer-file-name)))
4524       (when info
4525         (let ((basename (file-name-nondirectory buffer-file-name))
4526               (args     (jka-compr-info-uncompress-args    info))
4527               (prog     (jka-compr-info-uncompress-program info))
4528               (message  (jka-compr-info-uncompress-message info))
4529               (err-file (jka-compr-make-temp-name)))
4530           (if message
4531               (message "%s %s..." message basename))
4532           (unwind-protect
4533               (unless (memq (apply 'call-process-region
4534                                    (point-min) (point-max)
4535                                    prog
4536                                    t (list t err-file) nil
4537                                    args)
4538                             jka-compr-acceptable-retval-list)
4539                 (jka-compr-error prog args basename message err-file))
4540             (jka-compr-delete-temp-file err-file)))))))
4541
4542 (defun gnus-mime-copy-part (&optional handle)
4543   "Put the MIME part under point into a new buffer.
4544 If `auto-compression-mode' is enabled, compressed files like .gz and .bz2
4545 are decompressed."
4546   (interactive)
4547   (gnus-article-check-buffer)
4548   (let* ((handle (or handle (get-text-property (point) 'gnus-data)))
4549          (contents (and handle (mm-get-part handle)))
4550          (base (and handle
4551                     (file-name-nondirectory
4552                      (or
4553                       (mail-content-type-get (mm-handle-type handle) 'name)
4554                       (mail-content-type-get (mm-handle-disposition handle)
4555                                              'filename)
4556                       "*decoded*"))))
4557          (buffer (and base (generate-new-buffer base))))
4558     (when contents
4559       (switch-to-buffer buffer)
4560       (insert contents)
4561       ;; We do it this way to make `normal-mode' set the appropriate mode.
4562       (unwind-protect
4563           (progn
4564             (setq buffer-file-name (expand-file-name base))
4565             (gnus-mime-jka-compr-maybe-uncompress)
4566             (normal-mode))
4567         (setq buffer-file-name nil))
4568       (goto-char (point-min)))))
4569
4570 (defun gnus-mime-print-part (&optional handle filename)
4571   "Print the MIME part under point."
4572   (interactive (list nil (ps-print-preprint current-prefix-arg)))
4573   (gnus-article-check-buffer)
4574   (let* ((handle (or handle (get-text-property (point) 'gnus-data)))
4575          (contents (and handle (mm-get-part handle)))
4576          (file (mm-make-temp-file (expand-file-name "mm." mm-tmp-directory)))
4577          (printer (mailcap-mime-info (mm-handle-media-type handle) "print")))
4578     (when contents
4579         (if printer
4580             (unwind-protect
4581                 (progn
4582                   (mm-save-part-to-file handle file)
4583                   (call-process shell-file-name nil
4584                                 (generate-new-buffer " *mm*")
4585                                 nil
4586                                 shell-command-switch
4587                                 (mm-mailcap-command
4588                                  printer file (mm-handle-type handle))))
4589               (delete-file file))
4590           (with-temp-buffer
4591             (insert contents)
4592             (gnus-print-buffer))
4593           (ps-despool filename)))))
4594
4595 (defun gnus-mime-inline-part (&optional handle arg)
4596   "Insert the MIME part under point into the current buffer."
4597   (interactive (list nil current-prefix-arg))
4598   (gnus-article-check-buffer)
4599   (let* ((handle (or handle (get-text-property (point) 'gnus-data)))
4600          contents charset
4601          (b (point))
4602          buffer-read-only)
4603     (when handle
4604       (if (and (not arg) (mm-handle-undisplayer handle))
4605           (mm-remove-part handle)
4606         (setq contents (mm-get-part handle))
4607         (cond
4608          ((not arg)
4609           (setq charset (or (mail-content-type-get
4610                              (mm-handle-type handle) 'charset)
4611                             gnus-newsgroup-charset)))
4612          ((numberp arg)
4613           (if (mm-handle-undisplayer handle)
4614               (mm-remove-part handle))
4615           (setq charset
4616                 (or (cdr (assq arg
4617                                gnus-summary-show-article-charset-alist))
4618                     (mm-read-coding-system "Charset: ")))))
4619         (forward-line 2)
4620         (mm-insert-inline handle
4621                           (if (and charset
4622                                    (setq charset (mm-charset-to-coding-system
4623                                                   charset))
4624                                    (not (eq charset 'ascii)))
4625                               (mm-decode-coding-string contents charset)
4626                             contents))
4627         (goto-char b)))))
4628
4629 (defun gnus-mime-view-part-as-charset (&optional handle arg)
4630   "Insert the MIME part under point into the current buffer using the
4631 specified charset."
4632   (interactive (list nil current-prefix-arg))
4633   (gnus-article-check-buffer)
4634   (let* ((handle (or handle (get-text-property (point) 'gnus-data)))
4635          contents charset
4636          (b (point))
4637          buffer-read-only)
4638     (when handle
4639       (if (mm-handle-undisplayer handle)
4640           (mm-remove-part handle))
4641       (let ((gnus-newsgroup-charset
4642              (or (cdr (assq arg
4643                             gnus-summary-show-article-charset-alist))
4644                  (mm-read-coding-system "Charset: ")))
4645             (gnus-newsgroup-ignored-charsets 'gnus-all))
4646         (gnus-article-press-button)))))
4647
4648 (defun gnus-mime-view-part-externally (&optional handle)
4649   "View the MIME part under point with an external viewer."
4650   (interactive)
4651   (gnus-article-check-buffer)
4652   (let* ((handle (or handle (get-text-property (point) 'gnus-data)))
4653          (mm-user-display-methods nil)
4654          (mm-inlined-types nil)
4655          (mail-parse-charset gnus-newsgroup-charset)
4656          (mail-parse-ignored-charsets
4657           (save-excursion (set-buffer gnus-summary-buffer)
4658                           gnus-newsgroup-ignored-charsets)))
4659     (when handle
4660       (if (mm-handle-undisplayer handle)
4661           (mm-remove-part handle)
4662         (mm-display-part handle)))))
4663
4664 (defun gnus-mime-view-part-internally (&optional handle)
4665   "View the MIME part under point with an internal viewer.
4666 If no internal viewer is available, use an external viewer."
4667   (interactive)
4668   (gnus-article-check-buffer)
4669   (let* ((handle (or handle (get-text-property (point) 'gnus-data)))
4670          (mm-inlined-types '(".*"))
4671          (mm-inline-large-images t)
4672          (mail-parse-charset gnus-newsgroup-charset)
4673          (mail-parse-ignored-charsets
4674           (save-excursion (set-buffer gnus-summary-buffer)
4675                           gnus-newsgroup-ignored-charsets))
4676          buffer-read-only)
4677     (when handle
4678       (if (mm-handle-undisplayer handle)
4679           (mm-remove-part handle)
4680         (mm-display-part handle)))))
4681
4682 (defun gnus-mime-action-on-part (&optional action)
4683   "Do something with the MIME attachment at \(point\)."
4684   (interactive
4685    (list (completing-read "Action: " gnus-mime-action-alist nil t)))
4686   (gnus-article-check-buffer)
4687   (let ((action-pair (assoc action gnus-mime-action-alist)))
4688     (if action-pair
4689         (funcall (cdr action-pair)))))
4690
4691 (defun gnus-article-part-wrapper (n function)
4692   (save-current-buffer
4693     (set-buffer gnus-article-buffer)
4694     (when (> n (length gnus-article-mime-handle-alist))
4695       (error "No such part"))
4696     (gnus-article-goto-part n)
4697     (let ((handle (cdr (assq n gnus-article-mime-handle-alist))))
4698       (funcall function handle))))
4699
4700 (defun gnus-article-pipe-part (n)
4701   "Pipe MIME part N, which is the numerical prefix."
4702   (interactive "p")
4703   (gnus-article-part-wrapper n 'mm-pipe-part))
4704
4705 (defun gnus-article-save-part (n)
4706   "Save MIME part N, which is the numerical prefix."
4707   (interactive "p")
4708   (gnus-article-part-wrapper n 'mm-save-part))
4709
4710 (defun gnus-article-interactively-view-part (n)
4711   "View MIME part N interactively, which is the numerical prefix."
4712   (interactive "p")
4713   (gnus-article-part-wrapper n 'mm-interactively-view-part))
4714
4715 (defun gnus-article-copy-part (n)
4716   "Copy MIME part N, which is the numerical prefix."
4717   (interactive "p")
4718   (gnus-article-part-wrapper n 'gnus-mime-copy-part))
4719
4720 (defun gnus-article-view-part-as-charset (n)
4721   "Copy MIME part N, which is the numerical prefix."
4722   (interactive "p")
4723   (gnus-article-part-wrapper n 'gnus-mime-view-part-as-charset))
4724
4725 (defun gnus-article-view-part-externally (n)
4726   "View MIME part N externally, which is the numerical prefix."
4727   (interactive "p")
4728   (gnus-article-part-wrapper n 'gnus-mime-view-part-externally))
4729
4730 (defun gnus-article-inline-part (n)
4731   "Inline MIME part N, which is the numerical prefix."
4732   (interactive "p")
4733   (gnus-article-part-wrapper n 'gnus-mime-inline-part))
4734
4735 (defun gnus-article-mime-match-handle-first (condition)
4736   (if condition
4737       (let ((alist gnus-article-mime-handle-alist) ihandle n)
4738         (while (setq ihandle (pop alist))
4739           (if (and (cond
4740                     ((functionp condition)
4741                      (funcall condition (cdr ihandle)))
4742                     ((eq condition 'undisplayed)
4743                      (not (or (mm-handle-undisplayer (cdr ihandle))
4744                               (equal (mm-handle-media-type (cdr ihandle))
4745                                      "multipart/alternative"))))
4746                     ((eq condition 'undisplayed-alternative)
4747                      (not (mm-handle-undisplayer (cdr ihandle))))
4748                     (t t))
4749                    (gnus-article-goto-part (car ihandle))
4750                    (or (not n) (< (car ihandle) n)))
4751               (setq n (car ihandle))))
4752         (or n 1))
4753     1))
4754
4755 (defun gnus-article-view-part (&optional n)
4756   "View MIME part N, which is the numerical prefix."
4757   (interactive "P")
4758   (save-current-buffer
4759     (set-buffer gnus-article-buffer)
4760     (or (numberp n) (setq n (gnus-article-mime-match-handle-first
4761                              gnus-article-mime-match-handle-function)))
4762     (when (> n (length gnus-article-mime-handle-alist))
4763       (error "No such part"))
4764     (let ((handle (cdr (assq n gnus-article-mime-handle-alist))))
4765       (when (gnus-article-goto-part n)
4766         (if (equal (car handle) "multipart/alternative")
4767             (gnus-article-press-button)
4768           (when (eq (gnus-mm-display-part handle) 'internal)
4769             (gnus-set-window-start)))))))
4770
4771 (defsubst gnus-article-mime-total-parts ()
4772   (if (bufferp (car gnus-article-mime-handles))
4773       1 ;; single part
4774     (1- (length gnus-article-mime-handles))))
4775
4776 (defun gnus-mm-display-part (handle)
4777   "Display HANDLE and fix MIME button."
4778   (let ((id (get-text-property (point) 'gnus-part))
4779         (point (point))
4780         buffer-read-only)
4781     (forward-line 1)
4782     (prog1
4783         (let ((window (selected-window))
4784               (mail-parse-charset gnus-newsgroup-charset)
4785               (mail-parse-ignored-charsets
4786                (if (gnus-buffer-live-p gnus-summary-buffer)
4787                    (save-excursion
4788                      (set-buffer gnus-summary-buffer)
4789                      gnus-newsgroup-ignored-charsets)
4790                  nil)))
4791           (save-excursion
4792             (unwind-protect
4793                 (let ((win (gnus-get-buffer-window (current-buffer) t))
4794                       (beg (point)))
4795                   (when win
4796                     (select-window win))
4797                   (goto-char point)
4798                   (forward-line)
4799                   (if (mm-handle-displayed-p handle)
4800                       ;; This will remove the part.
4801                       (mm-display-part handle)
4802                     (save-restriction
4803                       (narrow-to-region (point)
4804                                         (if (eobp) (point) (1+ (point))))
4805                       (mm-display-part handle)
4806                       ;; We narrow to the part itself and
4807                       ;; then call the treatment functions.
4808                       (goto-char (point-min))
4809                       (forward-line 1)
4810                       (narrow-to-region (point) (point-max))
4811                       (gnus-treat-article
4812                        nil id
4813                        (gnus-article-mime-total-parts)
4814                        (mm-handle-media-type handle)))))
4815               (if (window-live-p window)
4816                   (select-window window)))))
4817       (goto-char point)
4818       (gnus-delete-line)
4819       (gnus-insert-mime-button
4820        handle id (list (mm-handle-displayed-p handle)))
4821       (goto-char point))))
4822
4823 (defun gnus-article-goto-part (n)
4824   "Go to MIME part N."
4825   (gnus-goto-char (text-property-any (point-min) (point-max) 'gnus-part n)))
4826
4827 (defun gnus-insert-mime-button (handle gnus-tmp-id &optional displayed)
4828   (let ((gnus-tmp-name
4829          (or (mail-content-type-get (mm-handle-type handle) 'name)
4830              (mail-content-type-get (mm-handle-disposition handle) 'filename)
4831              (mail-content-type-get (mm-handle-type handle) 'url)
4832              ""))
4833         (gnus-tmp-type (mm-handle-media-type handle))
4834         (gnus-tmp-description
4835          (mail-decode-encoded-word-string (or (mm-handle-description handle)
4836                                               "")))
4837         (gnus-tmp-dots
4838          (if (if displayed (car displayed)
4839                (mm-handle-displayed-p handle))
4840              "" "..."))
4841         (gnus-tmp-length (with-current-buffer (mm-handle-buffer handle)
4842                            (buffer-size)))
4843         gnus-tmp-type-long b e)
4844     (when (string-match ".*/" gnus-tmp-name)
4845       (setq gnus-tmp-name (replace-match "" t t gnus-tmp-name)))
4846     (setq gnus-tmp-type-long (concat gnus-tmp-type
4847                                      (and (not (equal gnus-tmp-name ""))
4848                                           (concat "; " gnus-tmp-name))))
4849     (unless (equal gnus-tmp-description "")
4850       (setq gnus-tmp-type-long (concat " --- " gnus-tmp-type-long)))
4851     (unless (bolp)
4852       (insert "\n"))
4853     (setq b (point))
4854     (gnus-eval-format
4855      gnus-mime-button-line-format gnus-mime-button-line-format-alist
4856      `(,@(gnus-local-map-property gnus-mime-button-map)
4857          gnus-callback gnus-mm-display-part
4858          gnus-part ,gnus-tmp-id
4859          article-type annotation
4860          gnus-data ,handle))
4861     (setq e (if (bolp)
4862                 ;; Exclude a newline.
4863                 (1- (point))
4864               (point)))
4865     (widget-convert-button
4866      'link b e
4867      :mime-handle handle
4868      :action 'gnus-widget-press-button
4869      :button-keymap gnus-mime-button-map
4870      :help-echo
4871      (lambda (widget/window &optional overlay pos)
4872        ;; Needed to properly clear the message due to a bug in
4873        ;; wid-edit (XEmacs only).
4874        (if (boundp 'help-echo-owns-message)
4875            (setq help-echo-owns-message t))
4876        (format
4877         "%S: %s the MIME part; %S: more options"
4878         (aref gnus-mouse-2 0)
4879         ;; XEmacs will get a single widget arg; Emacs 21 will get
4880         ;; window, overlay, position.
4881         (if (mm-handle-displayed-p
4882              (if overlay
4883                  (with-current-buffer (gnus-overlay-buffer overlay)
4884                    (widget-get (widget-at (gnus-overlay-start overlay))
4885                                :mime-handle))
4886                (widget-get widget/window :mime-handle)))
4887             "hide" "show")
4888         (aref gnus-down-mouse-3 0))))))
4889
4890 (defun gnus-widget-press-button (elems el)
4891   (goto-char (widget-get elems :from))
4892   (gnus-article-press-button))
4893
4894 (defvar gnus-displaying-mime nil)
4895
4896 (defun gnus-display-mime (&optional ihandles)
4897   "Display the MIME parts."
4898   (save-excursion
4899     (save-selected-window
4900       (let ((window (get-buffer-window gnus-article-buffer))
4901             (point (point)))
4902         (when window
4903           (select-window window)
4904           ;; We have to do this since selecting the window
4905           ;; may change the point.  So we set the window point.
4906           (set-window-point window point)))
4907       (let* ((handles (or ihandles
4908                           (mm-dissect-buffer nil gnus-article-loose-mime)
4909                           (and gnus-article-emulate-mime
4910                                (mm-uu-dissect))))
4911              buffer-read-only handle name type b e display)
4912         (when (and (not ihandles)
4913                    (not gnus-displaying-mime))
4914           ;; Top-level call; we clean up.
4915           (when gnus-article-mime-handles
4916             (mm-destroy-parts gnus-article-mime-handles)
4917             (setq gnus-article-mime-handle-alist nil));; A trick.
4918           (setq gnus-article-mime-handles handles)
4919           ;; We allow users to glean info from the handles.
4920           (when gnus-article-mime-part-function
4921             (gnus-mime-part-function handles)))
4922         (if (and handles
4923                  (or (not (stringp (car handles)))
4924                      (cdr handles)))
4925             (progn
4926               (when (and (not ihandles)
4927                          (not gnus-displaying-mime))
4928                 ;; Clean up for mime parts.
4929                 (article-goto-body)
4930                 (delete-region (point) (point-max)))
4931               (let ((gnus-displaying-mime t))
4932                 (gnus-mime-display-part handles)))
4933           (save-restriction
4934             (article-goto-body)
4935             (narrow-to-region (point) (point-max))
4936             (gnus-treat-article nil 1 1)
4937             (widen)))
4938         (unless ihandles
4939           ;; Highlight the headers.
4940           (save-excursion
4941             (save-restriction
4942               (article-goto-body)
4943               (narrow-to-region (point-min) (point))
4944               (gnus-treat-article 'head))))))))
4945
4946 (defcustom gnus-mime-display-multipart-as-mixed nil
4947   "Display \"multipart\" parts as  \"multipart/mixed\".
4948
4949 If t, it overrides nil values of
4950 `gnus-mime-display-multipart-alternative-as-mixed' and
4951 `gnus-mime-display-multipart-related-as-mixed'."
4952   :group 'gnus-article-mime
4953   :type 'boolean)
4954
4955 (defcustom gnus-mime-display-multipart-alternative-as-mixed nil
4956   "Display \"multipart/alternative\" parts as  \"multipart/mixed\"."
4957   :group 'gnus-article-mime
4958   :type 'boolean)
4959
4960 (defcustom gnus-mime-display-multipart-related-as-mixed nil
4961   "Display \"multipart/related\" parts as  \"multipart/mixed\".
4962
4963 If displaying \"text/html\" is discouraged \(see
4964 `mm-discouraged-alternatives'\) images or other material inside a
4965 \"multipart/related\" part might be overlooked when this variable is nil."
4966   :group 'gnus-article-mime
4967   :type 'boolean)
4968
4969 (defun gnus-mime-display-part (handle)
4970   (cond
4971    ;; Single part.
4972    ((not (stringp (car handle)))
4973     (gnus-mime-display-single handle))
4974    ;; User-defined multipart
4975    ((cdr (assoc (car handle) gnus-mime-multipart-functions))
4976     (funcall (cdr (assoc (car handle) gnus-mime-multipart-functions))
4977              handle))
4978    ;; multipart/alternative
4979    ((and (equal (car handle) "multipart/alternative")
4980          (not (or gnus-mime-display-multipart-as-mixed
4981                   gnus-mime-display-multipart-alternative-as-mixed)))
4982     (let ((id (1+ (length gnus-article-mime-handle-alist))))
4983       (push (cons id handle) gnus-article-mime-handle-alist)
4984       (gnus-mime-display-alternative (cdr handle) nil nil id)))
4985    ;; multipart/related
4986    ((and (equal (car handle) "multipart/related")
4987          (not (or gnus-mime-display-multipart-as-mixed
4988                   gnus-mime-display-multipart-related-as-mixed)))
4989     ;;;!!!We should find the start part, but we just default
4990     ;;;!!!to the first part.
4991     ;;(gnus-mime-display-part (cadr handle))
4992     ;;;!!! Most multipart/related is an HTML message plus images.
4993     ;;;!!! Unfortunately we are unable to let W3 display those
4994     ;;;!!! included images, so we just display it as a mixed multipart.
4995     ;;(gnus-mime-display-mixed (cdr handle))
4996     ;;;!!! No, w3 can display everything just fine.
4997     (gnus-mime-display-part (cadr handle)))
4998    ((equal (car handle) "multipart/signed")
4999     (gnus-add-wash-type 'signed)
5000     (gnus-mime-display-security handle))
5001    ((equal (car handle) "multipart/encrypted")
5002     (gnus-add-wash-type 'encrypted)
5003     (gnus-mime-display-security handle))
5004    ;; Other multiparts are handled like multipart/mixed.
5005    (t
5006     (gnus-mime-display-mixed (cdr handle)))))
5007
5008 (defun gnus-mime-part-function (handles)
5009   (if (stringp (car handles))
5010       (mapcar 'gnus-mime-part-function (cdr handles))
5011     (funcall gnus-article-mime-part-function handles)))
5012
5013 (defun gnus-mime-display-mixed (handles)
5014   (mapcar 'gnus-mime-display-part handles))
5015
5016 (defun gnus-mime-display-single (handle)
5017   (let ((type (mm-handle-media-type handle))
5018         (ignored gnus-ignored-mime-types)
5019         (not-attachment t)
5020         (move nil)
5021         display text)
5022     (catch 'ignored
5023       (progn
5024         (while ignored
5025           (when (string-match (pop ignored) type)
5026             (throw 'ignored nil)))
5027         (if (and (setq not-attachment
5028                        (and (not (mm-inline-override-p handle))
5029                             (or (not (mm-handle-disposition handle))
5030                                 (equal (car (mm-handle-disposition handle))
5031                                        "inline")
5032                                 (mm-attachment-override-p handle))))
5033                  (mm-automatic-display-p handle)
5034                  (or (and
5035                       (mm-inlinable-p handle)
5036                       (mm-inlined-p handle))
5037                      (mm-automatic-external-display-p type)))
5038             (setq display t)
5039           (when (equal (mm-handle-media-supertype handle) "text")
5040             (setq text t)))
5041         (let ((id (1+ (length gnus-article-mime-handle-alist)))
5042               beg)
5043           (push (cons id handle) gnus-article-mime-handle-alist)
5044           (when (or (not display)
5045                     (not (gnus-unbuttonized-mime-type-p type)))
5046             ;(gnus-article-insert-newline)
5047             (gnus-insert-mime-button
5048              handle id (list (or display (and not-attachment text))))
5049             (gnus-article-insert-newline)
5050             ;(gnus-article-insert-newline)
5051             ;; Remember modify the number of forward lines.
5052             (setq move t))
5053           (setq beg (point))
5054           (cond
5055            (display
5056             (when move
5057               (forward-line -1)
5058               (setq beg (point)))
5059             (let ((mail-parse-charset gnus-newsgroup-charset)
5060                   (mail-parse-ignored-charsets
5061                    (save-excursion (condition-case ()
5062                                        (set-buffer gnus-summary-buffer)
5063                                      (error))
5064                                    gnus-newsgroup-ignored-charsets)))
5065               (mm-display-part handle t))
5066             (goto-char (point-max)))
5067            ((and text not-attachment)
5068             (when move
5069               (forward-line -1)
5070               (setq beg (point)))
5071             (gnus-article-insert-newline)
5072             (mm-insert-inline handle (mm-get-part handle))
5073             (goto-char (point-max))))
5074           ;; Do highlighting.
5075           (save-excursion
5076             (save-restriction
5077               (narrow-to-region beg (point))
5078               (gnus-treat-article
5079                nil id
5080                (gnus-article-mime-total-parts)
5081                (mm-handle-media-type handle)))))))))
5082
5083 (defun gnus-unbuttonized-mime-type-p (type)
5084   "Say whether TYPE is to be unbuttonized."
5085   (unless gnus-inhibit-mime-unbuttonizing
5086     (when (catch 'found
5087             (let ((types gnus-unbuttonized-mime-types))
5088               (while types
5089                 (when (string-match (pop types) type)
5090                   (throw 'found t)))))
5091       (not (catch 'found
5092              (let ((types gnus-buttonized-mime-types))
5093                (while types
5094                  (when (string-match (pop types) type)
5095                    (throw 'found t)))))))))
5096
5097 (defun gnus-article-insert-newline ()
5098   "Insert a newline, but mark it as undeletable."
5099   (gnus-put-text-property
5100    (point) (progn (insert "\n") (point)) 'gnus-undeletable t))
5101
5102 (defun gnus-mime-display-alternative (handles &optional preferred ibegend id)
5103   (let* ((preferred (or preferred (mm-preferred-alternative handles)))
5104          (ihandles handles)
5105          (point (point))
5106          handle buffer-read-only from props begend not-pref)
5107     (save-window-excursion
5108       (save-restriction
5109         (when ibegend
5110           (narrow-to-region (car ibegend)
5111                             (or (cdr ibegend)
5112                                 (progn
5113                                   (goto-char (car ibegend))
5114                                   (forward-line 2)
5115                                   (point))))
5116           (delete-region (point-min) (point-max))
5117           (mm-remove-parts handles))
5118         (setq begend (list (point-marker)))
5119         ;; Do the toggle.
5120         (unless (setq not-pref (cadr (member preferred ihandles)))
5121           (setq not-pref (car ihandles)))
5122         (when (or ibegend
5123                   (not preferred)
5124                   (not (gnus-unbuttonized-mime-type-p
5125                         "multipart/alternative")))
5126           (gnus-add-text-properties
5127            (setq from (point))
5128            (progn
5129              (insert (format "%d.  " id))
5130              (point))
5131            `(gnus-callback
5132              (lambda (handles)
5133                (unless ,(not ibegend)
5134                  (setq gnus-article-mime-handle-alist
5135                        ',gnus-article-mime-handle-alist))
5136                (gnus-mime-display-alternative
5137                 ',ihandles ',not-pref ',begend ,id))
5138              ,@(gnus-local-map-property gnus-mime-button-map)
5139              ,gnus-mouse-face-prop ,gnus-article-mouse-face
5140              face ,gnus-article-button-face
5141              gnus-part ,id
5142              gnus-data ,handle))
5143           (widget-convert-button 'link from (point)
5144                                  :action 'gnus-widget-press-button
5145                                  :button-keymap gnus-widget-button-keymap)
5146           ;; Do the handles
5147           (while (setq handle (pop handles))
5148             (gnus-add-text-properties
5149              (setq from (point))
5150              (progn
5151                (insert (format "(%c) %-18s"
5152                                (if (equal handle preferred) ?* ? )
5153                                (mm-handle-media-type handle)))
5154                (point))
5155              `(gnus-callback
5156                (lambda (handles)
5157                  (unless ,(not ibegend)
5158                    (setq gnus-article-mime-handle-alist
5159                          ',gnus-article-mime-handle-alist))
5160                  (gnus-mime-display-alternative
5161                   ',ihandles ',handle ',begend ,id))
5162                ,@(gnus-local-map-property gnus-mime-button-map)
5163                ,gnus-mouse-face-prop ,gnus-article-mouse-face
5164                face ,gnus-article-button-face
5165                gnus-part ,id
5166                gnus-data ,handle))
5167             (widget-convert-button 'link from (point)
5168                                    :action 'gnus-widget-press-button
5169                                    :button-keymap gnus-widget-button-keymap)
5170             (insert "  "))
5171           (insert "\n\n"))
5172         (when preferred
5173           (if (stringp (car preferred))
5174               (gnus-display-mime preferred)
5175             (let ((mail-parse-charset gnus-newsgroup-charset)
5176                   (mail-parse-ignored-charsets
5177                    (save-excursion (set-buffer gnus-summary-buffer)
5178                                    gnus-newsgroup-ignored-charsets)))
5179               (mm-display-part preferred)
5180               ;; Do highlighting.
5181               (save-excursion
5182                 (save-restriction
5183                   (narrow-to-region (car begend) (point-max))
5184                   (gnus-treat-article
5185                    nil (length gnus-article-mime-handle-alist)
5186                    (gnus-article-mime-total-parts)
5187                    (mm-handle-media-type handle))))))
5188           (goto-char (point-max))
5189           (setcdr begend (point-marker)))))
5190     (when ibegend
5191       (goto-char point))))
5192
5193 (defconst gnus-article-wash-status-strings
5194   (let ((alist '((cite "c" "Possible hidden citation text"
5195                        " " "All citation text visible")
5196                  (headers "h" "Hidden headers"
5197                           " " "All headers visible.")
5198                  (pgp "p" "Encrypted or signed message status hidden"
5199                       " " "No hidden encryption nor digital signature status")
5200                  (signature "s" "Signature has been hidden"
5201                             " " "Signature is visible")
5202                  (overstrike "o" "Overstrike (^H) characters applied"
5203                              " " "No overstrike characters applied")
5204                  (gnus-show-mime "m" "Mime processing is activated"
5205                                  " " "Mime processing is not activated")
5206                  (emphasis "e" "/*_Emphasis_*/ characters applied"
5207                            " " "No /*_emphasis_*/ characters applied")))
5208         result)
5209     (dolist (entry alist result)
5210       (let ((key (nth 0 entry))
5211             (on (copy-sequence (nth 1 entry)))
5212             (on-help (nth 2 entry))
5213             (off (copy-sequence (nth 3 entry)))
5214             (off-help (nth 4 entry)))
5215         (put-text-property 0 1 'help-echo on-help on)
5216         (put-text-property 0 1 'help-echo off-help off)
5217         (push (list key on off) result))))
5218   "Alist of strings describing wash status in the mode line.
5219 Each entry has the form (KEY ON OF), where the KEY is a symbol
5220 representing the particular washing function, ON is the string to use
5221 in the article mode line when the washing function is active, and OFF
5222 is the string to use when it is inactive.")
5223
5224 (defun gnus-article-wash-status-entry (key value)
5225   (let ((entry (assoc key gnus-article-wash-status-strings)))
5226     (if value (nth 1 entry) (nth 2 entry))))
5227
5228 (defun gnus-article-wash-status ()
5229   "Return a string which display status of article washing."
5230   (save-excursion
5231     (set-buffer gnus-article-buffer)
5232     (let ((cite (memq 'cite gnus-article-wash-types))
5233           (headers (memq 'headers gnus-article-wash-types))
5234           (boring (memq 'boring-headers gnus-article-wash-types))
5235           (pgp (memq 'pgp gnus-article-wash-types))
5236           (pem (memq 'pem gnus-article-wash-types))
5237           (signed (memq 'signed gnus-article-wash-types))
5238           (encrypted (memq 'encrypted gnus-article-wash-types))
5239           (signature (memq 'signature gnus-article-wash-types))
5240           (overstrike (memq 'overstrike gnus-article-wash-types))
5241           (emphasis (memq 'emphasis gnus-article-wash-types)))
5242       (concat
5243        (gnus-article-wash-status-entry 'cite cite)
5244        (gnus-article-wash-status-entry 'headers (or headers boring))
5245        (gnus-article-wash-status-entry 'pgp (or pgp pem signed encrypted))
5246        (gnus-article-wash-status-entry 'signature signature)
5247        (gnus-article-wash-status-entry 'overstrike overstrike)
5248        (gnus-article-wash-status-entry 'gnus-show-mime gnus-show-mime)
5249        (gnus-article-wash-status-entry 'emphasis emphasis)))))
5250
5251 (defun gnus-add-wash-type (type)
5252   "Add a washing of TYPE to the current status."
5253   (add-to-list 'gnus-article-wash-types type))
5254
5255 (defun gnus-delete-wash-type (type)
5256   "Add a washing of TYPE to the current status."
5257   (setq gnus-article-wash-types (delq type gnus-article-wash-types)))
5258
5259 (defun gnus-add-image (category image)
5260   "Add IMAGE of CATEGORY to the list of displayed images."
5261   (let ((entry (assq category gnus-article-image-alist)))
5262     (unless entry
5263       (setq entry (list category))
5264       (push entry gnus-article-image-alist))
5265     (nconc entry (list image))))
5266
5267 (defun gnus-delete-images (category)
5268   "Delete all images in CATEGORY."
5269   (let ((entry (assq category gnus-article-image-alist)))
5270     (dolist (image (cdr entry))
5271       (gnus-remove-image image category))
5272     (setq gnus-article-image-alist (delq entry gnus-article-image-alist))
5273     (gnus-delete-wash-type category)))
5274
5275 (defalias 'gnus-article-hide-headers-if-wanted 'gnus-article-maybe-hide-headers)
5276
5277 (defun gnus-article-maybe-hide-headers ()
5278   "Hide unwanted headers if `gnus-have-all-headers' is nil.
5279 Provided for backwards compatibility."
5280   (when (and (or (not (gnus-buffer-live-p gnus-summary-buffer))
5281                  (not (save-excursion (set-buffer gnus-summary-buffer)
5282                                       gnus-have-all-headers)))
5283              (not gnus-inhibit-hiding))
5284     (gnus-article-hide-headers)))
5285
5286 ;;; Article savers.
5287
5288 (defun gnus-output-to-file (file-name)
5289   "Append the current article to a file named FILE-NAME."
5290   (let ((artbuf (current-buffer)))
5291     (with-temp-buffer
5292       (insert-buffer-substring artbuf)
5293       ;; Append newline at end of the buffer as separator, and then
5294       ;; save it to file.
5295       (goto-char (point-max))
5296       (insert "\n")
5297       (let ((file-name-coding-system nnmail-pathname-coding-system)
5298             (pathname-coding-system nnmail-pathname-coding-system))
5299         (write-region-as-binary (point-min) (point-max) file-name 'append))
5300       t)))
5301
5302 (defun gnus-narrow-to-page (&optional arg)
5303   "Narrow the article buffer to a page.
5304 If given a numerical ARG, move forward ARG pages."
5305   (interactive "P")
5306   (setq arg (if arg (prefix-numeric-value arg) 0))
5307   (save-excursion
5308     (set-buffer gnus-article-buffer)
5309     (goto-char (point-min))
5310     (widen)
5311     ;; Remove any old next/prev buttons.
5312     (when (gnus-visual-p 'page-marker)
5313       (let ((buffer-read-only nil))
5314         (gnus-remove-text-with-property 'gnus-prev)
5315         (gnus-remove-text-with-property 'gnus-next)))
5316     (when
5317         (cond ((< arg 0)
5318                (re-search-backward page-delimiter nil 'move (1+ (abs arg))))
5319               ((> arg 0)
5320                (re-search-forward page-delimiter nil 'move arg)))
5321       (goto-char (match-end 0)))
5322     (narrow-to-region
5323      (point)
5324      (if (re-search-forward page-delimiter nil 'move)
5325          (match-beginning 0)
5326        (point)))
5327     (when (and (gnus-visual-p 'page-marker)
5328                (not (= (point-min) 1)))
5329       (save-excursion
5330         (goto-char (point-min))
5331         (gnus-insert-prev-page-button)))
5332     (when (and (gnus-visual-p 'page-marker)
5333                (< (+ (point-max) 2) (buffer-size)))
5334       (save-excursion
5335         (goto-char (point-max))
5336         (gnus-insert-next-page-button)))))
5337
5338 ;; Article mode commands
5339
5340 (defun gnus-article-goto-next-page ()
5341   "Show the next page of the article."
5342   (interactive)
5343   (when (gnus-article-next-page)
5344     (goto-char (point-min))
5345     (gnus-article-read-summary-keys nil (gnus-character-to-event ?n))))
5346
5347
5348 (defun gnus-article-goto-prev-page ()
5349   "Show the next page of the article."
5350   (interactive)
5351   (if (bobp)
5352       (gnus-article-read-summary-keys nil (gnus-character-to-event ?p))
5353     (gnus-article-prev-page nil)))
5354
5355 ;; This is cleaner but currently breaks `gnus-pick-mode':
5356 ;;
5357 ;; (defun gnus-article-goto-next-page ()
5358 ;;   "Show the next page of the article."
5359 ;;   (interactive)
5360 ;;   (gnus-eval-in-buffer-window gnus-summary-buffer
5361 ;;     (gnus-summary-next-page)))
5362 ;;
5363 ;; (defun gnus-article-goto-prev-page ()
5364 ;;   "Show the next page of the article."
5365 ;;   (interactive)
5366 ;;   (gnus-eval-in-buffer-window gnus-summary-buffer
5367 ;;     (gnus-summary-prev-page)))
5368
5369 (defun gnus-article-next-page (&optional lines)
5370   "Show the next page of the current article.
5371 If end of article, return non-nil.  Otherwise return nil.
5372 Argument LINES specifies lines to be scrolled up."
5373   (interactive "p")
5374   (move-to-window-line -1)
5375   (if (save-excursion
5376         (end-of-line)
5377         (and (pos-visible-in-window-p)  ;Not continuation line.
5378              (>= (1+ (point)) (point-max)))) ;Allow for trailing newline.
5379       ;; Nothing in this page.
5380       (if (or (not gnus-page-broken)
5381               (save-excursion
5382                 (save-restriction
5383                   (widen) (forward-line 1) (eobp)))) ;Real end-of-buffer?
5384           (progn
5385             (when gnus-article-over-scroll
5386               (gnus-article-next-page-1 lines))
5387             t)                  ;Nothing more.
5388         (gnus-narrow-to-page 1)         ;Go to next page.
5389         nil)
5390     ;; More in this page.
5391     (gnus-article-next-page-1 lines)
5392     nil))
5393
5394 (defun gnus-article-next-page-1 (lines)
5395   (let ((scroll-in-place nil))
5396     (condition-case ()
5397         (scroll-up lines)
5398       (end-of-buffer
5399        ;; Long lines may cause an end-of-buffer error.
5400        (goto-char (point-max)))))
5401   (move-to-window-line 0))
5402
5403 (defun gnus-article-prev-page (&optional lines)
5404   "Show previous page of current article.
5405 Argument LINES specifies lines to be scrolled down."
5406   (interactive "p")
5407   (move-to-window-line 0)
5408   (if (and gnus-page-broken
5409            (bobp)
5410            (not (save-restriction (widen) (bobp)))) ;Real beginning-of-buffer?
5411       (progn
5412         (gnus-narrow-to-page -1)        ;Go to previous page.
5413         (goto-char (point-max))
5414         (recenter -1))
5415     (let ((scroll-in-place nil))
5416       (prog1
5417           (condition-case ()
5418               (scroll-down lines)
5419             (beginning-of-buffer
5420              (goto-char (point-min))))
5421         (move-to-window-line 0)))))
5422
5423 (defun gnus-article-only-boring-p ()
5424   "Decide whether there is only boring text remaining in the article.
5425 Something \"interesting\" is a word of at least two letters that does
5426 not have a face in `gnus-article-boring-faces'."
5427   (when (and gnus-article-skip-boring
5428              (boundp 'gnus-article-boring-faces)
5429              (symbol-value 'gnus-article-boring-faces))
5430     (save-excursion
5431       (catch 'only-boring
5432         (while (re-search-forward "\\b\\w\\w" nil t)
5433           (forward-char -1)
5434           (when (not (gnus-intersection
5435                       (gnus-faces-at (point))
5436                       (symbol-value 'gnus-article-boring-faces)))
5437             (throw 'only-boring nil)))
5438         (throw 'only-boring t)))))
5439
5440 (defun gnus-article-refer-article ()
5441   "Read article specified by message-id around point."
5442   (interactive)
5443   (save-excursion
5444     (re-search-backward "[ \t]\\|^" (gnus-point-at-bol) t)
5445     (re-search-forward "<?news:<?\\|<" (gnus-point-at-eol) t)
5446     (if (re-search-forward "[^@ ]+@[^ \t>]+" (gnus-point-at-eol) t)
5447         (let ((msg-id (concat "<" (match-string 0) ">")))
5448           (set-buffer gnus-summary-buffer)
5449           (gnus-summary-refer-article msg-id))
5450       (error "No references around point"))))
5451
5452 (defun gnus-article-show-summary ()
5453   "Reconfigure windows to show summary buffer."
5454   (interactive)
5455   (if (not (gnus-buffer-live-p gnus-summary-buffer))
5456       (error "There is no summary buffer for this article buffer")
5457     (gnus-article-set-globals)
5458     (gnus-configure-windows 'article)
5459     (gnus-summary-goto-subject gnus-current-article)
5460     (gnus-summary-position-point)))
5461
5462 (defun gnus-article-describe-briefly ()
5463   "Describe article mode commands briefly."
5464   (interactive)
5465   (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")))
5466
5467 (defun gnus-article-summary-command ()
5468   "Execute the last keystroke in the summary buffer."
5469   (interactive)
5470   (let ((obuf (current-buffer))
5471         (owin (current-window-configuration))
5472         func)
5473     (switch-to-buffer gnus-article-current-summary 'norecord)
5474     (setq func (lookup-key (current-local-map) (this-command-keys)))
5475     (call-interactively func)
5476     (set-buffer obuf)
5477     (set-window-configuration owin)
5478     (set-window-point (get-buffer-window (current-buffer)) (point))))
5479
5480 (defun gnus-article-summary-command-nosave ()
5481   "Execute the last keystroke in the summary buffer."
5482   (interactive)
5483   (let (func)
5484     (pop-to-buffer gnus-article-current-summary 'norecord)
5485     (setq func (lookup-key (current-local-map) (this-command-keys)))
5486     (call-interactively func)))
5487
5488 (defun gnus-article-check-buffer ()
5489   "Beep if not in an article buffer."
5490   (unless (eq (get-buffer gnus-article-buffer) (current-buffer))
5491     (error "Command invoked outside of a Gnus article buffer")))
5492
5493 (defun gnus-article-read-summary-keys (&optional arg key not-restore-window)
5494   "Read a summary buffer key sequence and execute it from the article buffer."
5495   (interactive "P")
5496   (gnus-article-check-buffer)
5497   (let ((nosaves
5498          '("q" "Q"  "c" "r" "\C-c\C-f" "m"  "a" "f"
5499            "Zc" "ZC" "ZE" "ZJ" "ZQ" "ZZ" "Zn" "ZR" "ZG" "ZN" "ZP"
5500            "=" "^" "\M-^" "|"))
5501         (nosave-but-article
5502          '("A\r"))
5503         (nosave-in-article
5504          '("\C-d"))
5505         (up-to-top
5506          '("n" "Gn" "p" "Gp"))
5507         keys new-sum-point)
5508     (save-excursion
5509       (set-buffer gnus-article-current-summary)
5510       (let (gnus-pick-mode)
5511         (push (or key last-command-event) unread-command-events)
5512         (setq keys (static-if (featurep 'xemacs)
5513                        (events-to-keys (read-key-sequence nil))
5514                      (read-key-sequence nil)))))
5515     (message "")
5516
5517     (if (or (member keys nosaves)
5518             (member keys nosave-but-article)
5519             (member keys nosave-in-article))
5520         (let (func)
5521           (save-window-excursion
5522             (pop-to-buffer gnus-article-current-summary 'norecord)
5523             ;; We disable the pick minor mode commands.
5524             (let (gnus-pick-mode)
5525               (setq func (lookup-key (current-local-map) keys))))
5526           (if (or (not func)
5527                   (numberp func))
5528               (ding)
5529             (unless (member keys nosave-in-article)
5530               (set-buffer gnus-article-current-summary))
5531             (call-interactively func)
5532             (setq new-sum-point (point)))
5533           (when (member keys nosave-but-article)
5534             (pop-to-buffer gnus-article-buffer 'norecord)))
5535       ;; These commands should restore window configuration.
5536       (let ((obuf (current-buffer))
5537             (owin (current-window-configuration))
5538             (opoint (point))
5539             (summary gnus-article-current-summary)
5540             func in-buffer selected)
5541         (if not-restore-window
5542             (pop-to-buffer summary 'norecord)
5543           (switch-to-buffer summary 'norecord))
5544         (setq in-buffer (current-buffer))
5545         ;; We disable the pick minor mode commands.
5546         (if (and (setq func (let (gnus-pick-mode)
5547                               (lookup-key (current-local-map) keys)))
5548                  (functionp func))
5549             (progn
5550               (call-interactively func)
5551               (setq new-sum-point (point))
5552               (when (eq in-buffer (current-buffer))
5553                 (setq selected (gnus-summary-select-article))
5554                 (set-buffer obuf)
5555                 (unless not-restore-window
5556                   (set-window-configuration owin))
5557                 (when (eq selected 'old)
5558                   (article-goto-body)
5559                   (set-window-start (get-buffer-window (current-buffer))
5560                                     1)
5561                   (set-window-point (get-buffer-window (current-buffer))
5562                                     (point)))
5563                 (let ((win (get-buffer-window gnus-article-current-summary)))
5564                   (when win
5565                     (set-window-point win new-sum-point))))    )
5566           (switch-to-buffer gnus-article-buffer)
5567           (ding))))))
5568
5569 (defun gnus-article-describe-key (key)
5570   "Display documentation of the function invoked by KEY.  KEY is a string."
5571   (interactive "kDescribe key: ")
5572   (gnus-article-check-buffer)
5573   (if (eq (key-binding key) 'gnus-article-read-summary-keys)
5574       (save-excursion
5575         (set-buffer gnus-article-current-summary)
5576         (let (gnus-pick-mode)
5577           (if (featurep 'xemacs)
5578               (progn
5579                 (push (elt key 0) unread-command-events)
5580                 (setq key (events-to-keys
5581                            (read-key-sequence "Describe key: "))))
5582             (setq unread-command-events
5583                   (mapcar
5584                    (lambda (x) (if (>= x 128) (list 'meta (- x 128)) x))
5585                    key))
5586             (setq key (read-key-sequence "Describe key: "))))
5587         (describe-key key))
5588     (describe-key key)))
5589
5590 (defun gnus-article-describe-key-briefly (key &optional insert)
5591   "Display documentation of the function invoked by KEY.  KEY is a string."
5592   (interactive "kDescribe key: \nP")
5593   (gnus-article-check-buffer)
5594   (if (eq (key-binding key) 'gnus-article-read-summary-keys)
5595       (save-excursion
5596         (set-buffer gnus-article-current-summary)
5597         (let (gnus-pick-mode)
5598           (if (featurep 'xemacs)
5599               (progn
5600                 (push (elt key 0) unread-command-events)
5601                 (setq key (events-to-keys
5602                            (read-key-sequence "Describe key: "))))
5603             (setq unread-command-events
5604                   (mapcar
5605                    (lambda (x) (if (>= x 128) (list 'meta (- x 128)) x))
5606                    key))
5607             (setq key (read-key-sequence "Describe key: "))))
5608         (describe-key-briefly key insert))
5609     (describe-key-briefly key insert)))
5610
5611 (defun gnus-article-reply-with-original (&optional wide)
5612   "Start composing a reply mail to the current message.
5613 The text in the region will be yanked.  If the region isn't active,
5614 the entire article will be yanked."
5615   (interactive "P")
5616   (let ((article (cdr gnus-article-current))
5617         contents)
5618     (if (not (gnus-mark-active-p))
5619         (with-current-buffer gnus-summary-buffer
5620           (gnus-summary-reply (list (list article)) wide))
5621       (setq contents (buffer-substring (point) (mark t)))
5622       ;; Deactivate active regions.
5623       (when (and (boundp 'transient-mark-mode)
5624                  transient-mark-mode)
5625         (setq mark-active nil))
5626       (with-current-buffer gnus-summary-buffer
5627         (gnus-summary-reply
5628          (list (list article contents)) wide)))))
5629
5630 (defun gnus-article-followup-with-original ()
5631   "Compose a followup to the current article.
5632 The text in the region will be yanked.  If the region isn't active,
5633 the entire article will be yanked."
5634   (interactive)
5635   (let ((article (cdr gnus-article-current))
5636         contents)
5637       (if (not (gnus-mark-active-p))
5638           (with-current-buffer gnus-summary-buffer
5639             (gnus-summary-followup (list (list article))))
5640         (setq contents (buffer-substring (point) (mark t)))
5641         ;; Deactivate active regions.
5642         (when (and (boundp 'transient-mark-mode)
5643                    transient-mark-mode)
5644           (setq mark-active nil))
5645         (with-current-buffer gnus-summary-buffer
5646           (gnus-summary-followup
5647            (list (list article contents)))))))
5648
5649 (defun gnus-article-hide (&optional arg force)
5650   "Hide all the gruft in the current article.
5651 This means that signatures, cited text and (some) headers will be
5652 hidden.
5653 If given a prefix, show the hidden text instead."
5654   (interactive (append (gnus-article-hidden-arg) (list 'force)))
5655   (gnus-article-hide-headers arg)
5656   (gnus-article-hide-list-identifiers arg)
5657   (gnus-article-hide-citation-maybe arg force)
5658   (gnus-article-hide-signature arg))
5659
5660 (defun gnus-article-maybe-highlight ()
5661   "Do some article highlighting if article highlighting is requested."
5662   (when (gnus-visual-p 'article-highlight 'highlight)
5663     (gnus-article-highlight-some)))
5664
5665 (defun gnus-check-group-server ()
5666   ;; Make sure the connection to the server is alive.
5667   (unless (gnus-server-opened
5668            (gnus-find-method-for-group gnus-newsgroup-name))
5669     (gnus-check-server (gnus-find-method-for-group gnus-newsgroup-name))
5670     (gnus-request-group gnus-newsgroup-name t)))
5671
5672 (eval-when-compile
5673   (autoload 'nneething-get-file-name "nneething"))
5674
5675 (defun gnus-request-article-this-buffer (article group)
5676   "Get an article and insert it into this buffer."
5677   (let (do-update-line sparse-header)
5678     (prog1
5679         (save-excursion
5680           (erase-buffer)
5681           (gnus-kill-all-overlays)
5682           (setq group (or group gnus-newsgroup-name))
5683
5684           ;; Using `gnus-request-article' directly will insert the article into
5685           ;; `nntp-server-buffer' - so we'll save some time by not having to
5686           ;; copy it from the server buffer into the article buffer.
5687
5688           ;; We only request an article by message-id when we do not have the
5689           ;; headers for it, so we'll have to get those.
5690           (when (stringp article)
5691             (gnus-read-header article))
5692
5693           ;; If the article number is negative, that means that this article
5694           ;; doesn't belong in this newsgroup (possibly), so we find its
5695           ;; message-id and request it by id instead of number.
5696           (when (and (numberp article)
5697                      gnus-summary-buffer
5698                      (get-buffer gnus-summary-buffer)
5699                      (gnus-buffer-exists-p gnus-summary-buffer))
5700             (save-excursion
5701               (set-buffer gnus-summary-buffer)
5702               (let ((header (gnus-summary-article-header article)))
5703                 (when (< article 0)
5704                   (cond
5705                    ((memq article gnus-newsgroup-sparse)
5706                     ;; This is a sparse gap article.
5707                     (setq do-update-line article)
5708                     (setq article (mail-header-id header))
5709                     (setq sparse-header (gnus-read-header article))
5710                     (setq gnus-newsgroup-sparse
5711                           (delq article gnus-newsgroup-sparse)))
5712                    ((vectorp header)
5713                     ;; It's a real article.
5714                     (setq article (mail-header-id header)))
5715                    (t
5716                     ;; It is an extracted pseudo-article.
5717                     (setq article 'pseudo)
5718                     (gnus-request-pseudo-article header))))
5719
5720                 (let ((method (gnus-find-method-for-group
5721                                gnus-newsgroup-name)))
5722                   (when (and (eq (car method) 'nneething)
5723                              (vectorp header))
5724                     (let ((dir (nneething-get-file-name
5725                                 (mail-header-id header))))
5726                       (when (and (stringp dir)
5727                                  (file-directory-p dir))
5728                         (setq article 'nneething)
5729                         (gnus-group-enter-directory dir))))))))
5730
5731           (cond
5732            ;; Refuse to select canceled articles.
5733            ((and (numberp article)
5734                  gnus-summary-buffer
5735                  (get-buffer gnus-summary-buffer)
5736                  (gnus-buffer-exists-p gnus-summary-buffer)
5737                  (eq (cdr (save-excursion
5738                             (set-buffer gnus-summary-buffer)
5739                             (assq article gnus-newsgroup-reads)))
5740                      gnus-canceled-mark))
5741             nil)
5742            ;; We first check `gnus-original-article-buffer'.
5743            ((and (get-buffer gnus-original-article-buffer)
5744                  (numberp article)
5745                  (save-excursion
5746                    (set-buffer gnus-original-article-buffer)
5747                    (and (equal (car gnus-original-article) group)
5748                         (eq (cdr gnus-original-article) article))))
5749             (insert-buffer-substring gnus-original-article-buffer)
5750             'article)
5751            ;; Check the backlog.
5752            ((and gnus-keep-backlog
5753                  (gnus-backlog-request-article group article (current-buffer)))
5754             'article)
5755            ;; Check asynchronous pre-fetch.
5756            ((gnus-async-request-fetched-article group article (current-buffer))
5757             (gnus-async-prefetch-next group article gnus-summary-buffer)
5758             (when (and (numberp article) gnus-keep-backlog)
5759               (gnus-backlog-enter-article group article (current-buffer)))
5760             'article)
5761            ;; Check the cache.
5762            ((and gnus-use-cache
5763                  (numberp article)
5764                  (gnus-cache-request-article article group))
5765             'article)
5766            ;; Check the agent cache.
5767            ((gnus-agent-request-article article group)
5768             'article)
5769            ;; Get the article and put into the article buffer.
5770            ((or (stringp article)
5771                 (numberp article))
5772             (let ((gnus-override-method gnus-override-method)
5773                   (methods (and (stringp article)
5774                                 gnus-refer-article-method))
5775                   (backend (car (gnus-find-method-for-group
5776                                  gnus-newsgroup-name)))
5777                   result
5778                   (buffer-read-only nil))
5779               (if (or (not (listp methods))
5780                       (and (symbolp (car methods))
5781                            (assq (car methods) nnoo-definition-alist)))
5782                   (setq methods (list methods)))
5783               (when (and (null gnus-override-method)
5784                          methods)
5785                 (setq gnus-override-method (pop methods)))
5786               (while (not result)
5787                 (when (eq gnus-override-method 'current)
5788                   (setq gnus-override-method
5789                         (with-current-buffer gnus-summary-buffer
5790                           gnus-current-select-method)))
5791                 (erase-buffer)
5792                 (gnus-kill-all-overlays)
5793                 (let ((gnus-newsgroup-name group))
5794                   (gnus-check-group-server))
5795                 (cond
5796                  ((gnus-request-article article group (current-buffer))
5797                   (when (numberp article)
5798                     (gnus-async-prefetch-next group article
5799                                               gnus-summary-buffer)
5800                     (when gnus-keep-backlog
5801                       (gnus-backlog-enter-article
5802                        group article (current-buffer))))
5803                   (setq result 'article))
5804                  (methods
5805                   (setq gnus-override-method (pop methods)))
5806                  ((not (string-match "^400 "
5807                                      (nnheader-get-report backend)))
5808                   ;; If we get 400 server disconnect, reconnect and
5809                   ;; retry; otherwise, assume the article has expired.
5810                   (setq result 'done))))
5811               (and (eq result 'article) 'article)))
5812            ;; It was a pseudo.
5813            (t article)))
5814
5815       ;; Associate this article with the current summary buffer.
5816       (setq gnus-article-current-summary gnus-summary-buffer)
5817
5818       ;; Take the article from the original article buffer
5819       ;; and place it in the buffer it's supposed to be in.
5820       (when (and (get-buffer gnus-article-buffer)
5821                  (equal (buffer-name (current-buffer))
5822                         (buffer-name (get-buffer gnus-article-buffer))))
5823         (save-excursion
5824           (if (get-buffer gnus-original-article-buffer)
5825               (set-buffer gnus-original-article-buffer)
5826             (set-buffer (gnus-get-buffer-create gnus-original-article-buffer))
5827             (buffer-disable-undo)
5828             (setq major-mode 'gnus-original-article-mode)
5829             (setq buffer-read-only t))
5830           (let (buffer-read-only)
5831             (erase-buffer)
5832             (insert-buffer-substring gnus-article-buffer))
5833           (setq gnus-original-article (cons group article)))
5834
5835         ;; Decode charsets.
5836         (run-hooks 'gnus-article-decode-hook)
5837         ;; Mark article as decoded or not.
5838         (setq gnus-article-decoded-p gnus-article-decode-hook))
5839
5840       ;; Update sparse articles.
5841       (when (and do-update-line
5842                  (or (numberp article)
5843                      (stringp article)))
5844         (let ((buf (current-buffer)))
5845           (set-buffer gnus-summary-buffer)
5846           (gnus-summary-update-article do-update-line sparse-header)
5847           (gnus-summary-goto-subject do-update-line nil t)
5848           (set-window-point (gnus-get-buffer-window (current-buffer) t)
5849                             (point))
5850           (set-buffer buf))))))
5851
5852 ;;;
5853 ;;; Article editing
5854 ;;;
5855
5856 (defcustom gnus-article-edit-mode-hook nil
5857   "Hook run in article edit mode buffers."
5858   :group 'gnus-article-various
5859   :type 'hook)
5860
5861 (defcustom gnus-article-edit-article-setup-function
5862   'gnus-article-mime-edit-article-setup
5863   "Function called to setup an editing article buffer."
5864   :group 'gnus-article-various
5865   :type 'function)
5866
5867 (defvar gnus-article-edit-done-function nil)
5868
5869 (defvar gnus-article-edit-mode-map nil)
5870 (defvar gnus-article-edit-mode nil)
5871
5872 ;; Should we be using derived.el for this?
5873 (unless gnus-article-edit-mode-map
5874   (setq gnus-article-edit-mode-map (make-keymap))
5875   (set-keymap-parent gnus-article-edit-mode-map text-mode-map)
5876
5877   (gnus-define-keys gnus-article-edit-mode-map
5878     "\C-c?"    describe-mode
5879     "\C-c\C-c" gnus-article-edit-done
5880     "\C-c\C-k" gnus-article-edit-exit
5881     "\C-c\C-f\C-t" message-goto-to
5882     "\C-c\C-f\C-o" message-goto-from
5883     "\C-c\C-f\C-b" message-goto-bcc
5884     ;;"\C-c\C-f\C-w" message-goto-fcc
5885     "\C-c\C-f\C-c" message-goto-cc
5886     "\C-c\C-f\C-s" message-goto-subject
5887     "\C-c\C-f\C-r" message-goto-reply-to
5888     "\C-c\C-f\C-n" message-goto-newsgroups
5889     "\C-c\C-f\C-d" message-goto-distribution
5890     "\C-c\C-f\C-f" message-goto-followup-to
5891     "\C-c\C-f\C-m" message-goto-mail-followup-to
5892     "\C-c\C-f\C-k" message-goto-keywords
5893     "\C-c\C-f\C-u" message-goto-summary
5894     "\C-c\C-f\C-i" message-insert-or-toggle-importance
5895     "\C-c\C-f\C-a" message-generate-unsubscribed-mail-followup-to
5896     "\C-c\C-b" message-goto-body
5897     "\C-c\C-i" message-goto-signature
5898
5899     "\C-c\C-t" message-insert-to
5900     "\C-c\C-n" message-insert-newsgroups
5901     "\C-c\C-o" message-sort-headers
5902     "\C-c\C-e" message-elide-region
5903     "\C-c\C-v" message-delete-not-region
5904     "\C-c\C-z" message-kill-to-signature
5905     "\M-\r" message-newline-and-reformat
5906     "\C-c\C-a" mml-attach-file
5907     "\C-a" message-beginning-of-line
5908     "\t" message-tab
5909     "\M-;" comment-region)
5910
5911   (gnus-define-keys (gnus-article-edit-wash-map
5912                      "\C-c\C-w" gnus-article-edit-mode-map)
5913     "f" gnus-article-edit-full-stops))
5914
5915 (easy-menu-define
5916   gnus-article-edit-mode-field-menu gnus-article-edit-mode-map ""
5917   '("Field"
5918     ["Fetch To" message-insert-to t]
5919     ["Fetch Newsgroups" message-insert-newsgroups t]
5920     "----"
5921     ["To" message-goto-to t]
5922     ["From" message-goto-from t]
5923     ["Subject" message-goto-subject t]
5924     ["Cc" message-goto-cc t]
5925     ["Reply-To" message-goto-reply-to t]
5926     ["Summary" message-goto-summary t]
5927     ["Keywords" message-goto-keywords t]
5928     ["Newsgroups" message-goto-newsgroups t]
5929     ["Followup-To" message-goto-followup-to t]
5930     ["Mail-Followup-To" message-goto-mail-followup-to t]
5931     ["Distribution" message-goto-distribution t]
5932     ["Body" message-goto-body t]
5933     ["Signature" message-goto-signature t]))
5934
5935 (define-derived-mode gnus-article-edit-mode text-mode "Article Edit"
5936   "Major mode for editing articles.
5937 This is an extended text-mode.
5938
5939 \\{gnus-article-edit-mode-map}"
5940   (make-local-variable 'gnus-article-edit-done-function)
5941   (make-local-variable 'gnus-prev-winconf)
5942   (set (make-local-variable 'font-lock-defaults)
5943        '(message-font-lock-keywords t))
5944   (set (make-local-variable 'mail-header-separator) "")
5945   (set (make-local-variable 'gnus-article-edit-mode) t)
5946   (easy-menu-add message-mode-field-menu message-mode-map)
5947   (setq buffer-read-only nil)
5948   (buffer-enable-undo)
5949   (widen))
5950
5951 (defun gnus-article-edit (&optional force)
5952   "Edit the current article.
5953 This will have permanent effect only in mail groups.
5954 If FORCE is non-nil, allow editing of articles even in read-only
5955 groups."
5956   (interactive "P")
5957   (when (and (not force)
5958              (gnus-group-read-only-p))
5959     (error "The current newsgroup does not support article editing"))
5960   (gnus-article-date-original)
5961   (gnus-article-edit-article
5962    'ignore
5963    `(lambda (no-highlight)
5964       'ignore
5965       (gnus-summary-edit-article-done
5966        ,(or (mail-header-references gnus-current-headers) "")
5967        ,(gnus-group-read-only-p) ,gnus-summary-buffer no-highlight))))
5968
5969 (defun gnus-article-edit-article (start-func exit-func)
5970   "Start editing the contents of the current article buffer."
5971   (let ((winconf (current-window-configuration)))
5972     (set-buffer gnus-article-buffer)
5973     (gnus-article-edit-mode)
5974     (funcall start-func)
5975     (set-buffer-modified-p nil)
5976     (gnus-configure-windows 'edit-article)
5977     (setq gnus-article-edit-done-function exit-func)
5978     (setq gnus-prev-winconf winconf)
5979     (when gnus-article-edit-article-setup-function
5980       (funcall gnus-article-edit-article-setup-function))
5981     (gnus-message 6 "C-c C-c to end edits; C-c C-k to exit")))
5982
5983 (defun gnus-article-edit-done (&optional arg)
5984   "Update the article edits and exit."
5985   (interactive "P")
5986   (let ((func gnus-article-edit-done-function)
5987         (buf (current-buffer))
5988         (start (window-start))
5989         (p (point))
5990         (winconf gnus-prev-winconf))
5991     (remove-hook 'gnus-article-mode-hook
5992                  'gnus-article-mime-edit-article-unwind)
5993     (widen) ;; Widen it in case that users narrowed the buffer.
5994     (funcall func arg)
5995     (set-buffer buf)
5996     ;; The cache and backlog have to be flushed somewhat.
5997     (when gnus-keep-backlog
5998       (gnus-backlog-remove-article
5999        (car gnus-article-current) (cdr gnus-article-current)))
6000     ;; Flush original article as well.
6001     (save-excursion
6002       (when (get-buffer gnus-original-article-buffer)
6003         (set-buffer gnus-original-article-buffer)
6004         (setq gnus-original-article nil)))
6005     (when gnus-use-cache
6006       (gnus-cache-update-article
6007        (car gnus-article-current) (cdr gnus-article-current)))
6008     ;; We remove all text props from the article buffer.
6009     (kill-all-local-variables)
6010     (gnus-set-text-properties (point-min) (point-max) nil)
6011     (gnus-article-mode)
6012     (set-window-configuration winconf)
6013     (set-buffer buf)
6014     (set-window-start (get-buffer-window buf) start)
6015     (set-window-point (get-buffer-window buf) (point)))
6016   (gnus-summary-show-article))
6017
6018 (defun gnus-article-edit-exit ()
6019   "Exit the article editing without updating."
6020   (interactive)
6021   (when (or (not (buffer-modified-p))
6022             (yes-or-no-p "Article modified; kill anyway? "))
6023     (let ((curbuf (current-buffer))
6024           (p (point))
6025           (window-start (window-start)))
6026       (erase-buffer)
6027       (if (gnus-buffer-live-p gnus-original-article-buffer)
6028           (insert-buffer-substring gnus-original-article-buffer))
6029       (let ((winconf gnus-prev-winconf))
6030         (kill-all-local-variables)
6031         (gnus-article-mode)
6032         (set-window-configuration winconf)
6033         ;; Tippy-toe some to make sure that point remains where it was.
6034         (save-current-buffer
6035           (set-buffer curbuf)
6036           (set-window-start (get-buffer-window (current-buffer)) window-start)
6037           (goto-char p))))
6038     (gnus-summary-show-article)))
6039
6040 (defun gnus-article-edit-full-stops ()
6041   "Interactively repair spacing at end of sentences."
6042   (interactive)
6043   (save-excursion
6044     (goto-char (point-min))
6045     (search-forward-regexp "^$" nil t)
6046     (let ((case-fold-search nil))
6047       (query-replace-regexp "\\([.!?][])}]* \\)\\([[({A-Z]\\)" "\\1 \\2"))))
6048
6049 ;;;
6050 ;;; Article editing with MIME-Edit
6051 ;;;
6052
6053 (defcustom gnus-article-mime-edit-article-setup-hook nil
6054   "Hook run after setting up a MIME editing article buffer."
6055   :group 'gnus-article-various
6056   :type 'hook)
6057
6058 (defun gnus-article-mime-edit-article-unwind ()
6059   "Unwind `gnus-article-buffer' if article editing was given up."
6060   (remove-hook 'gnus-article-mode-hook 'gnus-article-mime-edit-article-unwind)
6061   (when (featurep 'font-lock)
6062     (setq font-lock-defaults nil)
6063     (font-lock-mode -1))
6064   (when mime-edit-mode-flag
6065     (mime-edit-exit 'nomime 'no-error)
6066     (message "")))
6067
6068 (defun gnus-article-mime-edit-article-setup ()
6069   "Convert current buffer to MIME-Edit buffer and turn on MIME-Edit mode
6070 after replacing with the original article."
6071   (setq gnus-show-mime t)
6072   (setq gnus-article-edit-done-function
6073         `(lambda (&rest args)
6074            (when mime-edit-mode-flag
6075              (let (mime-edit-insert-user-agent-field)
6076                (mime-edit-exit))
6077              (message ""))
6078            (goto-char (point-min))
6079            (let (case-fold-search)
6080              (when (re-search-forward
6081                     (format "^%s$" (regexp-quote mail-header-separator))
6082                     nil t)
6083                (replace-match "")))
6084            (apply ,gnus-article-edit-done-function args)
6085            (insert
6086             (prog1
6087                 (buffer-substring-no-properties (point-min) (point-max))
6088               (set-buffer (get-buffer-create gnus-original-article-buffer))
6089               (erase-buffer)))
6090            (setq gnus-current-headers (gnus-article-make-full-mail-header))
6091            (set-buffer gnus-article-buffer)
6092            (gnus-article-prepare-display)))
6093   (substitute-key-definition 'gnus-article-edit-done
6094                              'gnus-article-mime-edit-done
6095                              gnus-article-edit-mode-map)
6096   (substitute-key-definition 'gnus-article-edit-exit
6097                              'gnus-article-mime-edit-exit
6098                              gnus-article-edit-mode-map)
6099   (erase-buffer)
6100   (insert-buffer-substring gnus-original-article-buffer)
6101   (unless (member (with-current-buffer gnus-summary-buffer
6102                     gnus-newsgroup-name)
6103                   '("nndraft:delayed" "nndraft:drafts"))
6104     (let ((ofn (symbol-function 'mime-edit-decode-single-part-in-buffer)))
6105       (fset 'mime-edit-decode-single-part-in-buffer
6106             (lambda (&rest args)
6107               (if (let ((content-type (car args)))
6108                     (and (eq 'message (mime-content-type-primary-type
6109                                        content-type))
6110                          (eq 'rfc822 (mime-content-type-subtype
6111                                       content-type))))
6112                   (setcar (cdr args) 'not-decode-text))
6113               (apply ofn args)))
6114       (unwind-protect
6115           (mime-edit-again)
6116         (fset 'mime-edit-decode-single-part-in-buffer ofn))))
6117   (when (featurep 'font-lock)
6118     (set (make-local-variable 'font-lock-defaults)
6119          '(message-font-lock-keywords t))
6120     (font-lock-set-defaults)
6121     (turn-on-font-lock))
6122   (set-buffer-modified-p nil)
6123   (delete-other-windows)
6124   (add-hook 'gnus-article-mode-hook 'gnus-article-mime-edit-article-unwind)
6125   (gnus-run-hooks 'gnus-article-mime-edit-article-setup-hook))
6126
6127 (defun gnus-article-mime-edit-done (&optional arg)
6128   "Update the article MIME edits and exit."
6129   (interactive "P")
6130   (when (featurep 'font-lock)
6131     (setq font-lock-defaults nil)
6132     (font-lock-mode -1))
6133   (let ((inhibit-read-only t))
6134     (gnus-article-edit-done arg)))
6135
6136 (defun gnus-article-mime-edit-exit ()
6137   "Exit the article MIME editing without updating."
6138   (interactive)
6139   (when (or (not (buffer-modified-p))
6140             (yes-or-no-p "Article modified; kill anyway? "))
6141     (when (featurep 'font-lock)
6142       (setq font-lock-defaults nil)
6143       (font-lock-mode -1))
6144     (when mime-edit-mode-flag
6145       (let (mime-edit-insert-user-agent-field)
6146         (mime-edit-exit))
6147       (message ""))
6148     (goto-char (point-min))
6149     (let (case-fold-search)
6150       (when (re-search-forward
6151              (format "^%s$" (regexp-quote mail-header-separator)) nil t)
6152         (replace-match "")))
6153     (let ((winconf gnus-prev-winconf))
6154       (insert (prog1
6155                   (buffer-substring-no-properties (point-min) (point-max))
6156                 (set-buffer (get-buffer-create gnus-original-article-buffer))
6157                 (erase-buffer)))
6158       (setq gnus-current-headers (gnus-article-make-full-mail-header))
6159       (set-buffer gnus-article-buffer)
6160       (gnus-article-prepare-display)
6161       (set-window-configuration winconf))))
6162
6163 ;;;
6164 ;;; Article highlights
6165 ;;;
6166
6167 ;; Written by Per Abrahamsen <abraham@iesd.auc.dk>.
6168
6169 ;;; Internal Variables:
6170
6171 (defcustom gnus-button-url-regexp
6172   (if (string-match "[[:digit:]]" "1") ;; support POSIX?
6173       "\\b\\(\\(www\\.\\|\\(s?https?\\|ftp\\|file\\|gopher\\|nntp\\|news\\|telnet\\|wais\\|mailto\\|info\\):\\)\\(//[-a-z0-9_.]+:[0-9]*\\)?[-a-z0-9_=!?#$@~%&*+\\/:;.,[:word:]]+[-a-z0-9_=#$@~%&*+\\/[:word:]]\\)"
6174     "\\b\\(\\(www\\.\\|\\(s?https?\\|ftp\\|file\\|gopher\\|nntp\\|news\\|telnet\\|wais\\|mailto\\|info\\):\\)\\(//[-a-z0-9_.]+:[0-9]*\\)?\\([-a-z0-9_=!?#$@~%&*+\\/:;.,]\\|\\w\\)+\\([-a-z0-9_=#$@~%&*+\\/]\\|\\w\\)\\)")
6175   "Regular expression that matches URLs."
6176   :group 'gnus-article-buttons
6177   :type 'regexp)
6178
6179 (defcustom gnus-button-valid-fqdn-regexp
6180   message-valid-fqdn-regexp
6181   "Regular expression that matches a valid FQDN."
6182   :group 'gnus-article-buttons
6183   :type 'regexp)
6184
6185 (defcustom gnus-button-man-handler 'manual-entry
6186   "Function to use for displaying man pages.
6187 The function must take at least one argument with a string naming the
6188 man page."
6189   :type '(choice (function-item :tag "Man" manual-entry)
6190                  (function-item :tag "Woman" woman)
6191                  (function :tag "Other"))
6192   :group 'gnus-article-buttons)
6193
6194 (defcustom gnus-ctan-url "http://tug.ctan.org/tex-archive/"
6195   "Top directory of a CTAN \(Comprehensive TeX Archive Network\) archive.
6196 If the default site is too slow, try to find a CTAN mirror, see
6197 <URL:http://tug.ctan.org/tex-archive/CTAN.sites?action=/index.html>.  See also
6198 the variable `gnus-button-handle-ctan'."
6199   :group 'gnus-article-buttons
6200   :link '(custom-manual "(gnus)Group Parameters")
6201   :type '(choice (const "http://www.tex.ac.uk/tex-archive/")
6202                  (const "http://tug.ctan.org/tex-archive/")
6203                  (const "http://www.dante.de/CTAN/")
6204                  (string :tag "Other")))
6205
6206 (defcustom gnus-button-ctan-handler 'browse-url
6207   "Function to use for displaying CTAN links.
6208 The function must take one argument, the string naming the URL."
6209   :type '(choice (function-item :tag "Browse Url" browse-url)
6210                  (function :tag "Other"))
6211   :group 'gnus-article-buttons)
6212
6213 (defcustom gnus-button-handle-ctan-bogus-regexp "^/?tex-archive/\\|^/"
6214   "Bogus strings removed from CTAN URLs."
6215   :group 'gnus-article-buttons
6216   :type '(choice (const "^/?tex-archive/\\|/")
6217                  (regexp :tag "Other")))
6218
6219 (defcustom gnus-button-ctan-directory-regexp
6220   (concat
6221    "\\("; Cannot use `\(?: ... \)' (compatibility with Emacs 20).
6222    "biblio\\|digests\\|dviware\\|fonts\\|graphics\\|help\\|"
6223    "indexing\\|info\\|language\\|macros\\|support\\|systems\\|"
6224    "tds\\|tools\\|usergrps\\|web\\|nonfree\\|obsolete"
6225    "\\)")
6226   "Regular expression for ctan directories.
6227 It should match all directories in the top level of `gnus-ctan-url'."
6228   :group 'gnus-article-buttons
6229   :type 'regexp)
6230
6231 (defcustom gnus-button-mid-or-mail-regexp
6232   (concat "\\b\\(<?[a-z0-9][^<>\")!;:,{}\n\t ]*@"
6233           gnus-button-valid-fqdn-regexp
6234           ">?\\)\\b")
6235   "Regular expression that matches a message ID or a mail address."
6236   :group 'gnus-article-buttons
6237   :type 'regexp)
6238
6239 (defcustom gnus-button-prefer-mid-or-mail 'gnus-button-mid-or-mail-heuristic
6240   "What to do when the button on a string as \"foo123@bar.invalid\" is pushed.
6241 Strings like this can be either a message ID or a mail address.  If it is one
6242 of the symbols `mid' or `mail', Gnus will always assume that the string is a
6243 message ID or a mail address, respectively.  If this variable is set to the
6244 symbol `ask', always query the user what do do.  If it is a function, this
6245 function will be called with the string as it's only argument.  The function
6246 must return `mid', `mail', `invalid' or `ask'."
6247   :group 'gnus-article-buttons
6248   :type '(choice (function-item :tag "Heuristic function"
6249                                 gnus-button-mid-or-mail-heuristic)
6250                  (const ask)
6251                  (const mid)
6252                  (const mail)))
6253
6254 (defcustom gnus-button-mid-or-mail-heuristic-alist
6255   '((-10.0 . ".+\\$.+@")
6256     (-10.0 . "#")
6257     (-10.0 . "\\*")
6258     (-5.0  . "\\+[^+]*\\+.*@") ;; # two plus signs
6259     (-5.0  . "@[Nn][Ee][Ww][Ss]") ;; /\@news/i
6260     (-5.0  . "@.*[Dd][Ii][Aa][Ll][Uu][Pp]") ;; /\@.*dialup/i;
6261     (-1.0  . "^[^a-z]+@")
6262     ;;
6263     (-5.0  . "\\.[0-9][0-9]+.*@") ;; "\.[0-9]{2,}.*\@"
6264     (-5.0  . "[a-z].*[A-Z].*[a-z].*[A-Z].*@") ;; "([a-z].*[A-Z].*){2,}\@"
6265     (-3.0  . "[A-Z][A-Z][a-z][a-z].*@")
6266     (-5.0  . "\\...?.?@") ;; (-5.0 . "\..{1,3}\@")
6267     ;;
6268     (-2.0  . "^[0-9]")
6269     (-1.0  . "^[0-9][0-9]")
6270     ;;
6271     ;; -3.0 /^[0-9][0-9a-fA-F]{2,2}/;
6272     (-3.0  . "^[0-9][0-9a-fA-F][0-9a-fA-F][^0-9a-fA-F]")
6273     ;; -5.0 /^[0-9][0-9a-fA-F]{3,3}/;
6274     (-5.0  . "^[0-9][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][^0-9a-fA-F]")
6275     ;;
6276     (-3.0  .  "[0-9][0-9][0-9][0-9][0-9][^0-9].*@") ;; "[0-9]{5,}.*\@"
6277     (-3.0  .  "[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][^0-9].*@")
6278     ;;       "[0-9]{8,}.*\@"
6279     (-3.0
6280      . "[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9].*@")
6281     ;; "[0-9]{12,}.*\@"
6282     ;; compensation for TDMA dated mail addresses:
6283     (25.0  . "-dated-[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]+.*@")
6284     ;;
6285     (-20.0 . "\\.fsf@") ;; Gnus
6286     (-20.0 . "^slrn")
6287     (-20.0 . "^Pine")
6288     (-20.0 . "_-_") ;; Subject change in thread
6289     ;;
6290     (-20.0 . "\\.ln@") ;; leafnode
6291     (-30.0 . "@ID-[0-9]+\\.[a-zA-Z]+\\.dfncis\\.de")
6292     (-30.0 . "@4[Aa][Xx]\\.com") ;; Forte Agent
6293     ;;
6294     ;; (5.0 . "") ;; $local_part_len <= 7
6295     (10.0  . "^[^0-9]+@")
6296     (3.0   . "^[^0-9]+[0-9][0-9]?[0-9]?@")
6297     ;;      ^[^0-9]+[0-9]{1,3}\@ digits only at end of local part
6298     (3.0   . "\@stud")
6299     ;;
6300     (2.0   . "[a-z][a-z][._-][A-Z][a-z].*@")
6301     ;;
6302     (0.5   . "^[A-Z][a-z]")
6303     (0.5   . "^[A-Z][a-z][a-z]")
6304     (1.5   . "^[A-Z][a-z][A-Z][a-z][^a-z]") ;; ^[A-Z][a-z]{3,3}
6305     (2.0   . "^[A-Z][a-z][A-Z][a-z][a-z][^a-z]")) ;; ^[A-Z][a-z]{4,4}
6306   "An alist of \(RATE . REGEXP\) pairs for `gnus-button-mid-or-mail-heuristic'.
6307
6308 A negative RATE indicates a message IDs, whereas a positive indicates a mail
6309 address.  The REGEXP is processed with `case-fold-search' set to nil."
6310   :group 'gnus-article-buttons
6311   :type '(repeat (cons (number :tag "Rate")
6312                        (regexp :tag "Regexp"))))
6313
6314 (defun gnus-button-mid-or-mail-heuristic (mid-or-mail)
6315   "Guess whether MID-OR-MAIL is a message ID or a mail address.
6316 Returns `mid' if MID-OR-MAIL is a message IDs, `mail' if it's a mail
6317 address, `ask' if unsure and `invalid' if the string is invalid."
6318   (let ((case-fold-search nil)
6319         (list gnus-button-mid-or-mail-heuristic-alist)
6320         (result 0) rate regexp lpartlen elem)
6321     (setq lpartlen
6322           (length (gnus-replace-in-string mid-or-mail "^\\(.*\\)@.*$" "\\1")))
6323     (gnus-message 8 "`%s', length of local part=`%s'." mid-or-mail lpartlen)
6324     ;; Certain special cases...
6325     (when (string-match
6326            (concat
6327             "^0[0-9]+-[0-9][0-9][0-9][0-9]@t-online\\.de$" "\\|"
6328             "^[0-9]+\.[0-9]+\@compuserve")
6329            mid-or-mail)
6330       (gnus-message 8 "`%s' is a known mail address.")
6331       (setq result 'mail))
6332     (when (string-match "@.*@\\| " mid-or-mail)
6333       (gnus-message 8 "`%s' is invalid.")
6334       (setq result 'invalid))
6335     ;; Nothing more to do, if result is not a number here...
6336     (when (numberp result)
6337       (while list
6338         (setq elem (car list)
6339               rate (car elem)
6340               regexp (cdr elem)
6341               list (cdr list))
6342         (when (string-match regexp mid-or-mail)
6343           (setq result (+ result rate))
6344           (gnus-message
6345            9 "`%s' matched `%s', rate `%s', result `%s'."
6346            mid-or-mail regexp rate result)))
6347       (when (<= lpartlen 7)
6348         (setq result (+ result 5.0))
6349         (gnus-message 9 "`%s' matched (<= lpartlen 7), result `%s'."
6350                       mid-or-mail result))
6351       (when (>= lpartlen 12)
6352         (gnus-message 9 "`%s' matched (>= lpartlen 12)" mid-or-mail)
6353         (cond
6354          ((string-match "[0-9][^0-9]+[0-9].*@" mid-or-mail)
6355           ;; Long local part should contain realname if e-mail address,
6356           ;; too many digits: message-id.
6357           ;; $score -= 5.0 + 0.1 * $local_part_len;
6358           (setq rate (* -1.0 (+ 5.0 (* 0.1 lpartlen))))
6359           (setq result (+ result rate))
6360           (gnus-message
6361            9 "Many digits in `%s', rate `%s', result `%s'."
6362            mid-or-mail rate result))
6363          ((string-match "[^aeiouy][^aeiouy][^aeiouy][^aeiouy]+.*\@"
6364                         mid-or-mail)
6365           ;; Too few vowels [^aeiouy]{4,}.*\@
6366           (setq result (+ result -5.0))
6367           (gnus-message
6368            9 "Few vowels in `%s', rate `%s', result `%s'."
6369            mid-or-mail -5.0 result))
6370          (t
6371           (setq result (+ result 5.0))
6372           (gnus-message
6373            9 "`%s', rate `%s', result `%s'." mid-or-mail 5.0 result)))))
6374     (gnus-message 8 "`%s': Final rate is `%s'." mid-or-mail result)
6375     (cond
6376      ;; Maybe we should make this a customizable alist: (condition . 'result)
6377      ((< result -10.0) 'mid)
6378      ((> result  10.0) 'mail)
6379      (t 'ask))))
6380
6381 (defun gnus-button-handle-mid-or-mail (mid-or-mail)
6382   (let* ((pref gnus-button-prefer-mid-or-mail) guessed
6383          (url-mid (concat "news" ":" mid-or-mail))
6384          (url-mailto (concat "mailto" ":" mid-or-mail)))
6385     (gnus-message 9 "mid-or-mail=%s" mid-or-mail)
6386     (when (fboundp pref)
6387       (setq guessed
6388             ;; get rid of surrounding angles...
6389             (funcall pref
6390                      (gnus-replace-in-string mid-or-mail "^<\\|>$" "")))
6391       (if (or (eq 'mid guessed) (eq 'mail guessed))
6392           (setq pref guessed)
6393         (setq pref 'ask)))
6394     (if (eq pref 'ask)
6395         (save-window-excursion
6396           (if (y-or-n-p (concat "Is <" mid-or-mail "> a mail address? "))
6397               (setq pref 'mail)
6398             (setq pref 'mid))))
6399     (cond ((eq pref 'mid)
6400            (gnus-message 8 "calling `gnus-button-handle-news' %s" url-mid)
6401            (gnus-button-handle-news url-mid))
6402           ((eq pref 'mail)
6403            (gnus-message 8 "calling `gnus-url-mailto'  %s" url-mailto)
6404            (gnus-url-mailto url-mailto))
6405           (t (gnus-message 3 "Invalid string.")))))
6406
6407 (defun gnus-button-handle-custom (url)
6408   "Follow a Custom URL."
6409   (customize-apropos (gnus-url-unhex-string url)))
6410
6411 (defvar gnus-button-handle-describe-prefix "^\\(C-h\\|<?[Ff]1>?\\)")
6412
6413 ;; FIXME: Maybe we should merge some of the functions that do quite similar
6414 ;; stuff?
6415
6416 (defun gnus-button-handle-describe-function (url)
6417   "Call `describe-function' when pushing the corresponding URL button."
6418   (describe-function
6419    (intern
6420     (gnus-replace-in-string url gnus-button-handle-describe-prefix ""))))
6421
6422 (defun gnus-button-handle-describe-variable (url)
6423   "Call `describe-variable' when pushing the corresponding URL button."
6424   (describe-variable
6425    (intern
6426     (gnus-replace-in-string url gnus-button-handle-describe-prefix ""))))
6427
6428 (defun gnus-button-handle-symbol (url)
6429 "Display help on variable or function.
6430 Calls `describe-variable' or `describe-function'."
6431   (let ((sym (intern url)))
6432     (cond
6433      ((fboundp sym) (describe-function sym))
6434      ((boundp sym) (describe-variable sym))
6435      (t (gnus-message 3 "`%s' is not a known function of variable." url)))))
6436
6437 (defun gnus-button-handle-describe-key (url)
6438   "Call `describe-key' when pushing the corresponding URL button."
6439   (let* ((key-string
6440           (gnus-replace-in-string url gnus-button-handle-describe-prefix ""))
6441          (keys (ignore-errors (eval `(kbd ,key-string)))))
6442     (if keys
6443         (describe-key keys)
6444       (gnus-message 3 "Invalid key sequence in button: %s" key-string))))
6445
6446 (defun gnus-button-handle-apropos (url)
6447   "Call `apropos' when pushing the corresponding URL button."
6448   (apropos (gnus-replace-in-string url gnus-button-handle-describe-prefix "")))
6449
6450 (defun gnus-button-handle-apropos-command (url)
6451   "Call `apropos' when pushing the corresponding URL button."
6452   (apropos-command
6453    (gnus-replace-in-string url gnus-button-handle-describe-prefix "")))
6454
6455 (defun gnus-button-handle-apropos-variable (url)
6456   "Call `apropos' when pushing the corresponding URL button."
6457   (funcall
6458    (if (fboundp 'apropos-variable) 'apropos-variable 'apropos)
6459    (gnus-replace-in-string url gnus-button-handle-describe-prefix "")))
6460
6461 (defun gnus-button-handle-apropos-documentation (url)
6462   "Call `apropos' when pushing the corresponding URL button."
6463   (funcall
6464    (if (fboundp 'apropos-documentation) 'apropos-documentation 'apropos)
6465    (gnus-replace-in-string url gnus-button-handle-describe-prefix "")))
6466
6467 (defun gnus-button-handle-library (url)
6468   "Call `locate-library' when pushing the corresponding URL button."
6469   (gnus-message 9 "url=`%s'" url)
6470   (let* ((lib (locate-library url))
6471          (file (gnus-replace-in-string (or lib "") "\.elc" ".el")))
6472     (if (not lib)
6473         (gnus-message 1 "Cannot locale library `%s'." url)
6474       (find-file-read-only file))))
6475
6476 (defun gnus-button-handle-ctan (url)
6477   "Call `browse-url' when pushing a CTAN URL button."
6478   (funcall
6479    gnus-button-ctan-handler
6480    (concat
6481     gnus-ctan-url
6482     (gnus-replace-in-string url gnus-button-handle-ctan-bogus-regexp ""))))
6483
6484 (defcustom gnus-button-tex-level 5
6485   "*Integer that says how many TeX-related buttons Gnus will show.
6486 The higher the number, the more buttons will appear and the more false
6487 positives are possible.  Note that you can set this variable local to
6488 specific groups.  Setting it higher in TeX groups is probably a good idea.
6489 See Info node `(gnus)Group Parameters' and the variable `gnus-parameters' on
6490 how to set variables in specific groups."
6491   :group 'gnus-article-buttons
6492   :link '(custom-manual "(gnus)Group Parameters")
6493   :type 'integer)
6494
6495 (defcustom gnus-button-man-level 5
6496   "*Integer that says how many man-related buttons Gnus will show.
6497 The higher the number, the more buttons will appear and the more false
6498 positives are possible.  Note that you can set this variable local to
6499 specific groups.  Setting it higher in Unix groups is probably a good idea.
6500 See Info node `(gnus)Group Parameters' and the variable `gnus-parameters' on
6501 how to set variables in specific groups."
6502   :group 'gnus-article-buttons
6503   :link '(custom-manual "(gnus)Group Parameters")
6504   :type 'integer)
6505
6506 (defcustom gnus-button-emacs-level 5
6507   "*Integer that says how many emacs-related buttons Gnus will show.
6508 The higher the number, the more buttons will appear and the more false
6509 positives are possible.  Note that you can set this variable local to
6510 specific groups.  Setting it higher in Emacs or Gnus related groups is
6511 probably a good idea.  See Info node `(gnus)Group Parameters' and the variable
6512 `gnus-parameters' on how to set variables in specific groups."
6513   :group 'gnus-article-buttons
6514   :link '(custom-manual "(gnus)Group Parameters")
6515   :type 'integer)
6516
6517 (defcustom gnus-button-message-level 5
6518   "*Integer that says how many buttons for news or mail messages will appear.
6519 The higher the number, the more buttons will appear and the more false
6520 positives are possible."
6521   ;; mail addresses, MIDs, URLs for news, ...
6522   :group 'gnus-article-buttons
6523   :type 'integer)
6524
6525 (defcustom gnus-button-browse-level 5
6526   "*Integer that says how many buttons for browsing will appear.
6527 The higher the number, the more buttons will appear and the more false
6528 positives are possible."
6529   ;; stuff handled by `browse-url' or `gnus-button-embedded-url'
6530   :group 'gnus-article-buttons
6531   :type 'integer)
6532
6533 (defcustom gnus-button-alist
6534   '(("<\\(url:[>\n\t ]*?\\)?\\(nntp\\|news\\):[>\n\t ]*\\([^>\n\t ]*@[^>\n\t ]*\\)>"
6535      0 (>= gnus-button-message-level 0) gnus-button-handle-news 3)
6536     ("\\b\\(nntp\\|news\\):\\([^>\n\t ]*@[^>)!;:,\n\t ]*\\)" 0 t
6537      gnus-button-handle-news 2)
6538     ("\\(\\b<\\(url:[>\n\t ]*\\)?\\(nntp\\|news\\):[>\n\t ]*\\(//\\)?\\([^>\n\t ]*\\)>\\)"
6539      1 (>= gnus-button-message-level 0) gnus-button-fetch-group 5)
6540     ("\\b\\(nntp\\|news\\):\\(//\\)?\\([^'\">\n\t ]+\\)"
6541      0 (>= gnus-button-message-level 0) gnus-button-fetch-group 3)
6542     ("\\bin\\( +article\\| +message\\)? +\\(<\\([^\n @<>]+@[^\n @<>]+\\)>\\)"
6543      2 (>= gnus-button-message-level 0) gnus-button-message-id 3)
6544     ("\\(<URL: *\\)mailto: *\\([^> \n\t]+\\)>"
6545      0 (>= gnus-button-message-level 0) gnus-url-mailto 2)
6546     ("mailto:\\([-a-z.@_+0-9%=?]+\\)"
6547      0 (>= gnus-button-message-level 0) gnus-url-mailto 1)
6548     ("\\bmailto:\\([^ \n\t]+\\)"
6549      0 (>= gnus-button-message-level 0) gnus-url-mailto 1)
6550     ;; CTAN
6551     ((concat "\\bCTAN:[ \t\n]?[^>)!;:,'\n\t ]*\\("
6552              gnus-button-ctan-directory-regexp
6553              "[^][>)!;:,'\n\t ]+\\)")
6554      0 (>= gnus-button-tex-level 1) gnus-button-handle-ctan 1)
6555     ((concat "\\btex-archive/\\("
6556              gnus-button-ctan-directory-regexp
6557              "/[-_.a-z0-9/]+[-_./a-z0-9]+[/a-z0-9]\\)")
6558      1 (>= gnus-button-tex-level 6) gnus-button-handle-ctan 1)
6559     ((concat
6560       "\\b\\("
6561       gnus-button-ctan-directory-regexp
6562       "/[-_.a-z0-9]+/[-_./a-z0-9]+[/a-z0-9]\\)")
6563      1 (>= gnus-button-tex-level 8) gnus-button-handle-ctan 1)
6564     ;; This is info
6565     ("\\binfo:\\(//\\)?\\([^'\">\n\t ]+\\)"
6566      0 (>= gnus-button-emacs-level 1) gnus-button-handle-info-url 2)
6567     ("\\((Info-goto-node\\|(info\\)[ \t\n]*\\(\"[^\"]*\"\\))" 0
6568      (>= gnus-button-emacs-level 1) gnus-button-handle-info-url 2)
6569     ("\\b\\(C-h\\|<?[Ff]1>?\\)[ \t\n]+i[ \t\n]+d?[ \t\n]?m[ \t\n]+\\([^ ]+ ?[^ ]+\\)[ \t\n]+RET"
6570      ;; Info links like `C-h i d m CC Mode RET'
6571      0 (>= gnus-button-emacs-level 1) gnus-button-handle-info-keystrokes 2)
6572     ;; This is custom
6573     ("\\bcustom:\\(//\\)?\\([^'\">\n\t ]+\\)"
6574      0 (>= gnus-button-emacs-level 5) gnus-button-handle-custom 2)
6575     ("M-x[ \t\n]customize-[^ ]+[ \t\n]RET[ \t\n]\\([^ ]+\\)[ \t\n]RET" 0
6576      (>= gnus-button-emacs-level 1) gnus-button-handle-custom 1)
6577     ;; Emacs help commands
6578     ("M-x[ \t\n]+apropos[ \t\n]+RET[ \t\n]+\\([^ \t\n]+\\)[ \t\n]+RET"
6579      ;; regexp doesn't match arguments containing ` '.
6580      0 (>= gnus-button-emacs-level 1) gnus-button-handle-apropos 1)
6581     ("M-x[ \t\n]+apropos-command[ \t\n]+RET[ \t\n]+\\([^ \t\n]+\\)[ \t\n]+RET"
6582      0 (>= gnus-button-emacs-level 1) gnus-button-handle-apropos-command 1)
6583     ("M-x[ \t\n]+apropos-variable[ \t\n]+RET[ \t\n]+\\([^ \t\n]+\\)[ \t\n]+RET"
6584      0 (>= gnus-button-emacs-level 1) gnus-button-handle-apropos-variable 1)
6585     ("M-x[ \t\n]+apropos-documentation[ \t\n]+RET[ \t\n]+\\([^ \t\n]+\\)[ \t\n]+RET"
6586      0 (>= gnus-button-emacs-level 1) gnus-button-handle-apropos-documentation 1)
6587     ;; The following entries may lead to many false positives so don't enable
6588     ;; them by default (use a high button level):
6589     ("/\\([a-z][-a-z0-9]+\\.el\\)\\>"
6590      1 (>= gnus-button-emacs-level 8) gnus-button-handle-library 1)
6591     ("`\\([a-z][-a-z0-9]+\\.el\\)'"
6592      1 (>= gnus-button-emacs-level 8) gnus-button-handle-library 1)
6593     ("`\\([a-z][a-z0-9]+-[a-z]+-[-a-z]+\\|\\(gnus\\|message\\)-[-a-z]+\\)'"
6594      0 (>= gnus-button-emacs-level 8) gnus-button-handle-symbol 1)
6595     ("`\\([a-z][a-z0-9]+-[a-z]+\\)'"
6596      0 (>= gnus-button-emacs-level 9) gnus-button-handle-symbol 1)
6597     ("(setq[ \t\n]+\\([a-z][a-z0-9]+-[-a-z0-9]+\\)[ \t\n]+.+)"
6598      1 (>= gnus-button-emacs-level 7) gnus-button-handle-describe-variable 1)
6599     ("\\b\\(C-h\\|<?[Ff]1>?\\)[ \t\n]+f[ \t\n]+\\([^ \t\n]+\\)[ \t\n]+RET"
6600      0 (>= gnus-button-emacs-level 1) gnus-button-handle-describe-function 2)
6601     ("\\b\\(C-h\\|<?[Ff]1>?\\)[ \t\n]+v[ \t\n]+\\([^ \t\n]+\\)[ \t\n]+RET"
6602      0 (>= gnus-button-emacs-level 1) gnus-button-handle-describe-variable 2)
6603     ("`\\(\\b\\(C-h\\|<?[Ff]1>?\\)[ \t\n]+k[ \t\n]+\\([^']+\\)\\)'"
6604      ;; Unlike the other regexps we really have to require quoting
6605      ;; here to determine where it ends.
6606      1 (>= gnus-button-emacs-level 1) gnus-button-handle-describe-key 3)
6607     ;; This is how URLs _should_ be embedded in text (RFC 1738)...
6608     ("<URL: *\\([^<>]*\\)>"
6609      1 (>= gnus-button-browse-level 0) gnus-button-embedded-url 1)
6610     ;; Raw URLs.
6611     (gnus-button-url-regexp
6612      0 (>= gnus-button-browse-level 0) browse-url 0)
6613     ;; man pages
6614     ("\\b\\([a-z][a-z]+\\)([1-9])\\W"
6615      0 (and (>= gnus-button-man-level 1) (< gnus-button-man-level 3))
6616      gnus-button-handle-man 1)
6617     ;; more man pages: resolv.conf(5), iso_8859-1(7), xterm(1x)
6618     ("\\b\\([a-z][-_.a-z0-9]+\\)([1-9])\\W"
6619      0 (and (>= gnus-button-man-level 3) (< gnus-button-man-level 5))
6620      gnus-button-handle-man 1)
6621     ;; even more: Apache::PerlRun(3pm), PDL::IO::FastRaw(3pm),
6622     ;; SoWWWAnchor(3iv), XSelectInput(3X11), X(1), X(7)
6623     ("\\b\\([a-z][-+_.:a-z0-9]+\\)([1-9][X1a-z]*)\\W\\|\\b\\(X\\)([1-9])\\W"
6624      0 (>= gnus-button-man-level 5) gnus-button-handle-man 1)
6625     ;; MID or mail: To avoid too many false positives we don't try to catch
6626     ;; all kind of allowed MIDs or mail addresses.  Domain part must contain
6627     ;; at least one dot.  TLD must contain two or three chars or be a know TLD
6628     ;; (info|name|...).  Put this entry near the _end_ of `gnus-button-alist'
6629     ;; so that non-ambiguous entries (see above) match first.
6630     (gnus-button-mid-or-mail-regexp
6631      0 (>= gnus-button-message-level 5) gnus-button-handle-mid-or-mail 1))
6632   "*Alist of regexps matching buttons in article bodies.
6633
6634 Each entry has the form (REGEXP BUTTON FORM CALLBACK PAR...), where
6635 REGEXP: is the string (case insensitive) matching text around the button (can
6636 also be Lisp expression evaluating to a string),
6637 BUTTON: is the number of the regexp grouping actually matching the button,
6638 FORM: is a Lisp expression which must eval to true for the button to
6639 be added,
6640 CALLBACK: is the function to call when the user push this button, and each
6641 PAR: is a number of a regexp grouping whose text will be passed to CALLBACK.
6642
6643 CALLBACK can also be a variable, in that case the value of that
6644 variable it the real callback function."
6645   :group 'gnus-article-buttons
6646   :type '(repeat (list (choice regexp variable sexp)
6647                        (integer :tag "Button")
6648                        (sexp :tag "Form")
6649                        (function :tag "Callback")
6650                        (repeat :tag "Par"
6651                                :inline t
6652                                (integer :tag "Regexp group")))))
6653
6654 (defcustom gnus-header-button-alist
6655   '(("^\\(References\\|Message-I[Dd]\\|^In-Reply-To\\):" "<[^<>]+>"
6656      0 (>= gnus-button-message-level 0) gnus-button-message-id 0)
6657     ("^\\(From\\|Reply-To\\):" ": *\\(.+\\)$"
6658      1 (>= gnus-button-message-level 0) gnus-button-reply 1)
6659     ("^\\(Cc\\|To\\):" "[^ \t\n<>,()\"]+@[^ \t\n<>,()\"]+"
6660      0 (>= gnus-button-message-level 0) gnus-button-mailto 0)
6661     ("^X-[Uu][Rr][Ll]:" gnus-button-url-regexp
6662      0 (>= gnus-button-browse-level 0) browse-url 0)
6663     ("^Subject:" gnus-button-url-regexp
6664      0 (>= gnus-button-browse-level 0) browse-url 0)
6665     ("^[^:]+:" gnus-button-url-regexp
6666      0 (>= gnus-button-browse-level 0) browse-url 0)
6667     ("^[^:]+:" "\\bmailto:\\([-a-z.@_+0-9%=?]+\\)"
6668      0 (>= gnus-button-message-level 0) gnus-url-mailto 1)
6669     ("^[^:]+:" "\\(<\\(url: \\)?\\(nntp\\|news\\):\\([^>\n ]*\\)>\\)"
6670      1 (>= gnus-button-message-level 0) gnus-button-message-id 4))
6671   "*Alist of headers and regexps to match buttons in article heads.
6672
6673 This alist is very similar to `gnus-button-alist', except that each
6674 alist has an additional HEADER element first in each entry:
6675
6676 \(HEADER REGEXP BUTTON FORM CALLBACK PAR)
6677
6678 HEADER is a regexp to match a header.  For a fuller explanation, see
6679 `gnus-button-alist'."
6680   :group 'gnus-article-buttons
6681   :group 'gnus-article-headers
6682   :type '(repeat (list (regexp :tag "Header")
6683                        (choice regexp variable)
6684                        (integer :tag "Button")
6685                        (sexp :tag "Form")
6686                        (function :tag "Callback")
6687                        (repeat :tag "Par"
6688                                :inline t
6689                                (integer :tag "Regexp group")))))
6690
6691 (defvar gnus-button-regexp nil)
6692 (defvar gnus-button-marker-list nil)
6693 ;; Regexp matching any of the regexps from `gnus-button-alist'.
6694
6695 (defvar gnus-button-last nil)
6696 ;; The value of `gnus-button-alist' when `gnus-button-regexp' was build.
6697
6698 ;;; Commands:
6699
6700 (defun gnus-article-push-button (event)
6701   "Check text under the mouse pointer for a callback function.
6702 If the text under the mouse pointer has a `gnus-callback' property,
6703 call it with the value of the `gnus-data' text property."
6704   (interactive "e")
6705   (set-buffer (window-buffer (posn-window (event-start event))))
6706   (let* ((pos (posn-point (event-start event)))
6707          (data (get-text-property pos 'gnus-data))
6708          (fun (get-text-property pos 'gnus-callback)))
6709     (goto-char pos)
6710     (when fun
6711       (funcall fun data))))
6712
6713 (defun gnus-article-press-button ()
6714   "Check text at point for a callback function.
6715 If the text at point has a `gnus-callback' property,
6716 call it with the value of the `gnus-data' text property."
6717   (interactive)
6718   (let ((data (get-text-property (point) 'gnus-data))
6719         (fun (get-text-property (point) 'gnus-callback)))
6720     (when fun
6721       (funcall fun data))))
6722
6723 (defun gnus-article-prev-button (n)
6724   "Move point to N buttons backward.
6725 If N is negative, move forward instead."
6726   (interactive "p")
6727   (gnus-article-next-button (- n)))
6728
6729 (defun gnus-article-next-button (n)
6730   "Move point to N buttons forward.
6731 If N is negative, move backward instead."
6732   (interactive "p")
6733   (let ((function (if (< n 0) 'previous-single-property-change
6734                     'next-single-property-change))
6735         (inhibit-point-motion-hooks t)
6736         (backward (< n 0))
6737         (limit (if (< n 0) (point-min) (point-max))))
6738     (setq n (abs n))
6739     (while (and (not (= limit (point)))
6740                 (> n 0))
6741       ;; Skip past the current button.
6742       (when (get-text-property (point) 'gnus-callback)
6743         (goto-char (funcall function (point) 'gnus-callback nil limit)))
6744       ;; Go to the next (or previous) button.
6745       (gnus-goto-char (funcall function (point) 'gnus-callback nil limit))
6746       ;; Put point at the start of the button.
6747       (when (and backward (not (get-text-property (point) 'gnus-callback)))
6748         (goto-char (funcall function (point) 'gnus-callback nil limit)))
6749       ;; Skip past intangible buttons.
6750       (when (get-text-property (point) 'intangible)
6751         (incf n))
6752       (decf n))
6753     (unless (zerop n)
6754       (gnus-message 5 "No more buttons"))
6755     n))
6756
6757 (defun gnus-article-highlight (&optional force)
6758   "Highlight current article.
6759 This function calls `gnus-article-highlight-headers',
6760 `gnus-article-highlight-citation',
6761 `gnus-article-highlight-signature', and `gnus-article-add-buttons' to
6762 do the highlighting.  See the documentation for those functions."
6763   (interactive (list 'force))
6764   (gnus-article-highlight-headers)
6765   (gnus-article-highlight-citation force)
6766   (gnus-article-highlight-signature)
6767   (gnus-article-add-buttons force)
6768   (gnus-article-add-buttons-to-head))
6769
6770 (defun gnus-article-highlight-some (&optional force)
6771   "Highlight current article.
6772 This function calls `gnus-article-highlight-headers',
6773 `gnus-article-highlight-signature', and `gnus-article-add-buttons' to
6774 do the highlighting.  See the documentation for those functions."
6775   (interactive (list 'force))
6776   (gnus-article-highlight-headers)
6777   (gnus-article-highlight-signature)
6778   (gnus-article-add-buttons))
6779
6780 (defun gnus-article-highlight-headers ()
6781   "Highlight article headers as specified by `gnus-header-face-alist'."
6782   (interactive)
6783   (save-excursion
6784     (set-buffer gnus-article-buffer)
6785     (save-restriction
6786       (let ((alist gnus-header-face-alist)
6787             (buffer-read-only nil)
6788             (case-fold-search t)
6789             (inhibit-point-motion-hooks t)
6790             entry regexp header-face field-face from hpoints fpoints)
6791         (article-narrow-to-head)
6792         (while (setq entry (pop alist))
6793           (goto-char (point-min))
6794           (setq regexp (concat "^\\("
6795                                (if (string-equal "" (nth 0 entry))
6796                                    "[^\t ]"
6797                                  (nth 0 entry))
6798                                "\\)")
6799                 header-face (nth 1 entry)
6800                 field-face (nth 2 entry))
6801           (while (and (re-search-forward regexp nil t)
6802                       (not (eobp)))
6803             (beginning-of-line)
6804             (setq from (point))
6805             (unless (search-forward ":" nil t)
6806               (forward-char 1))
6807             (when (and header-face
6808                        (not (memq (point) hpoints)))
6809               (push (point) hpoints)
6810               (gnus-put-text-property from (point) 'face header-face))
6811             (when (and field-face
6812                        (not (memq (setq from (point)) fpoints)))
6813               (push from fpoints)
6814               (if (re-search-forward "^[^ \t]" nil t)
6815                   (forward-char -2)
6816                 (goto-char (point-max)))
6817               (gnus-put-text-property from (point) 'face field-face))))))))
6818
6819 (defun gnus-article-highlight-signature ()
6820   "Highlight the signature in an article.
6821 It does this by highlighting everything after
6822 `gnus-signature-separator' using `gnus-signature-face'."
6823   (interactive)
6824   (when gnus-signature-face
6825     (save-excursion
6826       (set-buffer gnus-article-buffer)
6827       (let ((buffer-read-only nil)
6828             (inhibit-point-motion-hooks t))
6829         (save-restriction
6830           (when (gnus-article-narrow-to-signature)
6831             (gnus-overlay-put (gnus-make-overlay (point-min) (point-max))
6832                               'face gnus-signature-face)))))))
6833
6834 (defun gnus-article-buttonize-signature ()
6835   "Add button to the signature."
6836   (interactive)
6837   (save-excursion
6838     (set-buffer gnus-article-buffer)
6839     (let ((buffer-read-only nil)
6840           (inhibit-point-motion-hooks t))
6841       (when (gnus-article-search-signature)
6842         (gnus-article-add-button (match-beginning 0) (match-end 0)
6843                                  'gnus-signature-toggle
6844                                  (set-marker (make-marker)
6845                                              (1+ (match-end 0))))))))
6846
6847 (defun gnus-button-in-region-p (b e prop)
6848   "Say whether PROP exists in the region."
6849   (text-property-not-all b e prop nil))
6850
6851 (defun gnus-article-add-buttons (&optional force)
6852   "Find external references in the article and make buttons of them.
6853 \"External references\" are things like Message-IDs and URLs, as
6854 specified by `gnus-button-alist'."
6855   (interactive (list 'force))
6856   (save-excursion
6857     (set-buffer gnus-article-buffer)
6858     (let ((buffer-read-only nil)
6859           (inhibit-point-motion-hooks t)
6860           (case-fold-search t)
6861           (alist gnus-button-alist)
6862           beg entry regexp)
6863       ;; Remove all old markers.
6864       (let (marker entry new-list)
6865         (while (setq marker (pop gnus-button-marker-list))
6866           (if (or (< marker (point-min)) (>= marker (point-max)))
6867               (push marker new-list)
6868             (goto-char marker)
6869             (when (setq entry (gnus-button-entry))
6870               (put-text-property (match-beginning (nth 1 entry))
6871                                  (match-end (nth 1 entry))
6872                                  'gnus-callback nil))
6873             (set-marker marker nil)))
6874         (setq gnus-button-marker-list new-list))
6875       ;; We skip the headers.
6876       (article-goto-body)
6877       (setq beg (point))
6878       (while (setq entry (pop alist))
6879         (setq regexp (eval (car entry)))
6880         (goto-char beg)
6881         (while (re-search-forward regexp nil t)
6882           (let* ((start (and entry (match-beginning (nth 1 entry))))
6883                  (end (and entry (match-end (nth 1 entry))))
6884                  (from (match-beginning 0)))
6885             (when (and (or (eq t (nth 2 entry))
6886                            (eval (nth 2 entry)))
6887                        (not (gnus-button-in-region-p
6888                              start end 'gnus-callback)))
6889               ;; That optional form returned non-nil, so we add the
6890               ;; button.
6891               (gnus-article-add-button
6892                start end 'gnus-button-push
6893                (car (push (set-marker (make-marker) from)
6894                           gnus-button-marker-list))))))))))
6895
6896 ;; Add buttons to the head of an article.
6897 (defun gnus-article-add-buttons-to-head ()
6898   "Add buttons to the head of the article."
6899   (interactive)
6900   (save-excursion
6901     (set-buffer gnus-article-buffer)
6902     (save-restriction
6903       (let ((buffer-read-only nil)
6904             (inhibit-point-motion-hooks t)
6905             (case-fold-search t)
6906             (alist gnus-header-button-alist)
6907             entry beg end)
6908         (article-narrow-to-head)
6909         (while alist
6910           ;; Each alist entry.
6911           (setq entry (car alist)
6912                 alist (cdr alist))
6913           (goto-char (point-min))
6914           (while (re-search-forward (car entry) nil t)
6915             ;; Each header matching the entry.
6916             (setq beg (match-beginning 0))
6917             (setq end (or (and (re-search-forward "^[^ \t]" nil t)
6918                                (match-beginning 0))
6919                           (point-max)))
6920             (goto-char beg)
6921             (while (re-search-forward (eval (nth 1 entry)) end t)
6922               ;; Each match within a header.
6923               (let* ((entry (cdr entry))
6924                      (start (match-beginning (nth 1 entry)))
6925                      (end (match-end (nth 1 entry)))
6926                      (form (nth 2 entry)))
6927                 (goto-char (match-end 0))
6928                 (when (eval form)
6929                   (gnus-article-add-button
6930                    start end (nth 3 entry)
6931                    (buffer-substring (match-beginning (nth 4 entry))
6932                                      (match-end (nth 4 entry)))))))
6933             (goto-char end)))))))
6934
6935 ;;; External functions:
6936
6937 (defun gnus-article-add-button (from to fun &optional data)
6938   "Create a button between FROM and TO with callback FUN and data DATA."
6939   (when gnus-article-button-face
6940     (gnus-overlay-put (gnus-make-overlay from to)
6941                       'face gnus-article-button-face))
6942   (gnus-add-text-properties
6943    from to
6944    (nconc (and gnus-article-mouse-face
6945                (list gnus-mouse-face-prop gnus-article-mouse-face))
6946           (list 'gnus-callback fun)
6947           (and data (list 'gnus-data data))))
6948   (widget-convert-button 'link from to :action 'gnus-widget-press-button
6949                          ;; Quote `:button-keymap' for Mule 2.3
6950                          ;; but it won't work.
6951                          ':button-keymap gnus-widget-button-keymap))
6952
6953 ;;; Internal functions:
6954
6955 (defun gnus-article-set-globals ()
6956   (save-excursion
6957     (set-buffer gnus-summary-buffer)
6958     (gnus-set-global-variables)))
6959
6960 (defun gnus-signature-toggle (end)
6961   (save-excursion
6962     (set-buffer gnus-article-buffer)
6963     (let ((buffer-read-only nil)
6964           (inhibit-point-motion-hooks t)
6965           (limit (next-single-property-change end 'mime-view-entity
6966                                               nil (point-max))))
6967       (if (text-property-any end limit 'article-type 'signature)
6968           (progn
6969             (gnus-delete-wash-type 'signature)
6970             (gnus-remove-text-properties-when
6971              'article-type 'signature end limit
6972              (cons 'article-type (cons 'signature
6973                                        gnus-hidden-properties))))
6974         (gnus-add-wash-type 'signature)
6975         (gnus-add-text-properties-when
6976          'article-type nil end limit
6977          (cons 'article-type (cons 'signature
6978                                    gnus-hidden-properties)))))
6979     (let ((gnus-article-mime-handle-alist-1 gnus-article-mime-handle-alist))
6980       (gnus-set-mode-line 'article))))
6981
6982 (defun gnus-button-entry ()
6983   ;; Return the first entry in `gnus-button-alist' matching this place.
6984   (let ((alist gnus-button-alist)
6985         (entry nil))
6986     (while alist
6987       (setq entry (pop alist))
6988       (if (looking-at (eval (car entry)))
6989           (setq alist nil)
6990         (setq entry nil)))
6991     entry))
6992
6993 (defun gnus-button-push (marker)
6994   ;; Push button starting at MARKER.
6995   (save-excursion
6996     (goto-char marker)
6997     (let* ((entry (gnus-button-entry))
6998            (inhibit-point-motion-hooks t)
6999            (fun (nth 3 entry))
7000            (args (mapcar (lambda (group)
7001                            (let ((string (match-string group)))
7002                              (gnus-set-text-properties
7003                               0 (length string) nil string)
7004                              string))
7005                          (nthcdr 4 entry))))
7006       (cond
7007        ((fboundp fun)
7008         (apply fun args))
7009        ((and (boundp fun)
7010              (fboundp (symbol-value fun)))
7011         (apply (symbol-value fun) args))
7012        (t
7013         (gnus-message 1 "You must define `%S' to use this button"
7014                       (cons fun args)))))))
7015
7016 (defun gnus-parse-news-url (url)
7017   (let (scheme server group message-id articles)
7018     (with-temp-buffer
7019       (insert url)
7020       (goto-char (point-min))
7021       (when (looking-at "\\([A-Za-z]+\\):")
7022         (setq scheme (match-string 1))
7023         (goto-char (match-end 0)))
7024       (when (looking-at "//\\([^/]+\\)/")
7025         (setq server (match-string 1))
7026         (goto-char (match-end 0)))
7027
7028       (cond
7029        ((looking-at "\\(.*@.*\\)")
7030         (setq message-id (match-string 1)))
7031        ((looking-at "\\([^/]+\\)/\\([-0-9]+\\)")
7032         (setq group (match-string 1)
7033               articles (split-string (match-string 2) "-")))
7034        ((looking-at "\\([^/]+\\)/?")
7035         (setq group (match-string 1)))
7036        (t
7037         (error "Unknown news URL syntax"))))
7038     (list scheme server group message-id articles)))
7039
7040 (defun gnus-button-handle-news (url)
7041   "Fetch a news URL."
7042   (destructuring-bind (scheme server group message-id articles)
7043       (gnus-parse-news-url url)
7044     (cond
7045      (message-id
7046       (save-excursion
7047         (set-buffer gnus-summary-buffer)
7048         (if server
7049             (let ((gnus-refer-article-method (list (list 'nntp server))))
7050               (gnus-summary-refer-article message-id))
7051           (gnus-summary-refer-article message-id))))
7052      (group
7053       (gnus-button-fetch-group url)))))
7054
7055 (defun gnus-button-handle-man (url)
7056   "Fetch a man page."
7057   (funcall gnus-button-man-handler url))
7058
7059 (defun gnus-button-handle-info-url (url)
7060   "Fetch an info URL."
7061   (cond
7062    ((string-match "^\\([^:/]+\\)?/\\(.*\\)" url)
7063     (gnus-info-find-node
7064      (concat "(" (or (gnus-url-unhex-string (match-string 1 url))
7065                      "Gnus")
7066              ")" (gnus-url-unhex-string (match-string 2 url)))))
7067    ((string-match "([^)\"]+)[^\"]+" url)
7068     (setq url
7069           (gnus-replace-in-string
7070            (gnus-replace-in-string url "[\n\t ]+" " ") "\"" ""))
7071     (gnus-info-find-node url))
7072    (t (error "Can't parse %s" url))))
7073
7074 (defun gnus-button-handle-info-keystrokes (url)
7075   "Call `info' when pushing the corresponding URL button."
7076   ;; For links like `C-h i d m gnus RET', `C-h i d m CC Mode RET'.
7077   (info)
7078   (Info-directory)
7079   (Info-menu url))
7080
7081 (defun gnus-button-message-id (message-id)
7082   "Fetch MESSAGE-ID."
7083   (save-excursion
7084     (set-buffer gnus-summary-buffer)
7085     (gnus-summary-refer-article message-id)))
7086
7087 (defun gnus-button-fetch-group (address)
7088   "Fetch GROUP specified by ADDRESS."
7089   (if (not (string-match "[:/]" address))
7090       ;; This is just a simple group url.
7091       (gnus-group-read-ephemeral-group address gnus-select-method)
7092     (if (not
7093          (string-match
7094           "^\\([^:/]+\\)\\(:\\([^/]+\\)\\)?/\\([^/]+\\)\\(/\\([0-9]+\\)\\)?"
7095           address))
7096         (error "Can't parse %s" address)
7097       (gnus-group-read-ephemeral-group
7098        (match-string 4 address)
7099        `(nntp ,(match-string 1 address)
7100               (nntp-address ,(match-string 1 address))
7101               (nntp-port-number ,(if (match-end 3)
7102                                      (match-string 3 address)
7103                                    "nntp")))
7104        nil nil nil
7105        (and (match-end 6) (list (string-to-int (match-string 6 address))))))))
7106
7107 (defun gnus-url-parse-query-string (query &optional downcase)
7108   (let (retval pairs cur key val)
7109     (setq pairs (split-string query "&"))
7110     (while pairs
7111       (setq cur (car pairs)
7112             pairs (cdr pairs))
7113       (if (not (string-match "=" cur))
7114           nil                           ; Grace
7115         (setq key (gnus-url-unhex-string (substring cur 0 (match-beginning 0)))
7116               val (gnus-url-unhex-string (substring cur (match-end 0) nil) t))
7117         (if downcase
7118             (setq key (downcase key)))
7119         (setq cur (assoc key retval))
7120         (if cur
7121             (setcdr cur (cons val (cdr cur)))
7122           (setq retval (cons (list key val) retval)))))
7123     retval))
7124
7125 (defun gnus-url-mailto (url)
7126   ;; Send mail to someone
7127   (when (string-match "mailto:/*\\(.*\\)" url)
7128     (setq url (substring url (match-beginning 1) nil)))
7129   (let (to args subject func)
7130     (if (string-match (regexp-quote "?") url)
7131         (setq to (gnus-url-unhex-string (substring url 0 (match-beginning 0)))
7132               args (gnus-url-parse-query-string
7133                     (substring url (match-end 0) nil) t))
7134       (setq to (gnus-url-unhex-string url)))
7135     (setq args (cons (list "to" to) args)
7136           subject (cdr-safe (assoc "subject" args)))
7137     (gnus-msg-mail)
7138     (while args
7139       (setq func (intern-soft (concat "message-goto-" (downcase (caar args)))))
7140       (if (fboundp func)
7141           (funcall func)
7142         (message-position-on-field (caar args)))
7143       (insert (mapconcat 'identity (cdar args) ", "))
7144       (setq args (cdr args)))
7145     (if subject
7146         (message-goto-body)
7147       (message-goto-subject))))
7148
7149 (defun gnus-button-embedded-url (address)
7150   "Activate ADDRESS with `browse-url'."
7151   (browse-url (gnus-strip-whitespace address)))
7152
7153 ;;; Next/prev buttons in the article buffer.
7154
7155 (defvar gnus-next-page-line-format "%{%(Next page...%)%}\n")
7156 (defvar gnus-prev-page-line-format "%{%(Previous page...%)%}\n")
7157
7158 (defvar gnus-prev-page-map
7159   (let ((map (make-sparse-keymap)))
7160     (unless (>= emacs-major-version 21)
7161       ;; XEmacs doesn't care.
7162       (set-keymap-parent map gnus-article-mode-map))
7163     (define-key map gnus-mouse-2 'gnus-button-prev-page)
7164     (define-key map "\r" 'gnus-button-prev-page)
7165     map))
7166
7167 (defun gnus-insert-prev-page-button ()
7168   (let ((b (point))
7169         (buffer-read-only nil)
7170         (situation (get-text-property (point-min) 'mime-view-situation)))
7171     (gnus-eval-format
7172      gnus-prev-page-line-format nil
7173      `(,@(gnus-local-map-property gnus-prev-page-map)
7174          gnus-prev t
7175          gnus-callback gnus-article-button-prev-page
7176          article-type annotation
7177          mime-view-situation ,situation))
7178     (widget-convert-button
7179      'link b (if (bolp)
7180                  ;; Exclude a newline.
7181                  (1- (point))
7182                (point))
7183      :action 'gnus-button-prev-page
7184      :button-keymap gnus-prev-page-map)))
7185
7186 (defvar gnus-next-page-map
7187   (let ((map (make-sparse-keymap)))
7188     (unless (>= emacs-major-version 21)
7189       ;; XEmacs doesn't care.
7190       (set-keymap-parent map gnus-article-mode-map))
7191     (define-key map gnus-mouse-2 'gnus-button-next-page)
7192     (define-key map "\r" 'gnus-button-next-page)
7193     map))
7194
7195 (defun gnus-button-next-page (&optional args more-args)
7196   "Go to the next page."
7197   (interactive)
7198   (let ((win (selected-window)))
7199     (select-window (gnus-get-buffer-window gnus-article-buffer t))
7200     (gnus-article-next-page)
7201     (select-window win)))
7202
7203 (defun gnus-button-prev-page (&optional args more-args)
7204   "Go to the prev page."
7205   (interactive)
7206   (let ((win (selected-window)))
7207     (select-window (gnus-get-buffer-window gnus-article-buffer t))
7208     (gnus-article-prev-page)
7209     (select-window win)))
7210
7211 (defun gnus-insert-next-page-button ()
7212   (let ((b (point))
7213         (buffer-read-only nil)
7214         (situation (get-text-property (point-min) 'mime-view-situation)))
7215     (gnus-eval-format gnus-next-page-line-format nil
7216                       `(,@(gnus-local-map-property gnus-next-page-map)
7217                           gnus-next t
7218                           gnus-callback gnus-article-button-next-page
7219                           article-type annotation
7220                           mime-view-situation ,situation))
7221     (widget-convert-button
7222      'link b (if (bolp)
7223                  ;; Exclude a newline.
7224                  (1- (point))
7225                (point))
7226      :action 'gnus-button-next-page
7227      :button-keymap gnus-next-page-map)))
7228
7229 (defun gnus-article-button-next-page (arg)
7230   "Go to the next page."
7231   (interactive "P")
7232   (let ((win (selected-window)))
7233     (select-window (gnus-get-buffer-window gnus-article-buffer t))
7234     (gnus-article-next-page)
7235     (select-window win)))
7236
7237 (defun gnus-article-button-prev-page (arg)
7238   "Go to the prev page."
7239   (interactive "P")
7240   (let ((win (selected-window)))
7241     (select-window (gnus-get-buffer-window gnus-article-buffer t))
7242     (gnus-article-prev-page)
7243     (select-window win)))
7244
7245 (defvar gnus-decode-header-methods
7246   '(mail-decode-encoded-word-region)
7247   "List of methods used to decode headers.
7248
7249 This variable is a list of FUNCTION or (REGEXP . FUNCTION).  If item
7250 is FUNCTION, FUNCTION will be apply to all newsgroups.  If item is a
7251 \(REGEXP . FUNCTION), FUNCTION will be only apply to the newsgroups
7252 whose names match REGEXP.
7253
7254 For example:
7255 \((\"chinese\" . gnus-decode-encoded-word-region-by-guess)
7256  mail-decode-encoded-word-region
7257  (\"chinese\" . rfc1843-decode-region))
7258 ")
7259
7260 (defvar gnus-decode-header-methods-cache nil)
7261
7262 (defun gnus-multi-decode-header (start end)
7263   "Apply the functions from `gnus-encoded-word-methods' that match."
7264   (unless (and gnus-decode-header-methods-cache
7265                (eq gnus-newsgroup-name
7266                    (car gnus-decode-header-methods-cache)))
7267     (setq gnus-decode-header-methods-cache (list gnus-newsgroup-name))
7268     (mapcar (lambda (x)
7269               (if (symbolp x)
7270                   (nconc gnus-decode-header-methods-cache (list x))
7271                 (if (and gnus-newsgroup-name
7272                          (string-match (car x) gnus-newsgroup-name))
7273                     (nconc gnus-decode-header-methods-cache
7274                            (list (cdr x))))))
7275             gnus-decode-header-methods))
7276   (let ((xlist gnus-decode-header-methods-cache))
7277     (pop xlist)
7278     (save-restriction
7279       (narrow-to-region start end)
7280       (while xlist
7281         (funcall (pop xlist) (point-min) (point-max))))))
7282
7283 ;;;
7284 ;;; Treatment top-level handling.
7285 ;;;
7286
7287 (defun gnus-treat-article (condition &optional part-number total-parts type)
7288   (let ((length (- (point-max) (point-min)))
7289         (alist gnus-treatment-function-alist)
7290         (article-goto-body-goes-to-point-min-p t)
7291         (treated-type
7292          (or (not type)
7293              (catch 'found
7294                (let ((list gnus-article-treat-types))
7295                  (while list
7296                    (when (string-match (pop list) type)
7297                      (throw 'found t)))))))
7298         (highlightp (gnus-visual-p 'article-highlight 'highlight))
7299         (entity (static-unless (featurep 'xemacs)
7300                   (when (eq 'head condition)
7301                     (get-text-property (point-min) 'mime-view-entity))))
7302         val elem buttonized)
7303     (gnus-run-hooks 'gnus-part-display-hook)
7304     (unless gnus-inhibit-treatment
7305       (dolist (elem alist)
7306         (setq val
7307               (save-excursion
7308                 (when (gnus-buffer-live-p gnus-summary-buffer)
7309                   (set-buffer gnus-summary-buffer))
7310                 (symbol-value (car elem))))
7311         (when (and (or (consp val)
7312                        treated-type)
7313                    (gnus-treat-predicate val)
7314                    (or (not (get (car elem) 'highlight))
7315                        highlightp))
7316           (when (and (not buttonized)
7317                      (memq (car elem)
7318                            '(gnus-treat-hide-signature
7319                              gnus-treat-highlight-signature)))
7320             (gnus-article-buttonize-signature)
7321             (setq buttonized t))
7322           (save-restriction
7323             (funcall (cadr elem)))))
7324       ;; FSF Emacsen does not inherit the existing text properties
7325       ;; in the new text, so we should do it for `mime-view-entity'.
7326       (static-unless (featurep 'xemacs)
7327         (when entity
7328           (put-text-property (point-min) (point-max)
7329                              'mime-view-entity entity))))))
7330
7331 ;; Dynamic variables.
7332 (eval-when-compile
7333   (defvar part-number)
7334   (defvar total-parts)
7335   (defvar type)
7336   (defvar condition)
7337   (defvar length))
7338
7339 (defun gnus-treat-predicate (val)
7340   (cond
7341    ((null val)
7342     nil)
7343    ((and (listp val)
7344          (stringp (car val)))
7345     (apply 'gnus-or (mapcar `(lambda (s)
7346                                (string-match s ,(or gnus-newsgroup-name "")))
7347                             val)))
7348    ((listp val)
7349     (let ((pred (pop val)))
7350       (cond
7351        ((eq pred 'or)
7352         (apply 'gnus-or (mapcar 'gnus-treat-predicate val)))
7353        ((eq pred 'and)
7354         (apply 'gnus-and (mapcar 'gnus-treat-predicate val)))
7355        ((eq pred 'not)
7356         (not (gnus-treat-predicate (car val))))
7357        ((eq pred 'typep)
7358         (equal (car val) type))
7359        (t
7360         (error "%S is not a valid predicate" pred)))))
7361    ((eq val 'mime)
7362     gnus-show-mime)
7363    (condition
7364     (eq condition val))
7365    ((eq val t)
7366     t)
7367    ((eq val 'head)
7368     nil)
7369    ((eq val 'last)
7370     (eq part-number total-parts))
7371    ((numberp val)
7372     (< length val))
7373    (t
7374     (error "%S is not a valid value" val))))
7375
7376 (defun gnus-article-encrypt-body (protocol &optional n)
7377   "Encrypt the article body."
7378   (interactive
7379    (list
7380     (or gnus-article-encrypt-protocol
7381         (completing-read "Encrypt protocol: "
7382                          gnus-article-encrypt-protocol-alist
7383                          nil t))
7384     current-prefix-arg))
7385   (let ((func (cdr (assoc protocol gnus-article-encrypt-protocol-alist))))
7386     (unless func
7387       (error (format "Can't find the encrypt protocol %s" protocol)))
7388     (if (member gnus-newsgroup-name '("nndraft:delayed"
7389                                       "nndraft:drafts"
7390                                       "nndraft:queue"))
7391         (error "Can't encrypt the article in group %s"
7392                gnus-newsgroup-name))
7393     (gnus-summary-iterate n
7394       (save-excursion
7395         (set-buffer gnus-summary-buffer)
7396         (let ((mail-parse-charset gnus-newsgroup-charset)
7397               (mail-parse-ignored-charsets gnus-newsgroup-ignored-charsets)
7398               (summary-buffer gnus-summary-buffer)
7399               references point)
7400           (gnus-set-global-variables)
7401           (when (gnus-group-read-only-p)
7402             (error "The current newsgroup does not support article encrypt"))
7403           (gnus-summary-show-article t)
7404           (setq references
7405                 (or (mail-header-references gnus-current-headers) ""))
7406           (set-buffer gnus-article-buffer)
7407           (let* ((buffer-read-only nil)
7408                  (headers
7409                   (mapcar (lambda (field)
7410                             (and (save-restriction
7411                                    (message-narrow-to-head)
7412                                    (goto-char (point-min))
7413                                    (search-forward field nil t))
7414                                  (prog2
7415                                      (message-narrow-to-field)
7416                                      (buffer-string)
7417                                    (delete-region (point-min) (point-max))
7418                                    (widen))))
7419                           '("Content-Type:" "Content-Transfer-Encoding:"
7420                             "Content-Disposition:"))))
7421             (message-narrow-to-head)
7422             (message-remove-header "MIME-Version")
7423             (goto-char (point-max))
7424             (setq point (point))
7425             (insert (apply 'concat headers))
7426             (widen)
7427             (narrow-to-region point (point-max))
7428             (let ((message-options message-options))
7429               (message-options-set 'message-sender user-mail-address)
7430               (message-options-set 'message-recipients user-mail-address)
7431               (message-options-set 'message-sign-encrypt 'not)
7432               (funcall func))
7433             (goto-char (point-min))
7434             (insert "MIME-Version: 1.0\n")
7435             (widen)
7436             (gnus-summary-edit-article-done
7437              references nil summary-buffer t))
7438           (when gnus-keep-backlog
7439             (gnus-backlog-remove-article
7440              (car gnus-article-current) (cdr gnus-article-current)))
7441           (save-excursion
7442             (when (get-buffer gnus-original-article-buffer)
7443               (set-buffer gnus-original-article-buffer)
7444               (setq gnus-original-article nil)))
7445           (when gnus-use-cache
7446             (gnus-cache-update-article
7447              (car gnus-article-current) (cdr gnus-article-current))))))))
7448
7449 (defvar gnus-mime-security-button-line-format "%{%([[%t:%i]%D]%)%}\n"
7450   "The following specs can be used:
7451 %t  The security MIME type
7452 %i  Additional info
7453 %d  Details
7454 %D  Details if button is pressed")
7455
7456 (defvar gnus-mime-security-button-end-line-format "%{%([[End of %t]%D]%)%}\n"
7457   "The following specs can be used:
7458 %t  The security MIME type
7459 %i  Additional info
7460 %d  Details
7461 %D  Details if button is pressed")
7462
7463 (defvar gnus-mime-security-button-line-format-alist
7464   '((?t gnus-tmp-type ?s)
7465     (?i gnus-tmp-info ?s)
7466     (?d gnus-tmp-details ?s)
7467     (?D gnus-tmp-pressed-details ?s)))
7468
7469 (defvar gnus-mime-security-button-map
7470   (let ((map (make-sparse-keymap)))
7471     (unless (>= (string-to-number emacs-version) 21)
7472       (set-keymap-parent map gnus-article-mode-map))
7473     (define-key map gnus-mouse-2 'gnus-article-push-button)
7474     (define-key map "\r" 'gnus-article-press-button)
7475     map))
7476
7477 (defvar gnus-mime-security-details-buffer nil)
7478
7479 (defvar gnus-mime-security-button-pressed nil)
7480
7481 (defvar gnus-mime-security-show-details-inline t
7482   "If non-nil, show details in the article buffer.")
7483
7484 (defun gnus-mime-security-verify-or-decrypt (handle)
7485   (mm-remove-parts (cdr handle))
7486   (let ((region (mm-handle-multipart-ctl-parameter handle 'gnus-region))
7487         point buffer-read-only)
7488     (if region
7489         (goto-char (car region)))
7490     (save-restriction
7491       (narrow-to-region (point) (point))
7492       (with-current-buffer (mm-handle-multipart-original-buffer handle)
7493         (let* ((mm-verify-option 'known)
7494                (mm-decrypt-option 'known)
7495                (nparts (mm-possibly-verify-or-decrypt (cdr handle) handle)))
7496           (unless (eq nparts (cdr handle))
7497             (mm-destroy-parts (cdr handle))
7498             (setcdr handle nparts))))
7499       (setq point (point))
7500       (gnus-mime-display-security handle)
7501       (goto-char (point-max)))
7502     (when region
7503       (delete-region (point) (cdr region))
7504       (set-marker (car region) nil)
7505       (set-marker (cdr region) nil))
7506     (goto-char point)))
7507
7508 (defun gnus-mime-security-show-details (handle)
7509   (let ((details (mm-handle-multipart-ctl-parameter handle 'gnus-details)))
7510     (if (not details)
7511         (gnus-message 5 "No details.")
7512       (if gnus-mime-security-show-details-inline
7513           (let ((gnus-mime-security-button-pressed
7514                  (not (get-text-property (point) 'gnus-mime-details)))
7515                 (gnus-mime-security-button-line-format
7516                  (get-text-property (point) 'gnus-line-format))
7517                 buffer-read-only)
7518             (forward-char -1)
7519             (while (eq (get-text-property (point) 'gnus-line-format)
7520                        gnus-mime-security-button-line-format)
7521               (forward-char -1))
7522             (forward-char)
7523             (save-restriction
7524               (narrow-to-region (point) (point))
7525               (gnus-insert-mime-security-button handle))
7526             (delete-region (point)
7527                            (or (text-property-not-all
7528                                 (point) (point-max)
7529                                 'gnus-line-format
7530                                 gnus-mime-security-button-line-format)
7531                                (point-max))))
7532         ;; Not inlined.
7533         (if (gnus-buffer-live-p gnus-mime-security-details-buffer)
7534             (with-current-buffer gnus-mime-security-details-buffer
7535               (erase-buffer)
7536               t)
7537           (setq gnus-mime-security-details-buffer
7538                 (gnus-get-buffer-create "*MIME Security Details*")))
7539         (with-current-buffer gnus-mime-security-details-buffer
7540           (insert details)
7541           (goto-char (point-min)))
7542         (pop-to-buffer gnus-mime-security-details-buffer)))))
7543
7544 (defun gnus-mime-security-press-button (handle)
7545   (save-excursion
7546     (if (mm-handle-multipart-ctl-parameter handle 'gnus-info)
7547         (gnus-mime-security-show-details handle)
7548       (gnus-mime-security-verify-or-decrypt handle))))
7549
7550 (defun gnus-insert-mime-security-button (handle &optional displayed)
7551   (let* ((protocol (mm-handle-multipart-ctl-parameter handle 'protocol))
7552          (gnus-tmp-type
7553           (concat
7554            (or (nth 2 (assoc protocol mm-verify-function-alist))
7555                (nth 2 (assoc protocol mm-decrypt-function-alist))
7556                "Unknown")
7557            (if (equal (car handle) "multipart/signed")
7558                " Signed" " Encrypted")
7559            " Part"))
7560          (gnus-tmp-info
7561           (or (mm-handle-multipart-ctl-parameter handle 'gnus-info)
7562               "Undecided"))
7563          (gnus-tmp-details
7564           (mm-handle-multipart-ctl-parameter handle 'gnus-details))
7565          gnus-tmp-pressed-details
7566          b e)
7567     (setq gnus-tmp-details
7568           (if gnus-tmp-details
7569               (concat "\n" gnus-tmp-details)
7570             ""))
7571     (setq gnus-tmp-pressed-details
7572           (if gnus-mime-security-button-pressed gnus-tmp-details ""))
7573     (unless (bolp)
7574       (insert "\n"))
7575     (setq b (point))
7576     (gnus-eval-format
7577      gnus-mime-security-button-line-format
7578      gnus-mime-security-button-line-format-alist
7579      `(,@(gnus-local-map-property gnus-mime-security-button-map)
7580          gnus-callback gnus-mime-security-press-button
7581          gnus-line-format ,gnus-mime-security-button-line-format
7582          gnus-mime-details ,gnus-mime-security-button-pressed
7583          article-type annotation
7584          gnus-data ,handle))
7585     (setq e (if (bolp)
7586                 ;; Exclude a newline.
7587                 (1- (point))
7588               (point)))
7589     (widget-convert-button
7590      'link b e
7591      :mime-handle handle
7592      :action 'gnus-widget-press-button
7593      :button-keymap gnus-mime-security-button-map
7594      :help-echo
7595      (lambda (widget/window &optional overlay pos)
7596        ;; Needed to properly clear the message due to a bug in
7597        ;; wid-edit (XEmacs only).
7598        (when (boundp 'help-echo-owns-message)
7599          (setq help-echo-owns-message t))
7600        (format
7601         "%S: show detail"
7602         (aref gnus-mouse-2 0))))))
7603
7604 (defun gnus-mime-display-security (handle)
7605   (save-restriction
7606     (narrow-to-region (point) (point))
7607     (unless (gnus-unbuttonized-mime-type-p (car handle))
7608       (gnus-insert-mime-security-button handle))
7609     (gnus-mime-display-mixed (cdr handle))
7610     (unless (bolp)
7611       (insert "\n"))
7612     (unless (gnus-unbuttonized-mime-type-p (car handle))
7613       (let ((gnus-mime-security-button-line-format
7614              gnus-mime-security-button-end-line-format))
7615         (gnus-insert-mime-security-button handle)))
7616     (mm-set-handle-multipart-parameter
7617      handle 'gnus-region
7618      (cons (set-marker (make-marker) (point-min))
7619            (set-marker (make-marker) (point-max))))))
7620
7621
7622 ;;; @ for mime-view
7623 ;;;
7624
7625 (defun gnus-article-header-presentation-method (entity situation)
7626   (mime-insert-header entity)
7627   (article-decode-group-name))
7628
7629 (set-alist 'mime-header-presentation-method-alist
7630            'gnus-original-article-mode
7631            #'gnus-article-header-presentation-method)
7632
7633 (defun gnus-mime-preview-quitting-method ()
7634   (mime-preview-kill-buffer)
7635   (delete-other-windows)
7636   (gnus-article-show-summary)
7637   (gnus-summary-select-article gnus-show-all-headers t))
7638
7639 (set-alist 'mime-preview-quitting-method-alist
7640            'gnus-original-article-mode #'gnus-mime-preview-quitting-method)
7641
7642 (set-alist 'mime-preview-following-method-alist
7643            'gnus-original-article-mode #'gnus-following-method)
7644
7645 (set-alist 'mime-preview-over-to-previous-method-alist
7646            'gnus-original-article-mode
7647            (lambda ()
7648              (if (> (point-min) 1)
7649                  (gnus-article-prev-page)
7650                (gnus-article-read-summary-keys
7651                 nil (gnus-character-to-event ?P)))))
7652
7653 (set-alist 'mime-preview-over-to-next-method-alist
7654            'gnus-original-article-mode'
7655            (lambda ()
7656              (if (< (point-max) (buffer-size))
7657                  (gnus-article-next-page)
7658                (gnus-article-read-summary-keys
7659                 nil (gnus-character-to-event ?N)))))
7660
7661
7662 ;;; @ end
7663 ;;;
7664
7665 (gnus-ems-redefine)
7666
7667 (provide 'gnus-art)
7668
7669 (run-hooks 'gnus-art-load-hook)
7670
7671 ;;; gnus-art.el ends here