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