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