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