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