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