Release commit
[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 If `auto-compression-mode' is enabled, compressed files like .gz and .bz2
3991 are decompressed."
3992   (interactive)
3993   (gnus-article-check-buffer)
3994   (let* ((handle (or handle (get-text-property (point) 'gnus-data)))
3995          (contents (and handle (mm-get-part handle)))
3996          (base (and handle
3997                     (file-name-nondirectory
3998                      (or
3999                       (mail-content-type-get (mm-handle-type handle) 'name)
4000                       (mail-content-type-get (mm-handle-disposition handle)
4001                                              'filename)
4002                       "*decoded*"))))
4003          (buffer (and base (generate-new-buffer base))))
4004     (when contents
4005       (switch-to-buffer buffer)
4006       (insert contents)
4007       ;; We do it this way to make `normal-mode' set the appropriate mode.
4008       (unwind-protect
4009           (progn
4010             (setq buffer-file-name (expand-file-name base))
4011             (gnus-mime-jka-compr-maybe-uncompress)
4012             (normal-mode))
4013         (setq buffer-file-name nil))
4014       (goto-char (point-min)))))
4015
4016 (defun gnus-mime-print-part (&optional handle filename)
4017   "Print the MIME part under point."
4018   (interactive (list nil (ps-print-preprint current-prefix-arg)))
4019   (gnus-article-check-buffer)
4020   (let* ((handle (or handle (get-text-property (point) 'gnus-data)))
4021          (contents (and handle (mm-get-part handle)))
4022          (file (mm-make-temp-file (expand-file-name "mm." mm-tmp-directory)))
4023          (printer (mailcap-mime-info (mm-handle-media-type handle) "print")))
4024     (when contents
4025         (if printer
4026             (unwind-protect
4027                 (progn
4028                   (mm-save-part-to-file handle file)
4029                   (call-process shell-file-name nil
4030                                 (generate-new-buffer " *mm*")
4031                                 nil
4032                                 shell-command-switch
4033                                 (mm-mailcap-command
4034                                  printer file (mm-handle-type handle))))
4035               (delete-file file))
4036           (with-temp-buffer
4037             (insert contents)
4038             (gnus-print-buffer))
4039           (ps-despool filename)))))
4040
4041 (defun gnus-mime-inline-part (&optional handle arg)
4042   "Insert the MIME part under point into the current buffer."
4043   (interactive (list nil current-prefix-arg))
4044   (gnus-article-check-buffer)
4045   (let* ((handle (or handle (get-text-property (point) 'gnus-data)))
4046          contents charset
4047          (b (point))
4048          buffer-read-only)
4049     (when handle
4050       (if (and (not arg) (mm-handle-undisplayer handle))
4051           (mm-remove-part handle)
4052         (setq contents (mm-get-part handle))
4053         (cond
4054          ((not arg)
4055           (setq charset (or (mail-content-type-get
4056                              (mm-handle-type handle) 'charset)
4057                             gnus-newsgroup-charset)))
4058          ((numberp arg)
4059           (if (mm-handle-undisplayer handle)
4060               (mm-remove-part handle))
4061           (setq charset
4062                 (or (cdr (assq arg
4063                                gnus-summary-show-article-charset-alist))
4064                     (mm-read-coding-system "Charset: ")))))
4065         (forward-line 2)
4066         (mm-insert-inline handle
4067                           (if (and charset
4068                                    (setq charset (mm-charset-to-coding-system
4069                                                   charset))
4070                                    (not (eq charset 'ascii)))
4071                               (mm-decode-coding-string contents charset)
4072                             contents))
4073         (goto-char b)))))
4074
4075 (defun gnus-mime-view-part-as-charset (&optional handle arg)
4076   "Insert the MIME part under point into the current buffer using the
4077 specified charset."
4078   (interactive (list nil current-prefix-arg))
4079   (gnus-article-check-buffer)
4080   (let* ((handle (or handle (get-text-property (point) 'gnus-data)))
4081          contents charset
4082          (b (point))
4083          buffer-read-only)
4084     (when handle
4085       (if (mm-handle-undisplayer handle)
4086           (mm-remove-part handle))
4087       (let ((gnus-newsgroup-charset
4088              (or (cdr (assq arg
4089                             gnus-summary-show-article-charset-alist))
4090                  (mm-read-coding-system "Charset: ")))
4091           (gnus-newsgroup-ignored-charsets 'gnus-all))
4092         (gnus-article-press-button)))))
4093
4094 (defun gnus-mime-view-part-externally (&optional handle)
4095   "View the MIME part under point with an external viewer."
4096   (interactive)
4097   (gnus-article-check-buffer)
4098   (let* ((handle (or handle (get-text-property (point) 'gnus-data)))
4099          (mm-user-display-methods nil)
4100          (mm-inlined-types nil)
4101          (mail-parse-charset gnus-newsgroup-charset)
4102          (mail-parse-ignored-charsets
4103           (save-excursion (set-buffer gnus-summary-buffer)
4104                           gnus-newsgroup-ignored-charsets)))
4105     (when handle
4106       (if (mm-handle-undisplayer handle)
4107           (mm-remove-part handle)
4108         (mm-display-part handle)))))
4109
4110 (defun gnus-mime-view-part-internally (&optional handle)
4111   "View the MIME part under point with an internal viewer.
4112 If no internal viewer is available, use an external viewer."
4113   (interactive)
4114   (gnus-article-check-buffer)
4115   (let* ((handle (or handle (get-text-property (point) 'gnus-data)))
4116          (mm-inlined-types '(".*"))
4117          (mm-inline-large-images t)
4118          (mail-parse-charset gnus-newsgroup-charset)
4119          (mail-parse-ignored-charsets
4120           (save-excursion (set-buffer gnus-summary-buffer)
4121                           gnus-newsgroup-ignored-charsets)))
4122     (when handle
4123       (if (mm-handle-undisplayer handle)
4124           (mm-remove-part handle)
4125         (mm-display-part handle)))))
4126
4127 (defun gnus-mime-action-on-part (&optional action)
4128   "Do something with the MIME attachment at \(point\)."
4129   (interactive
4130    (list (completing-read "Action: " gnus-mime-action-alist)))
4131   (gnus-article-check-buffer)
4132   (let ((action-pair (assoc action gnus-mime-action-alist)))
4133     (if action-pair
4134         (funcall (cdr action-pair)))))
4135
4136 (defun gnus-article-part-wrapper (n function)
4137   (save-current-buffer
4138     (set-buffer gnus-article-buffer)
4139     (when (> n (length gnus-article-mime-handle-alist))
4140       (error "No such part"))
4141     (gnus-article-goto-part n)
4142     (let ((handle (cdr (assq n gnus-article-mime-handle-alist))))
4143       (funcall function handle))))
4144
4145 (defun gnus-article-pipe-part (n)
4146   "Pipe MIME part N, which is the numerical prefix."
4147   (interactive "p")
4148   (gnus-article-part-wrapper n 'mm-pipe-part))
4149
4150 (defun gnus-article-save-part (n)
4151   "Save MIME part N, which is the numerical prefix."
4152   (interactive "p")
4153   (gnus-article-part-wrapper n 'mm-save-part))
4154
4155 (defun gnus-article-interactively-view-part (n)
4156   "View MIME part N interactively, which is the numerical prefix."
4157   (interactive "p")
4158   (gnus-article-part-wrapper n 'mm-interactively-view-part))
4159
4160 (defun gnus-article-copy-part (n)
4161   "Copy MIME part N, which is the numerical prefix."
4162   (interactive "p")
4163   (gnus-article-part-wrapper n 'gnus-mime-copy-part))
4164
4165 (defun gnus-article-view-part-as-charset (n)
4166   "Copy MIME part N, which is the numerical prefix."
4167   (interactive "p")
4168   (gnus-article-part-wrapper n 'gnus-mime-view-part-as-charset))
4169
4170 (defun gnus-article-view-part-externally (n)
4171   "View MIME part N externally, which is the numerical prefix."
4172   (interactive "p")
4173   (gnus-article-part-wrapper n 'gnus-mime-view-part-externally))
4174
4175 (defun gnus-article-inline-part (n)
4176   "Inline MIME part N, which is the numerical prefix."
4177   (interactive "p")
4178   (gnus-article-part-wrapper n 'gnus-mime-inline-part))
4179
4180 (defun gnus-article-mime-match-handle-first (condition)
4181   (if condition
4182       (let ((alist gnus-article-mime-handle-alist) ihandle n)
4183         (while (setq ihandle (pop alist))
4184           (if (and (cond
4185                     ((functionp condition)
4186                      (funcall condition (cdr ihandle)))
4187                     ((eq condition 'undisplayed)
4188                      (not (or (mm-handle-undisplayer (cdr ihandle))
4189                               (equal (mm-handle-media-type (cdr ihandle))
4190                                      "multipart/alternative"))))
4191                     ((eq condition 'undisplayed-alternative)
4192                      (not (mm-handle-undisplayer (cdr ihandle))))
4193                     (t t))
4194                    (gnus-article-goto-part (car ihandle))
4195                    (or (not n) (< (car ihandle) n)))
4196               (setq n (car ihandle))))
4197         (or n 1))
4198     1))
4199
4200 (defun gnus-article-view-part (&optional n)
4201   "View MIME part N, which is the numerical prefix."
4202   (interactive "P")
4203   (save-current-buffer
4204     (set-buffer gnus-article-buffer)
4205     (or (numberp n) (setq n (gnus-article-mime-match-handle-first
4206                              gnus-article-mime-match-handle-function)))
4207     (when (> n (length gnus-article-mime-handle-alist))
4208       (error "No such part"))
4209     (let ((handle (cdr (assq n gnus-article-mime-handle-alist))))
4210       (when (gnus-article-goto-part n)
4211         (if (equal (car handle) "multipart/alternative")
4212             (gnus-article-press-button)
4213           (when (eq (gnus-mm-display-part handle) 'internal)
4214             (gnus-set-window-start)))))))
4215
4216 (defsubst gnus-article-mime-total-parts ()
4217   (if (bufferp (car gnus-article-mime-handles))
4218       1 ;; single part
4219     (1- (length gnus-article-mime-handles))))
4220
4221 (defun gnus-mm-display-part (handle)
4222   "Display HANDLE and fix MIME button."
4223   (let ((id (get-text-property (point) 'gnus-part))
4224         (point (point))
4225         buffer-read-only)
4226     (forward-line 1)
4227     (prog1
4228         (let ((window (selected-window))
4229               (mail-parse-charset gnus-newsgroup-charset)
4230               (mail-parse-ignored-charsets
4231                (if (gnus-buffer-live-p gnus-summary-buffer)
4232                    (save-excursion
4233                      (set-buffer gnus-summary-buffer)
4234                      gnus-newsgroup-ignored-charsets)
4235                  nil)))
4236           (save-excursion
4237             (unwind-protect
4238                 (let ((win (gnus-get-buffer-window (current-buffer) t))
4239                       (beg (point)))
4240                   (when win
4241                     (select-window win))
4242                   (goto-char point)
4243                   (forward-line)
4244                   (if (mm-handle-displayed-p handle)
4245                       ;; This will remove the part.
4246                       (mm-display-part handle)
4247                     (save-restriction
4248                       (narrow-to-region (point)
4249                                         (if (eobp) (point) (1+ (point))))
4250                       (mm-display-part handle)
4251                       ;; We narrow to the part itself and
4252                       ;; then call the treatment functions.
4253                       (goto-char (point-min))
4254                       (forward-line 1)
4255                       (narrow-to-region (point) (point-max))
4256                       (gnus-treat-article
4257                        nil id
4258                        (gnus-article-mime-total-parts)
4259                        (mm-handle-media-type handle)))))
4260               (if (window-live-p window)
4261                   (select-window window)))))
4262       (goto-char point)
4263       (delete-region (gnus-point-at-bol) (progn (forward-line 1) (point)))
4264       (gnus-insert-mime-button
4265        handle id (list (mm-handle-displayed-p handle)))
4266       (goto-char point))))
4267
4268 (defun gnus-article-goto-part (n)
4269   "Go to MIME part N."
4270   (let ((point (text-property-any (point-min) (point-max) 'gnus-part n)))
4271     (when point
4272       (goto-char point))))
4273
4274 (defun gnus-insert-mime-button (handle gnus-tmp-id &optional displayed)
4275   (let ((gnus-tmp-name
4276          (or (mail-content-type-get (mm-handle-type handle) 'name)
4277              (mail-content-type-get (mm-handle-disposition handle) 'filename)
4278              (mail-content-type-get (mm-handle-type handle) 'url)
4279              ""))
4280         (gnus-tmp-type (mm-handle-media-type handle))
4281         (gnus-tmp-description
4282          (mail-decode-encoded-word-string (or (mm-handle-description handle)
4283                                               "")))
4284         (gnus-tmp-dots
4285          (if (if displayed (car displayed)
4286                (mm-handle-displayed-p handle))
4287              "" "..."))
4288         (gnus-tmp-length (with-current-buffer (mm-handle-buffer handle)
4289                            (buffer-size)))
4290         gnus-tmp-type-long b e)
4291     (when (string-match ".*/" gnus-tmp-name)
4292       (setq gnus-tmp-name (replace-match "" t t gnus-tmp-name)))
4293     (setq gnus-tmp-type-long (concat gnus-tmp-type
4294                                      (and (not (equal gnus-tmp-name ""))
4295                                           (concat "; " gnus-tmp-name))))
4296     (unless (equal gnus-tmp-description "")
4297       (setq gnus-tmp-type-long (concat " --- " gnus-tmp-type-long)))
4298     (unless (bolp)
4299       (insert "\n"))
4300     (setq b (point))
4301     (gnus-eval-format
4302      gnus-mime-button-line-format gnus-mime-button-line-format-alist
4303      `(,@(gnus-local-map-property gnus-mime-button-map)
4304          gnus-callback gnus-mm-display-part
4305          gnus-part ,gnus-tmp-id
4306          article-type annotation
4307          gnus-data ,handle))
4308     (setq e (point))
4309     (widget-convert-button
4310      'link b e
4311      :mime-handle handle
4312      :action 'gnus-widget-press-button
4313      :button-keymap gnus-mime-button-map
4314      :help-echo
4315      (lambda (widget/window &optional overlay pos)
4316        ;; Needed to properly clear the message due to a bug in
4317        ;; wid-edit (XEmacs only).
4318        (if (boundp 'help-echo-owns-message)
4319            (setq help-echo-owns-message t))
4320        (format
4321         "%S: %s the MIME part; %S: more options"
4322         (aref gnus-mouse-2 0)
4323         ;; XEmacs will get a single widget arg; Emacs 21 will get
4324         ;; window, overlay, position.
4325         (if (mm-handle-displayed-p
4326              (if overlay
4327                  (with-current-buffer (gnus-overlay-buffer overlay)
4328                    (widget-get (widget-at (gnus-overlay-start overlay))
4329                                :mime-handle))
4330                (widget-get widget/window :mime-handle)))
4331             "hide" "show")
4332         (aref gnus-down-mouse-3 0))))))
4333
4334 (defun gnus-widget-press-button (elems el)
4335   (goto-char (widget-get elems :from))
4336   (gnus-article-press-button))
4337
4338 (defvar gnus-displaying-mime nil)
4339
4340 (defun gnus-display-mime (&optional ihandles)
4341   "Display the MIME parts."
4342   (save-excursion
4343     (save-selected-window
4344       (let ((window (get-buffer-window gnus-article-buffer))
4345             (point (point)))
4346         (when window
4347           (select-window window)
4348           ;; We have to do this since selecting the window
4349           ;; may change the point.  So we set the window point.
4350           (set-window-point window point)))
4351       (let* ((handles (or ihandles
4352                           (mm-dissect-buffer nil gnus-article-loose-mime)
4353                           (and gnus-article-emulate-mime
4354                                (mm-uu-dissect))))
4355              buffer-read-only handle name type b e display)
4356         (when (and (not ihandles)
4357                    (not gnus-displaying-mime))
4358           ;; Top-level call; we clean up.
4359           (when gnus-article-mime-handles
4360             (mm-destroy-parts gnus-article-mime-handles)
4361             (setq gnus-article-mime-handle-alist nil));; A trick.
4362           (setq gnus-article-mime-handles handles)
4363           ;; We allow users to glean info from the handles.
4364           (when gnus-article-mime-part-function
4365             (gnus-mime-part-function handles)))
4366         (if (and handles
4367                  (or (not (stringp (car handles)))
4368                      (cdr handles)))
4369             (progn
4370               (when (and (not ihandles)
4371                          (not gnus-displaying-mime))
4372                 ;; Clean up for mime parts.
4373                 (article-goto-body)
4374                 (delete-region (point) (point-max)))
4375               (let ((gnus-displaying-mime t))
4376                 (gnus-mime-display-part handles)))
4377           (save-restriction
4378             (article-goto-body)
4379             (narrow-to-region (point) (point-max))
4380             (gnus-treat-article nil 1 1)
4381             (widen)))
4382         (unless ihandles
4383           ;; Highlight the headers.
4384           (save-excursion
4385             (save-restriction
4386               (article-goto-body)
4387               (narrow-to-region (point-min) (point))
4388               (gnus-treat-article 'head))))))))
4389
4390 (defvar gnus-mime-display-multipart-as-mixed nil)
4391 (defvar gnus-mime-display-multipart-alternative-as-mixed nil)
4392 (defvar gnus-mime-display-multipart-related-as-mixed nil)
4393
4394 (defun gnus-mime-display-part (handle)
4395   (cond
4396    ;; Single part.
4397    ((not (stringp (car handle)))
4398     (gnus-mime-display-single handle))
4399    ;; User-defined multipart
4400    ((cdr (assoc (car handle) gnus-mime-multipart-functions))
4401     (funcall (cdr (assoc (car handle) gnus-mime-multipart-functions))
4402              handle))
4403    ;; multipart/alternative
4404    ((and (equal (car handle) "multipart/alternative")
4405          (not (or gnus-mime-display-multipart-as-mixed
4406                   gnus-mime-display-multipart-alternative-as-mixed)))
4407     (let ((id (1+ (length gnus-article-mime-handle-alist))))
4408       (push (cons id handle) gnus-article-mime-handle-alist)
4409       (gnus-mime-display-alternative (cdr handle) nil nil id)))
4410    ;; multipart/related
4411    ((and (equal (car handle) "multipart/related")
4412          (not (or gnus-mime-display-multipart-as-mixed
4413                   gnus-mime-display-multipart-related-as-mixed)))
4414     ;;;!!!We should find the start part, but we just default
4415     ;;;!!!to the first part.
4416     ;;(gnus-mime-display-part (cadr handle))
4417     ;;;!!! Most multipart/related is an HTML message plus images.
4418     ;;;!!! Unfortunately we are unable to let W3 display those
4419     ;;;!!! included images, so we just display it as a mixed multipart.
4420     ;;(gnus-mime-display-mixed (cdr handle))
4421     ;;;!!! No, w3 can display everything just fine.
4422     (gnus-mime-display-part (cadr handle)))
4423    ((equal (car handle) "multipart/signed")
4424     (gnus-add-wash-type 'signed)
4425     (gnus-mime-display-security handle))
4426    ((equal (car handle) "multipart/encrypted")
4427     (gnus-add-wash-type 'encrypted)
4428     (gnus-mime-display-security handle))
4429    ;; Other multiparts are handled like multipart/mixed.
4430    (t
4431     (gnus-mime-display-mixed (cdr handle)))))
4432
4433 (defun gnus-mime-part-function (handles)
4434   (if (stringp (car handles))
4435       (mapcar 'gnus-mime-part-function (cdr handles))
4436     (funcall gnus-article-mime-part-function handles)))
4437
4438 (defun gnus-mime-display-mixed (handles)
4439   (mapcar 'gnus-mime-display-part handles))
4440
4441 (defun gnus-mime-display-single (handle)
4442   (let ((type (mm-handle-media-type handle))
4443         (ignored gnus-ignored-mime-types)
4444         (not-attachment t)
4445         (move nil)
4446         display text)
4447     (catch 'ignored
4448       (progn
4449         (while ignored
4450           (when (string-match (pop ignored) type)
4451             (throw 'ignored nil)))
4452         (if (and (setq not-attachment
4453                        (and (not (mm-inline-override-p handle))
4454                             (or (not (mm-handle-disposition handle))
4455                                 (equal (car (mm-handle-disposition handle))
4456                                        "inline")
4457                                 (mm-attachment-override-p handle))))
4458                  (mm-automatic-display-p handle)
4459                  (or (and
4460                       (mm-inlinable-p handle)
4461                       (mm-inlined-p handle))
4462                      (mm-automatic-external-display-p type)))
4463             (setq display t)
4464           (when (equal (mm-handle-media-supertype handle) "text")
4465             (setq text t)))
4466         (let ((id (1+ (length gnus-article-mime-handle-alist)))
4467               beg)
4468           (push (cons id handle) gnus-article-mime-handle-alist)
4469           (when (or (not display)
4470                     (not (gnus-unbuttonized-mime-type-p type)))
4471             ;(gnus-article-insert-newline)
4472             (gnus-insert-mime-button
4473              handle id (list (or display (and not-attachment text))))
4474             (gnus-article-insert-newline)
4475             ;(gnus-article-insert-newline)
4476             ;; Remember modify the number of forward lines.
4477             (setq move t))
4478           (setq beg (point))
4479           (cond
4480            (display
4481             (when move
4482               (forward-line -1)
4483               (setq beg (point)))
4484             (let ((mail-parse-charset gnus-newsgroup-charset)
4485                   (mail-parse-ignored-charsets
4486                    (save-excursion (condition-case ()
4487                                        (set-buffer gnus-summary-buffer)
4488                                      (error))
4489                                    gnus-newsgroup-ignored-charsets)))
4490               (mm-display-part handle t))
4491             (goto-char (point-max)))
4492            ((and text not-attachment)
4493             (when move
4494               (forward-line -1)
4495               (setq beg (point)))
4496             (gnus-article-insert-newline)
4497             (mm-insert-inline handle (mm-get-part handle))
4498             (goto-char (point-max))))
4499           ;; Do highlighting.
4500           (save-excursion
4501             (save-restriction
4502               (narrow-to-region beg (point))
4503               (gnus-treat-article
4504                nil id
4505                (gnus-article-mime-total-parts)
4506                (mm-handle-media-type handle)))))))))
4507
4508 (defun gnus-unbuttonized-mime-type-p (type)
4509   "Say whether TYPE is to be unbuttonized."
4510   (unless gnus-inhibit-mime-unbuttonizing
4511     (when (catch 'found
4512             (let ((types gnus-unbuttonized-mime-types))
4513               (while types
4514                 (when (string-match (pop types) type)
4515                   (throw 'found t)))))
4516       (not (catch 'found
4517              (let ((types gnus-buttonized-mime-types))
4518                (while types
4519                  (when (string-match (pop types) type)
4520                    (throw 'found t)))))))))
4521
4522 (defun gnus-article-insert-newline ()
4523   "Insert a newline, but mark it as undeletable."
4524   (gnus-put-text-property
4525    (point) (progn (insert "\n") (point)) 'gnus-undeletable t))
4526
4527 (defun gnus-mime-display-alternative (handles &optional preferred ibegend id)
4528   (let* ((preferred (or preferred (mm-preferred-alternative handles)))
4529          (ihandles handles)
4530          (point (point))
4531          handle buffer-read-only from props begend not-pref)
4532     (save-window-excursion
4533       (save-restriction
4534         (when ibegend
4535           (narrow-to-region (car ibegend)
4536                             (or (cdr ibegend)
4537                                 (progn
4538                                   (goto-char (car ibegend))
4539                                   (forward-line 2)
4540                                   (point))))
4541           (delete-region (point-min) (point-max))
4542           (mm-remove-parts handles))
4543         (setq begend (list (point-marker)))
4544         ;; Do the toggle.
4545         (unless (setq not-pref (cadr (member preferred ihandles)))
4546           (setq not-pref (car ihandles)))
4547         (when (or ibegend
4548                   (not preferred)
4549                   (not (gnus-unbuttonized-mime-type-p
4550                         "multipart/alternative")))
4551           (gnus-add-text-properties
4552            (setq from (point))
4553            (progn
4554              (insert (format "%d.  " id))
4555              (point))
4556            `(gnus-callback
4557              (lambda (handles)
4558                (unless ,(not ibegend)
4559                  (setq gnus-article-mime-handle-alist
4560                        ',gnus-article-mime-handle-alist))
4561                (gnus-mime-display-alternative
4562                 ',ihandles ',not-pref ',begend ,id))
4563              ,@(gnus-local-map-property gnus-mime-button-map)
4564              ,gnus-mouse-face-prop ,gnus-article-mouse-face
4565              face ,gnus-article-button-face
4566              gnus-part ,id
4567              gnus-data ,handle))
4568           (widget-convert-button 'link from (point)
4569                                  :action 'gnus-widget-press-button
4570                                  :button-keymap gnus-widget-button-keymap)
4571           ;; Do the handles
4572           (while (setq handle (pop handles))
4573             (gnus-add-text-properties
4574              (setq from (point))
4575              (progn
4576                (insert (format "(%c) %-18s"
4577                                (if (equal handle preferred) ?* ? )
4578                                (mm-handle-media-type handle)))
4579                (point))
4580              `(gnus-callback
4581                (lambda (handles)
4582                  (unless ,(not ibegend)
4583                    (setq gnus-article-mime-handle-alist
4584                          ',gnus-article-mime-handle-alist))
4585                  (gnus-mime-display-alternative
4586                   ',ihandles ',handle ',begend ,id))
4587                ,@(gnus-local-map-property gnus-mime-button-map)
4588                ,gnus-mouse-face-prop ,gnus-article-mouse-face
4589                face ,gnus-article-button-face
4590                gnus-part ,id
4591                gnus-data ,handle))
4592             (widget-convert-button 'link from (point)
4593                                    :action 'gnus-widget-press-button
4594                                    :button-keymap gnus-widget-button-keymap)
4595             (insert "  "))
4596           (insert "\n\n"))
4597         (when preferred
4598           (if (stringp (car preferred))
4599               (gnus-display-mime preferred)
4600             (let ((mail-parse-charset gnus-newsgroup-charset)
4601                   (mail-parse-ignored-charsets
4602                    (save-excursion (set-buffer gnus-summary-buffer)
4603                                    gnus-newsgroup-ignored-charsets)))
4604               (mm-display-part preferred)
4605               ;; Do highlighting.
4606               (save-excursion
4607                 (save-restriction
4608                   (narrow-to-region (car begend) (point-max))
4609                   (gnus-treat-article
4610                    nil (length gnus-article-mime-handle-alist)
4611                    (gnus-article-mime-total-parts)
4612                    (mm-handle-media-type handle))))))
4613           (goto-char (point-max))
4614           (setcdr begend (point-marker)))))
4615     (when ibegend
4616       (goto-char point))))
4617
4618 (defconst gnus-article-wash-status-strings
4619   (let ((alist '((cite "c" "Possible hidden citation text"
4620                        " " "All citation text visible")
4621                  (headers "h" "Hidden headers"
4622                           " " "All headers visible.")
4623                  (pgp "p" "Encrypted or signed message status hidden"
4624                       " " "No hidden encryption nor digital signature status")
4625                  (signature "s" "Signature has been hidden"
4626                             " " "Signature is visible")
4627                  (overstrike "o" "Overstrike (^H) characters applied"
4628                              " " "No overstrike characters applied")
4629                  (emphasis "e" "/*_Emphasis_*/ characters applied"
4630                            " " "No /*_emphasis_*/ characters applied")))
4631         result)
4632     (dolist (entry alist result)
4633       (let ((key (nth 0 entry))
4634             (on (copy-sequence (nth 1 entry)))
4635             (on-help (nth 2 entry))
4636             (off (copy-sequence (nth 3 entry)))
4637             (off-help (nth 4 entry)))
4638         (put-text-property 0 1 'help-echo on-help on)
4639         (put-text-property 0 1 'help-echo off-help off)
4640         (push (list key on off) result))))
4641   "Alist of strings describing wash status in the mode line.
4642 Each entry has the form (KEY ON OF), where the KEY is a symbol
4643 representing the particular washing function, ON is the string to use
4644 in the article mode line when the washing function is active, and OFF
4645 is the string to use when it is inactive.")
4646
4647 (defun gnus-article-wash-status-entry (key value)
4648   (let ((entry (assoc key gnus-article-wash-status-strings)))
4649     (if value (nth 1 entry) (nth 2 entry))))
4650
4651 (defun gnus-article-wash-status ()
4652   "Return a string which display status of article washing."
4653   (save-excursion
4654     (set-buffer gnus-article-buffer)
4655     (let ((cite (memq 'cite gnus-article-wash-types))
4656           (headers (memq 'headers gnus-article-wash-types))
4657           (boring (memq 'boring-headers gnus-article-wash-types))
4658           (pgp (memq 'pgp gnus-article-wash-types))
4659           (pem (memq 'pem gnus-article-wash-types))
4660           (signed (memq 'signed gnus-article-wash-types))
4661           (encrypted (memq 'encrypted gnus-article-wash-types))
4662           (signature (memq 'signature gnus-article-wash-types))
4663           (overstrike (memq 'overstrike gnus-article-wash-types))
4664           (emphasis (memq 'emphasis gnus-article-wash-types)))
4665       (concat
4666        (gnus-article-wash-status-entry 'cite cite)
4667        (gnus-article-wash-status-entry 'headers (or headers boring))
4668        (gnus-article-wash-status-entry 'pgp (or pgp pem signed encrypted))
4669        (gnus-article-wash-status-entry 'signature signature)
4670        (gnus-article-wash-status-entry 'overstrike overstrike)
4671        (gnus-article-wash-status-entry 'emphasis emphasis)))))
4672
4673 (defun gnus-add-wash-type (type)
4674   "Add a washing of TYPE to the current status."
4675   (add-to-list 'gnus-article-wash-types type))
4676
4677 (defun gnus-delete-wash-type (type)
4678   "Add a washing of TYPE to the current status."
4679   (setq gnus-article-wash-types (delq type gnus-article-wash-types)))
4680
4681 (defun gnus-add-image (category image)
4682   "Add IMAGE of CATEGORY to the list of displayed images."
4683   (let ((entry (assq category gnus-article-image-alist)))
4684     (unless entry
4685       (setq entry (list category))
4686       (push entry gnus-article-image-alist))
4687     (nconc entry (list image))))
4688
4689 (defun gnus-delete-images (category)
4690   "Delete all images in CATEGORY."
4691   (let ((entry (assq category gnus-article-image-alist)))
4692     (dolist (image (cdr entry))
4693       (gnus-remove-image image))
4694     (setq gnus-article-image-alist (delq entry gnus-article-image-alist))
4695     (gnus-delete-wash-type category)))
4696
4697 (defalias 'gnus-article-hide-headers-if-wanted 'gnus-article-maybe-hide-headers)
4698
4699 (defun gnus-article-maybe-hide-headers ()
4700   "Hide unwanted headers if `gnus-have-all-headers' is nil.
4701 Provided for backwards compatibility."
4702   (when (and (or (not (gnus-buffer-live-p gnus-summary-buffer))
4703                  (not (save-excursion (set-buffer gnus-summary-buffer)
4704                                       gnus-have-all-headers)))
4705              (not gnus-inhibit-hiding))
4706     (gnus-article-hide-headers)))
4707
4708 ;;; Article savers.
4709
4710 (defun gnus-output-to-file (file-name)
4711   "Append the current article to a file named FILE-NAME."
4712   (let ((artbuf (current-buffer)))
4713     (with-temp-buffer
4714       (insert-buffer-substring artbuf)
4715       ;; Append newline at end of the buffer as separator, and then
4716       ;; save it to file.
4717       (goto-char (point-max))
4718       (insert "\n")
4719       (let ((file-name-coding-system nnmail-pathname-coding-system))
4720         (mm-append-to-file (point-min) (point-max) file-name))
4721       t)))
4722
4723 (defun gnus-narrow-to-page (&optional arg)
4724   "Narrow the article buffer to a page.
4725 If given a numerical ARG, move forward ARG pages."
4726   (interactive "P")
4727   (setq arg (if arg (prefix-numeric-value arg) 0))
4728   (save-excursion
4729     (set-buffer gnus-article-buffer)
4730     (goto-char (point-min))
4731     (widen)
4732     ;; Remove any old next/prev buttons.
4733     (when (gnus-visual-p 'page-marker)
4734       (let ((buffer-read-only nil))
4735         (gnus-remove-text-with-property 'gnus-prev)
4736         (gnus-remove-text-with-property 'gnus-next)))
4737     (when
4738         (cond ((< arg 0)
4739                (re-search-backward page-delimiter nil 'move (1+ (abs arg))))
4740               ((> arg 0)
4741                (re-search-forward page-delimiter nil 'move arg)))
4742       (goto-char (match-end 0)))
4743     (narrow-to-region
4744      (point)
4745      (if (re-search-forward page-delimiter nil 'move)
4746          (match-beginning 0)
4747        (point)))
4748     (when (and (gnus-visual-p 'page-marker)
4749                (not (= (point-min) 1)))
4750       (save-excursion
4751         (goto-char (point-min))
4752         (gnus-insert-prev-page-button)))
4753     (when (and (gnus-visual-p 'page-marker)
4754                (< (+ (point-max) 2) (buffer-size)))
4755       (save-excursion
4756         (goto-char (point-max))
4757         (gnus-insert-next-page-button)))))
4758
4759 ;; Article mode commands
4760
4761 (defun gnus-article-goto-next-page ()
4762   "Show the next page of the article."
4763   (interactive)
4764   (when (gnus-article-next-page)
4765     (goto-char (point-min))
4766     (gnus-article-read-summary-keys nil (gnus-character-to-event ?n))))
4767
4768 (defun gnus-article-goto-prev-page ()
4769   "Show the next page of the article."
4770   (interactive)
4771   (if (bobp) (gnus-article-read-summary-keys nil (gnus-character-to-event ?p))
4772     (gnus-article-prev-page nil)))
4773
4774 (defun gnus-article-next-page (&optional lines)
4775   "Show the next page of the current article.
4776 If end of article, return non-nil.  Otherwise return nil.
4777 Argument LINES specifies lines to be scrolled up."
4778   (interactive "p")
4779   (move-to-window-line -1)
4780   (if (save-excursion
4781         (end-of-line)
4782         (and (pos-visible-in-window-p)  ;Not continuation line.
4783              (>= (1+ (point)) (point-max)))) ;Allow for trailing newline.
4784       ;; Nothing in this page.
4785       (if (or (not gnus-page-broken)
4786               (save-excursion
4787                 (save-restriction
4788                   (widen) (forward-line 1) (eobp)))) ;Real end-of-buffer?
4789           t                             ;Nothing more.
4790         (gnus-narrow-to-page 1)         ;Go to next page.
4791         nil)
4792     ;; More in this page.
4793     (let ((scroll-in-place nil))
4794       (condition-case ()
4795           (scroll-up lines)
4796         (end-of-buffer
4797          ;; Long lines may cause an end-of-buffer error.
4798          (goto-char (point-max)))))
4799     (move-to-window-line 0)
4800     nil))
4801
4802 (defun gnus-article-prev-page (&optional lines)
4803   "Show previous page of current article.
4804 Argument LINES specifies lines to be scrolled down."
4805   (interactive "p")
4806   (move-to-window-line 0)
4807   (if (and gnus-page-broken
4808            (bobp)
4809            (not (save-restriction (widen) (bobp)))) ;Real beginning-of-buffer?
4810       (progn
4811         (gnus-narrow-to-page -1)        ;Go to previous page.
4812         (goto-char (point-max))
4813         (recenter -1))
4814     (let ((scroll-in-place nil))
4815       (prog1
4816           (condition-case ()
4817               (scroll-down lines)
4818             (beginning-of-buffer
4819              (goto-char (point-min))))
4820         (move-to-window-line 0)))))
4821
4822 (defun gnus-article-refer-article ()
4823   "Read article specified by message-id around point."
4824   (interactive)
4825   (let ((point (point)))
4826     (search-forward ">" nil t)          ;Move point to end of "<....>".
4827     (if (re-search-backward "\\(<[^<> \t\n]+>\\)" nil t)
4828         (let ((message-id (match-string 1)))
4829           (goto-char point)
4830           (set-buffer gnus-summary-buffer)
4831           (gnus-summary-refer-article message-id))
4832       (goto-char (point))
4833       (error "No references around point"))))
4834
4835 (defun gnus-article-show-summary ()
4836   "Reconfigure windows to show summary buffer."
4837   (interactive)
4838   (if (not (gnus-buffer-live-p gnus-summary-buffer))
4839       (error "There is no summary buffer for this article buffer")
4840     (gnus-article-set-globals)
4841     (gnus-configure-windows 'article)
4842     (gnus-summary-goto-subject gnus-current-article)
4843     (gnus-summary-position-point)))
4844
4845 (defun gnus-article-describe-briefly ()
4846   "Describe article mode commands briefly."
4847   (interactive)
4848   (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")))
4849
4850 (defun gnus-article-summary-command ()
4851   "Execute the last keystroke in the summary buffer."
4852   (interactive)
4853   (let ((obuf (current-buffer))
4854         (owin (current-window-configuration))
4855         func)
4856     (switch-to-buffer gnus-article-current-summary 'norecord)
4857     (setq func (lookup-key (current-local-map) (this-command-keys)))
4858     (call-interactively func)
4859     (set-buffer obuf)
4860     (set-window-configuration owin)
4861     (set-window-point (get-buffer-window (current-buffer)) (point))))
4862
4863 (defun gnus-article-summary-command-nosave ()
4864   "Execute the last keystroke in the summary buffer."
4865   (interactive)
4866   (let (func)
4867     (pop-to-buffer gnus-article-current-summary 'norecord)
4868     (setq func (lookup-key (current-local-map) (this-command-keys)))
4869     (call-interactively func)))
4870
4871 (defun gnus-article-check-buffer ()
4872   "Beep if not in an article buffer."
4873   (unless (equal major-mode 'gnus-article-mode)
4874     (error "Command invoked outside of a Gnus article buffer")))
4875
4876 (defun gnus-article-read-summary-keys (&optional arg key not-restore-window)
4877   "Read a summary buffer key sequence and execute it from the article buffer."
4878   (interactive "P")
4879   (gnus-article-check-buffer)
4880   (let ((nosaves
4881          '("q" "Q"  "c" "r" "\C-c\C-f" "m"  "a" "f"
4882            "Zc" "ZC" "ZE" "ZQ" "ZZ" "Zn" "ZR" "ZG" "ZN" "ZP"
4883            "=" "^" "\M-^" "|"))
4884         (nosave-but-article
4885          '("A\r"))
4886         (nosave-in-article
4887          '("\C-d"))
4888         (up-to-top
4889          '("n" "Gn" "p" "Gp"))
4890         keys new-sum-point)
4891     (save-excursion
4892       (set-buffer gnus-article-current-summary)
4893       (let (gnus-pick-mode)
4894         (push (or key last-command-event) unread-command-events)
4895         (setq keys (if (featurep 'xemacs)
4896                        (events-to-keys (read-key-sequence nil))
4897                      (read-key-sequence nil)))))
4898
4899     (message "")
4900
4901     (if (or (member keys nosaves)
4902             (member keys nosave-but-article)
4903             (member keys nosave-in-article))
4904         (let (func)
4905           (save-window-excursion
4906             (pop-to-buffer gnus-article-current-summary 'norecord)
4907             ;; We disable the pick minor mode commands.
4908             (let (gnus-pick-mode)
4909               (setq func (lookup-key (current-local-map) keys))))
4910           (if (or (not func)
4911                   (numberp func))
4912               (ding)
4913             (unless (member keys nosave-in-article)
4914               (set-buffer gnus-article-current-summary))
4915             (call-interactively func)
4916             (setq new-sum-point (point)))
4917           (when (member keys nosave-but-article)
4918             (pop-to-buffer gnus-article-buffer 'norecord)))
4919       ;; These commands should restore window configuration.
4920       (let ((obuf (current-buffer))
4921             (owin (current-window-configuration))
4922             (opoint (point))
4923             (summary gnus-article-current-summary)
4924             func in-buffer selected)
4925         (if not-restore-window
4926             (pop-to-buffer summary 'norecord)
4927           (switch-to-buffer summary 'norecord))
4928         (setq in-buffer (current-buffer))
4929         ;; We disable the pick minor mode commands.
4930         (if (and (setq func (let (gnus-pick-mode)
4931                               (lookup-key (current-local-map) keys)))
4932                  (functionp func))
4933             (progn
4934               (call-interactively func)
4935               (setq new-sum-point (point))
4936               (when (eq in-buffer (current-buffer))
4937                 (setq selected (gnus-summary-select-article))
4938                 (set-buffer obuf)
4939                 (unless not-restore-window
4940                   (set-window-configuration owin))
4941                 (when (eq selected 'old)
4942                   (article-goto-body)
4943                   (set-window-start (get-buffer-window (current-buffer))
4944                                     1)
4945                   (set-window-point (get-buffer-window (current-buffer))
4946                                     (point)))
4947                 (let ((win (get-buffer-window gnus-article-current-summary)))
4948                   (when win
4949                     (set-window-point win new-sum-point))))    )
4950           (switch-to-buffer gnus-article-buffer)
4951           (ding))))))
4952
4953 (defun gnus-article-describe-key (key)
4954   "Display documentation of the function invoked by KEY.  KEY is a string."
4955   (interactive "kDescribe key: ")
4956   (gnus-article-check-buffer)
4957   (if (eq (key-binding key) 'gnus-article-read-summary-keys)
4958       (save-excursion
4959         (set-buffer gnus-article-current-summary)
4960         (let (gnus-pick-mode)
4961           (if (featurep 'xemacs)
4962               (progn
4963                 (push (elt key 0) unread-command-events)
4964                 (setq key (events-to-keys
4965                            (read-key-sequence "Describe key: "))))
4966             (setq unread-command-events
4967                   (mapcar
4968                    (lambda (x) (if (>= x 128) (list 'meta (- x 128)) x))
4969                    (string-to-list key)))
4970             (setq key (read-key-sequence "Describe key: "))))
4971         (describe-key key))
4972     (describe-key key)))
4973
4974 (defun gnus-article-describe-key-briefly (key &optional insert)
4975   "Display documentation of the function invoked by KEY.  KEY is a string."
4976   (interactive "kDescribe key: \nP")
4977   (gnus-article-check-buffer)
4978   (if (eq (key-binding key) 'gnus-article-read-summary-keys)
4979       (save-excursion
4980         (set-buffer gnus-article-current-summary)
4981         (let (gnus-pick-mode)
4982           (if (featurep 'xemacs)
4983               (progn
4984                 (push (elt key 0) unread-command-events)
4985                 (setq key (events-to-keys
4986                            (read-key-sequence "Describe key: "))))
4987             (setq unread-command-events
4988                   (mapcar
4989                    (lambda (x) (if (>= x 128) (list 'meta (- x 128)) x))
4990                    (string-to-list key)))
4991             (setq key (read-key-sequence "Describe key: "))))
4992         (describe-key-briefly key insert))
4993     (describe-key-briefly key insert)))
4994
4995 (defun gnus-article-reply-with-original (&optional wide)
4996   "Start composing a reply mail to the current message.
4997 The text in the region will be yanked.  If the region isn't active,
4998 the entire article will be yanked."
4999   (interactive "P")
5000   (let ((article (cdr gnus-article-current))
5001         contents)
5002     (if (not (gnus-mark-active-p))
5003         (with-current-buffer gnus-summary-buffer
5004           (gnus-summary-reply (list (list article)) wide))
5005       (setq contents (buffer-substring (point) (mark t)))
5006       ;; Deactivate active regions.
5007       (when (and (boundp 'transient-mark-mode)
5008                  transient-mark-mode)
5009         (setq mark-active nil))
5010       (with-current-buffer gnus-summary-buffer
5011         (gnus-summary-reply
5012          (list (list article contents)) wide)))))
5013
5014 (defun gnus-article-followup-with-original ()
5015   "Compose a followup to the current article.
5016 The text in the region will be yanked.  If the region isn't active,
5017 the entire article will be yanked."
5018   (interactive)
5019   (let ((article (cdr gnus-article-current))
5020         contents)
5021       (if (not (gnus-mark-active-p))
5022           (with-current-buffer gnus-summary-buffer
5023             (gnus-summary-followup (list (list article))))
5024         (setq contents (buffer-substring (point) (mark t)))
5025         ;; Deactivate active regions.
5026         (when (and (boundp 'transient-mark-mode)
5027                    transient-mark-mode)
5028           (setq mark-active nil))
5029         (with-current-buffer gnus-summary-buffer
5030           (gnus-summary-followup
5031            (list (list article contents)))))))
5032
5033 (defun gnus-article-hide (&optional arg force)
5034   "Hide all the gruft in the current article.
5035 This means that PGP stuff, signatures, cited text and (some)
5036 headers will be hidden.
5037 If given a prefix, show the hidden text instead."
5038   (interactive (append (gnus-article-hidden-arg) (list 'force)))
5039   (gnus-article-hide-headers arg)
5040   (gnus-article-hide-list-identifiers arg)
5041   (gnus-article-hide-pgp arg)
5042   (gnus-article-hide-citation-maybe arg force)
5043   (gnus-article-hide-signature arg))
5044
5045 (defun gnus-article-maybe-highlight ()
5046   "Do some article highlighting if article highlighting is requested."
5047   (when (gnus-visual-p 'article-highlight 'highlight)
5048     (gnus-article-highlight-some)))
5049
5050 (defun gnus-check-group-server ()
5051   ;; Make sure the connection to the server is alive.
5052   (unless (gnus-server-opened
5053            (gnus-find-method-for-group gnus-newsgroup-name))
5054     (gnus-check-server (gnus-find-method-for-group gnus-newsgroup-name))
5055     (gnus-request-group gnus-newsgroup-name t)))
5056
5057 (eval-when-compile
5058   (autoload 'nneething-get-file-name "nneething"))
5059
5060 (defun gnus-request-article-this-buffer (article group)
5061   "Get an article and insert it into this buffer."
5062   (let (do-update-line sparse-header)
5063     (prog1
5064         (save-excursion
5065           (erase-buffer)
5066           (gnus-kill-all-overlays)
5067           (setq group (or group gnus-newsgroup-name))
5068
5069           ;; Using `gnus-request-article' directly will insert the article into
5070           ;; `nntp-server-buffer' - so we'll save some time by not having to
5071           ;; copy it from the server buffer into the article buffer.
5072
5073           ;; We only request an article by message-id when we do not have the
5074           ;; headers for it, so we'll have to get those.
5075           (when (stringp article)
5076             (gnus-read-header article))
5077
5078           ;; If the article number is negative, that means that this article
5079           ;; doesn't belong in this newsgroup (possibly), so we find its
5080           ;; message-id and request it by id instead of number.
5081           (when (and (numberp article)
5082                      gnus-summary-buffer
5083                      (get-buffer gnus-summary-buffer)
5084                      (gnus-buffer-exists-p gnus-summary-buffer))
5085             (save-excursion
5086               (set-buffer gnus-summary-buffer)
5087               (let ((header (gnus-summary-article-header article)))
5088                 (when (< article 0)
5089                   (cond
5090                    ((memq article gnus-newsgroup-sparse)
5091                     ;; This is a sparse gap article.
5092                     (setq do-update-line article)
5093                     (setq article (mail-header-id header))
5094                     (setq sparse-header (gnus-read-header article))
5095                     (setq gnus-newsgroup-sparse
5096                           (delq article gnus-newsgroup-sparse)))
5097                    ((vectorp header)
5098                     ;; It's a real article.
5099                     (setq article (mail-header-id header)))
5100                    (t
5101                     ;; It is an extracted pseudo-article.
5102                     (setq article 'pseudo)
5103                     (gnus-request-pseudo-article header))))
5104
5105                 (let ((method (gnus-find-method-for-group
5106                                gnus-newsgroup-name)))
5107                   (when (and (eq (car method) 'nneething)
5108                              (vectorp header))
5109                     (let ((dir (nneething-get-file-name
5110                                 (mail-header-id header))))
5111                       (when (and (stringp dir)
5112                                  (file-directory-p dir))
5113                         (setq article 'nneething)
5114                         (gnus-group-enter-directory dir))))))))
5115
5116           (cond
5117            ;; Refuse to select canceled articles.
5118            ((and (numberp article)
5119                  gnus-summary-buffer
5120                  (get-buffer gnus-summary-buffer)
5121                  (gnus-buffer-exists-p gnus-summary-buffer)
5122                  (eq (cdr (save-excursion
5123                             (set-buffer gnus-summary-buffer)
5124                             (assq article gnus-newsgroup-reads)))
5125                      gnus-canceled-mark))
5126             nil)
5127            ;; We first check `gnus-original-article-buffer'.
5128            ((and (get-buffer gnus-original-article-buffer)
5129                  (numberp article)
5130                  (save-excursion
5131                    (set-buffer gnus-original-article-buffer)
5132                    (and (equal (car gnus-original-article) group)
5133                         (eq (cdr gnus-original-article) article))))
5134             (insert-buffer-substring gnus-original-article-buffer)
5135             'article)
5136            ;; Check the backlog.
5137            ((and gnus-keep-backlog
5138                  (gnus-backlog-request-article group article (current-buffer)))
5139             'article)
5140            ;; Check asynchronous pre-fetch.
5141            ((gnus-async-request-fetched-article group article (current-buffer))
5142             (gnus-async-prefetch-next group article gnus-summary-buffer)
5143             (when (and (numberp article) gnus-keep-backlog)
5144               (gnus-backlog-enter-article group article (current-buffer)))
5145             'article)
5146            ;; Check the cache.
5147            ((and gnus-use-cache
5148                  (numberp article)
5149                  (gnus-cache-request-article article group))
5150             'article)
5151            ;; Check the agent cache.
5152            ((and gnus-agent gnus-agent-cache gnus-plugged
5153                  (numberp article)
5154                  (gnus-agent-request-article article group))
5155             'article)
5156            ;; Get the article and put into the article buffer.
5157            ((or (stringp article)
5158                 (numberp article))
5159             (let ((gnus-override-method gnus-override-method)
5160                   (methods (and (stringp article)
5161                                 gnus-refer-article-method))
5162                   (backend (car (gnus-find-method-for-group
5163                                  gnus-newsgroup-name)))
5164                   result
5165                   (buffer-read-only nil))
5166               (if (or (not (listp methods))
5167                       (and (symbolp (car methods))
5168                            (assq (car methods) nnoo-definition-alist)))
5169                   (setq methods (list methods)))
5170               (when (and (null gnus-override-method)
5171                          methods)
5172                 (setq gnus-override-method (pop methods)))
5173               (while (not result)
5174                 (when (eq gnus-override-method 'current)
5175                   (setq gnus-override-method
5176                         (with-current-buffer gnus-summary-buffer
5177                           gnus-current-select-method)))
5178                 (erase-buffer)
5179                 (gnus-kill-all-overlays)
5180                 (let ((gnus-newsgroup-name group))
5181                   (gnus-check-group-server))
5182                 (cond
5183                  ((gnus-request-article article group (current-buffer))
5184                   (when (numberp article)
5185                     (gnus-async-prefetch-next group article
5186                                               gnus-summary-buffer)
5187                     (when gnus-keep-backlog
5188                       (gnus-backlog-enter-article
5189                        group article (current-buffer))))
5190                   (setq result 'article))
5191                  (methods
5192                   (setq gnus-override-method (pop methods)))
5193                  ((not (string-match "^400 "
5194                                      (nnheader-get-report backend)))
5195                   ;; If we get 400 server disconnect, reconnect and
5196                   ;; retry; otherwise, assume the article has expired.
5197                   (setq result 'done))))
5198               (and (eq result 'article) 'article)))
5199            ;; It was a pseudo.
5200            (t article)))
5201
5202       ;; Associate this article with the current summary buffer.
5203       (setq gnus-article-current-summary gnus-summary-buffer)
5204
5205       ;; Take the article from the original article buffer
5206       ;; and place it in the buffer it's supposed to be in.
5207       (when (and (get-buffer gnus-article-buffer)
5208                  (equal (buffer-name (current-buffer))
5209                         (buffer-name (get-buffer gnus-article-buffer))))
5210         (save-excursion
5211           (if (get-buffer gnus-original-article-buffer)
5212               (set-buffer gnus-original-article-buffer)
5213             (set-buffer (gnus-get-buffer-create gnus-original-article-buffer))
5214             (buffer-disable-undo)
5215             (setq major-mode 'gnus-original-article-mode)
5216             (setq buffer-read-only t))
5217           (let (buffer-read-only)
5218             (erase-buffer)
5219             (insert-buffer-substring gnus-article-buffer))
5220           (setq gnus-original-article (cons group article)))
5221
5222         ;; Decode charsets.
5223         (run-hooks 'gnus-article-decode-hook)
5224         ;; Mark article as decoded or not.
5225         (setq gnus-article-decoded-p gnus-article-decode-hook))
5226
5227       ;; Update sparse articles.
5228       (when (and do-update-line
5229                  (or (numberp article)
5230                      (stringp article)))
5231         (let ((buf (current-buffer)))
5232           (set-buffer gnus-summary-buffer)
5233           (gnus-summary-update-article do-update-line sparse-header)
5234           (gnus-summary-goto-subject do-update-line nil t)
5235           (set-window-point (gnus-get-buffer-window (current-buffer) t)
5236                             (point))
5237           (set-buffer buf))))))
5238
5239 ;;;
5240 ;;; Article editing
5241 ;;;
5242
5243 (defcustom gnus-article-edit-mode-hook nil
5244   "Hook run in article edit mode buffers."
5245   :group 'gnus-article-various
5246   :type 'hook)
5247
5248 (defvar gnus-article-edit-done-function nil)
5249
5250 (defvar gnus-article-edit-mode-map nil)
5251 (defvar gnus-article-edit-mode nil)
5252
5253 ;; Should we be using derived.el for this?
5254 (unless gnus-article-edit-mode-map
5255   (setq gnus-article-edit-mode-map (make-keymap))
5256   (set-keymap-parent gnus-article-edit-mode-map text-mode-map)
5257
5258   (gnus-define-keys gnus-article-edit-mode-map
5259     "\C-c?"    describe-mode
5260     "\C-c\C-c" gnus-article-edit-done
5261     "\C-c\C-k" gnus-article-edit-exit
5262     "\C-c\C-f\C-t" message-goto-to
5263     "\C-c\C-f\C-o" message-goto-from
5264     "\C-c\C-f\C-b" message-goto-bcc
5265     ;;"\C-c\C-f\C-w" message-goto-fcc
5266     "\C-c\C-f\C-c" message-goto-cc
5267     "\C-c\C-f\C-s" message-goto-subject
5268     "\C-c\C-f\C-r" message-goto-reply-to
5269     "\C-c\C-f\C-n" message-goto-newsgroups
5270     "\C-c\C-f\C-d" message-goto-distribution
5271     "\C-c\C-f\C-f" message-goto-followup-to
5272     "\C-c\C-f\C-m" message-goto-mail-followup-to
5273     "\C-c\C-f\C-k" message-goto-keywords
5274     "\C-c\C-f\C-u" message-goto-summary
5275     "\C-c\C-f\C-i" message-insert-or-toggle-importance
5276     "\C-c\C-f\C-a" message-generate-unsubscribed-mail-followup-to
5277     "\C-c\C-b" message-goto-body
5278     "\C-c\C-i" message-goto-signature
5279
5280     "\C-c\C-t" message-insert-to
5281     "\C-c\C-n" message-insert-newsgroups
5282     "\C-c\C-o" message-sort-headers
5283     "\C-c\C-e" message-elide-region
5284     "\C-c\C-v" message-delete-not-region
5285     "\C-c\C-z" message-kill-to-signature
5286     "\M-\r" message-newline-and-reformat
5287     "\C-c\C-a" mml-attach-file
5288     "\C-a" message-beginning-of-line
5289     "\t" message-tab
5290     "\M-;" comment-region)
5291
5292   (gnus-define-keys (gnus-article-edit-wash-map
5293                      "\C-c\C-w" gnus-article-edit-mode-map)
5294     "f" gnus-article-edit-full-stops))
5295
5296 (easy-menu-define
5297   gnus-article-edit-mode-field-menu gnus-article-edit-mode-map ""
5298   '("Field"
5299     ["Fetch To" message-insert-to t]
5300     ["Fetch Newsgroups" message-insert-newsgroups t]
5301     "----"
5302     ["To" message-goto-to t]
5303     ["From" message-goto-from t]
5304     ["Subject" message-goto-subject t]
5305     ["Cc" message-goto-cc t]
5306     ["Reply-To" message-goto-reply-to t]
5307     ["Summary" message-goto-summary t]
5308     ["Keywords" message-goto-keywords t]
5309     ["Newsgroups" message-goto-newsgroups t]
5310     ["Followup-To" message-goto-followup-to t]
5311     ["Mail-Followup-To" message-goto-mail-followup-to t]
5312     ["Distribution" message-goto-distribution t]
5313     ["Body" message-goto-body t]
5314     ["Signature" message-goto-signature t]))
5315
5316 (define-derived-mode gnus-article-edit-mode text-mode "Article Edit"
5317   "Major mode for editing articles.
5318 This is an extended text-mode.
5319
5320 \\{gnus-article-edit-mode-map}"
5321   (make-local-variable 'gnus-article-edit-done-function)
5322   (make-local-variable 'gnus-prev-winconf)
5323   (set (make-local-variable 'font-lock-defaults)
5324        '(message-font-lock-keywords t))
5325   (set (make-local-variable 'mail-header-separator) "")
5326   (set (make-local-variable 'gnus-article-edit-mode) t)
5327   (easy-menu-add message-mode-field-menu message-mode-map)
5328   (mml-mode)
5329   (setq buffer-read-only nil)
5330   (buffer-enable-undo)
5331   (widen))
5332
5333 (defun gnus-article-edit (&optional force)
5334   "Edit the current article.
5335 This will have permanent effect only in mail groups.
5336 If FORCE is non-nil, allow editing of articles even in read-only
5337 groups."
5338   (interactive "P")
5339   (when (and (not force)
5340              (gnus-group-read-only-p))
5341     (error "The current newsgroup does not support article editing"))
5342   (gnus-article-date-original)
5343   (gnus-article-edit-article
5344    'ignore
5345    `(lambda (no-highlight)
5346       'ignore
5347       (gnus-summary-edit-article-done
5348        ,(or (mail-header-references gnus-current-headers) "")
5349        ,(gnus-group-read-only-p) ,gnus-summary-buffer no-highlight))))
5350
5351 (defun gnus-article-edit-article (start-func exit-func)
5352   "Start editing the contents of the current article buffer."
5353   (let ((winconf (current-window-configuration)))
5354     (set-buffer gnus-article-buffer)
5355     (gnus-article-edit-mode)
5356     (funcall start-func)
5357     (set-buffer-modified-p nil)
5358     (gnus-configure-windows 'edit-article)
5359     (setq gnus-article-edit-done-function exit-func)
5360     (setq gnus-prev-winconf winconf)
5361     (gnus-message 6 "C-c C-c to end edits")))
5362
5363 (defun gnus-article-edit-done (&optional arg)
5364   "Update the article edits and exit."
5365   (interactive "P")
5366   (let ((func gnus-article-edit-done-function)
5367         (buf (current-buffer))
5368         (start (window-start))
5369         (p (point))
5370         (winconf gnus-prev-winconf))
5371     (widen) ;; Widen it in case that users narrowed the buffer.
5372     (funcall func arg)
5373     (set-buffer buf)
5374     ;; The cache and backlog have to be flushed somewhat.
5375     (when gnus-keep-backlog
5376       (gnus-backlog-remove-article
5377        (car gnus-article-current) (cdr gnus-article-current)))
5378     ;; Flush original article as well.
5379     (save-excursion
5380       (when (get-buffer gnus-original-article-buffer)
5381         (set-buffer gnus-original-article-buffer)
5382         (setq gnus-original-article nil)))
5383     (when gnus-use-cache
5384       (gnus-cache-update-article
5385        (car gnus-article-current) (cdr gnus-article-current)))
5386     ;; We remove all text props from the article buffer.
5387     (kill-all-local-variables)
5388     (gnus-set-text-properties (point-min) (point-max) nil)
5389     (gnus-article-mode)
5390     (set-window-configuration winconf)
5391     (set-buffer buf)
5392     (set-window-start (get-buffer-window buf) start)
5393     (set-window-point (get-buffer-window buf) (point))))
5394
5395 (defun gnus-article-edit-exit ()
5396   "Exit the article editing without updating."
5397   (interactive)
5398   (when (or (not (buffer-modified-p))
5399             (yes-or-no-p "Article modified; kill anyway? "))
5400     (let ((curbuf (current-buffer))
5401           (p (point))
5402           (window-start (window-start)))
5403       (erase-buffer)
5404       (if (gnus-buffer-live-p gnus-original-article-buffer)
5405           (insert-buffer gnus-original-article-buffer))
5406       (let ((winconf gnus-prev-winconf))
5407         (kill-all-local-variables)
5408         (gnus-article-mode)
5409         (set-window-configuration winconf)
5410         ;; Tippy-toe some to make sure that point remains where it was.
5411         (save-current-buffer
5412           (set-buffer curbuf)
5413           (set-window-start (get-buffer-window (current-buffer)) window-start)
5414           (goto-char p))))))
5415
5416 (defun gnus-article-edit-full-stops ()
5417   "Interactively repair spacing at end of sentences."
5418   (interactive)
5419   (save-excursion
5420     (goto-char (point-min))
5421     (search-forward-regexp "^$" nil t)
5422     (let ((case-fold-search nil))
5423       (query-replace-regexp "\\([.!?][])}]* \\)\\([[({A-Z]\\)" "\\1 \\2"))))
5424
5425 ;;;
5426 ;;; Article highlights
5427 ;;;
5428
5429 ;; Written by Per Abrahamsen <abraham@iesd.auc.dk>.
5430
5431 ;;; Internal Variables:
5432
5433 (defcustom gnus-button-url-regexp
5434   (if (string-match "[[:digit:]]" "1") ;; support POSIX?
5435       "\\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:]]\\)"
5436     "\\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\\)\\)")
5437   "Regular expression that matches URLs."
5438   :group 'gnus-article-buttons
5439   :type 'regexp)
5440
5441 (defcustom gnus-button-valid-fqdn-regexp
5442   (concat "[a-z0-9][-.a-z0-9]+\\." ;; [hostname.subdomain.]domain.
5443           ;; valid TLDs:
5444           "\\([a-z][a-z]" ;; two letter country TDLs
5445           "\\|biz\\|com\\|edu\\|gov\\|int\\|mil\\|net\\|org"
5446           "\\|aero\\|coop\\|info\\|name\\|museum"
5447           "\\|arpa\\|pro\\|uucp\\|bitnet\\|bofh" ;; old style?
5448           "\\)")
5449   "Regular expression that matches a valid FQDN."
5450   :group 'gnus-article-buttons
5451   :type 'regexp)
5452
5453 (defcustom gnus-button-man-handler 'manual-entry
5454   "Function to use for displaying man pages.
5455 The function must take at least one argument with a string naming the
5456 man page."
5457   :type '(choice (function-item :tag "Man" manual-entry)
5458                  (function-item :tag "Woman" woman)
5459                  (function :tag "Other"))
5460   :group 'gnus-article-buttons)
5461
5462 (defcustom gnus-ctan-url "http://tug.ctan.org/tex-archive/"
5463   "Top directory of a CTAN \(Comprehensive TeX Archive Network\) archive.
5464 If the default site is too slow, try to find a CTAN mirror, see
5465 <URL:http://tug.ctan.org/tex-archive/CTAN.sites?action=/index.html>.  See also
5466 the variable `gnus-button-handle-ctan'."
5467   :group 'gnus-article-buttons
5468   :link '(custom-manual "(gnus)Group Parameters")
5469   :type '(choice (const "http://www.tex.ac.uk/tex-archive/")
5470                  (const "http://tug.ctan.org/tex-archive/")
5471                  (const "http://www.dante.de/CTAN/")
5472                  (string :tag "Other")))
5473
5474 (defcustom gnus-button-ctan-handler 'browse-url
5475   "Function to use for displaying CTAN links.
5476 The function must take one argument, the string naming the URL."
5477   :type '(choice (function-item :tag "Browse Url" browse-url)
5478                  (function :tag "Other"))
5479   :group 'gnus-article-buttons)
5480
5481 (defcustom gnus-button-handle-ctan-bogus-regexp "^/?tex-archive/\\|^/"
5482   "Bogus strings removed from CTAN URLs."
5483   :group 'gnus-article-buttons
5484   :type '(choice (const "^/?tex-archive/\\|/")
5485                  (regexp :tag "Other")))
5486
5487 (defcustom gnus-button-mid-or-mail-regexp
5488   (concat "\\b\\(<?[a-z0-9][^<>\")!;:,{}\n\t ]*@"
5489           gnus-button-valid-fqdn-regexp
5490           ">?\\)\\b")
5491   "Regular expression that matches a message ID or a mail address."
5492   :group 'gnus-article-buttons
5493   :type 'regexp)
5494
5495 (defcustom gnus-button-prefer-mid-or-mail 'guess
5496   "What to do when the button on a string as \"foo123@bar.com\" is pushed.
5497 Strings like this can be either a message ID or a mail address.  If the
5498 variable is set to the symbol `ask', query the user what do do.  If it is the
5499 symbol `guess', Gnus will do a guess and query the user what do do if it is
5500 ambiguous.  See the variable `gnus-button-guessed-mid-regexp' for details
5501 concerning the guessing.  If it is one of the sybols `mid' or `mail', Gnus
5502 will always assume that the string is a message ID or a mail address,
5503 respectivly."
5504   ;; FIXME: doc-string could/should be improved.
5505   :group 'gnus-article-buttons
5506   :type '(choice (const ask)
5507                  (const guess)
5508                  (const mid)
5509                  (const mail)))
5510
5511 (defcustom gnus-button-guessed-mid-regexp
5512   (concat
5513    "^<?\\(slrn\\|Pine\\.\\)"
5514    "\\|\\.fsf@\\|\\.fsf_-_@\\|\\.ln@"
5515    "\\|@4ax\\.com\\|@ID-[0-9]+\\.[a-zA-Z]+\\.dfncis\\.de"
5516           "\\|^<?.*[0-9].*[0-9].*[0-9].*[0-9].*[0-9].*[0-9].*@")
5517   "Regular expression that matches message IDs and not mail addresses."
5518   ;; TODO: Incorporate more matches from
5519   ;; <URL:http://piology.org/perl/id-or-mail.pl.html>. I.e. translate the
5520   ;; Perl-REs to Elisp-REs.
5521   :group 'gnus-article-buttons
5522   :type 'regexp)
5523
5524 (defun gnus-button-handle-mid-or-mail (mid-or-mail)
5525   (let* ((pref gnus-button-prefer-mid-or-mail)
5526          (url-mid (concat "news" ":" mid-or-mail))
5527          (url-mailto (concat "mailto" ":" mid-or-mail)))
5528     (gnus-message 9 "mid-or-mail=%s" mid-or-mail)
5529     ;; If it looks like a MID (well known readers or servers) use 'mid,
5530     ;; otherwise 'ask the user.
5531     (if (eq pref 'guess)
5532         (if (string-match gnus-button-guessed-mid-regexp mid-or-mail)
5533             (setq pref 'mid)
5534           (setq pref 'ask)))
5535     (if (eq pref 'ask)
5536         (save-window-excursion
5537           (if (y-or-n-p (concat "Is <" mid-or-mail "> a mail address? "))
5538               (setq pref 'mail)
5539             (setq pref 'mid))))
5540     (cond ((eq pref 'mid)
5541            (gnus-message 9 "calling `gnus-button-handle-news' %s" url-mid)
5542            (gnus-button-handle-news url-mid))
5543           ((eq pref 'mail)
5544            (gnus-message 9 "calling `gnus-url-mailto'  %s" url-mailto)
5545            (gnus-url-mailto url-mailto)))))
5546
5547 (defun gnus-button-handle-custom (url)
5548   "Follow a Custom URL."
5549   (customize-apropos (gnus-url-unhex-string url)))
5550
5551 (defvar gnus-button-handle-describe-prefix "^\\(C-h\\|<?[Ff]1>?\\)")
5552
5553 (defun gnus-button-handle-describe-function (url)
5554   "Call describe-function when pushing the corresponding URL button."
5555   (describe-function
5556    (intern
5557     (gnus-replace-in-string url gnus-button-handle-describe-prefix ""))))
5558
5559 (defun gnus-button-handle-describe-variable (url)
5560   "Call describe-variable when pushing the corresponding URL button."
5561   (describe-variable
5562    (intern
5563     (gnus-replace-in-string url gnus-button-handle-describe-prefix ""))))
5564
5565 ;; FIXME: Is is possible to implement this?  Else it should be removed here
5566 ;; and in `gnus-button-alist'.
5567 (defun gnus-button-handle-describe-key (url)
5568   "Call describe-key when pushing the corresponding URL button."
5569   (error "not implemented"))
5570
5571 (defun gnus-button-handle-apropos (url)
5572   "Call apropos when pushing the corresponding URL button."
5573   (apropos (gnus-replace-in-string url gnus-button-handle-describe-prefix "")))
5574
5575 (defun gnus-button-handle-apropos-command (url)
5576   "Call apropos when pushing the corresponding URL button."
5577   (apropos-command
5578    (gnus-replace-in-string url gnus-button-handle-describe-prefix "")))
5579
5580 (defun gnus-button-handle-apropos-variable (url)
5581   "Call apropos when pushing the corresponding URL button."
5582   (funcall
5583    (if (fboundp 'apropos-variable) 'apropos-variable 'apropos)
5584    (gnus-replace-in-string url gnus-button-handle-describe-prefix "")))
5585
5586 (defun gnus-button-handle-apropos-documentation (url)
5587   "Call apropos when pushing the corresponding URL button."
5588   (funcall
5589    (if (fboundp 'apropos-documentation) 'apropos-documentation 'apropos)
5590    (gnus-replace-in-string url gnus-button-handle-describe-prefix "")))
5591
5592 (defun gnus-button-handle-ctan (url)
5593   "Call `browse-url' when pushing a CTAN URL button."
5594   (funcall
5595    gnus-button-ctan-handler
5596    (concat
5597     gnus-ctan-url
5598     (gnus-replace-in-string url gnus-button-handle-ctan-bogus-regexp ""))))
5599
5600 (defcustom gnus-button-tex-level 5
5601   "*Integer that says how many TeX-related buttons Gnus will show.
5602 The higher the number, the more buttons will appear and the more false
5603 positives are possible.  Note that you can set this variable local to
5604 specifific groups.  Setting it higher in TeX groups is probably a good idea.
5605 See Info node `(gnus)Group Parameters' and the variable `gnus-parameters' on
5606 how to set variables in specific groups."
5607   :group 'gnus-article-buttons
5608   :link '(custom-manual "(gnus)Group Parameters")
5609   :type 'integer)
5610
5611 (defcustom gnus-button-man-level 5
5612   "*Integer that says how many man-related buttons Gnus will show.
5613 The higher the number, the more buttons will appear and the more false
5614 positives are possible.  Note that you can set this variable local to
5615 specifific groups.  Setting it higher in Unix groups is probably a good idea.
5616 See Info node `(gnus)Group Parameters' and the variable `gnus-parameters' on
5617 how to set variables in specific groups."
5618   :group 'gnus-article-buttons
5619   :link '(custom-manual "(gnus)Group Parameters")
5620   :type 'integer)
5621
5622 (defcustom gnus-button-emacs-level 5
5623   "*Integer that says how many emacs-related buttons Gnus will show.
5624 The higher the number, the more buttons will appear and the more false
5625 positives are possible.  Note that you can set this variable local to
5626 specifific groups.  Setting it higher in Emacs or Gnus related groups is
5627 probably a good idea.  See Info node `(gnus)Group Parameters' and the variable
5628 `gnus-parameters' on how to set variables in specific groups."
5629   :group 'gnus-article-buttons
5630   :link '(custom-manual "(gnus)Group Parameters")
5631   :type 'integer)
5632
5633 (defcustom gnus-button-mail-level 5
5634   "*Integer that says how many buttons for message IDs or mail addresses will appear.
5635 The higher the number, the more buttons will appear and the more false
5636 positives are possible."
5637   :group 'gnus-article-buttons
5638   :type 'integer)
5639
5640 (defcustom gnus-button-alist
5641   '(("<\\(url:[>\n\t ]*?\\)?\\(nntp\\|news\\):[>\n\t ]*\\([^>\n\t ]*@[^>\n\t ]*\\)>"
5642      0 t gnus-button-handle-news 3)
5643     ("\\b\\(nntp\\|news\\):\\([^>\n\t ]*@[^>)!;:,\n\t ]*\\)" 0 t
5644      gnus-button-handle-news 2)
5645     ("\\(\\b<\\(url:[>\n\t ]*\\)?news:[>\n\t ]*\\(//\\)?\\([^>\n\t ]*\\)>\\)"
5646      1 t
5647      gnus-button-fetch-group 4)
5648     ("\\bnews:\\(//\\)?\\([^'\">\n\t ]+\\)" 0 t gnus-button-fetch-group 2)
5649     ("\\bin\\( +article\\| +message\\)? +\\(<\\([^\n @<>]+@[^\n @<>]+\\)>\\)" 2
5650      t gnus-button-message-id 3)
5651     ("\\(<URL: *\\)mailto: *\\([^> \n\t]+\\)>" 0 t gnus-url-mailto 2)
5652     ("mailto:\\([-a-z.@_+0-9%=?]+\\)" 0 t gnus-url-mailto 1)
5653     ("\\bmailto:\\([^ \n\t]+\\)" 0 t gnus-url-mailto 1)
5654     ;; CTAN
5655     ("\\bCTAN:[ \t\n]*\\([^>)!;:,\n\t ]*\\)" 0 (>= gnus-button-tex-level 1)
5656      gnus-button-handle-ctan 1)
5657     ;; This is info
5658     ("\\binfo:\\(//\\)?\\([^'\">\n\t ]+\\)" 0
5659      (>= gnus-button-emacs-level 1) gnus-button-handle-info 2)
5660     ;; This is custom
5661     ("\\bcustom:\\(//\\)?\\([^'\">\n\t ]+\\)" 0
5662      (>= gnus-button-emacs-level 5) gnus-button-handle-custom 2)
5663     ("M-x[ \t\n]customize-[^ ]+[ \t\n]RET[ \t\n]\\([^ ]+\\)[ \t\n]RET" 0
5664      (>= gnus-button-emacs-level 1) gnus-button-handle-custom 1)
5665     ;; Emacs help commands
5666     ("M-x[ \t\n]+apropos[ \t\n]+RET[ \t\n]+\\([^ \t\n]+\\)[ \t\n]+RET"
5667      ;; regexp doesn't match arguments containing ` '.
5668      0 (>= gnus-button-emacs-level 1) gnus-button-handle-apropos 1)
5669     ("M-x[ \t\n]+apropos-command[ \t\n]+RET[ \t\n]+\\([^ \t\n]+\\)[ \t\n]+RET"
5670      0 (>= gnus-button-emacs-level 1) gnus-button-handle-apropos-command 1)
5671     ("M-x[ \t\n]+apropos-variable[ \t\n]+RET[ \t\n]+\\([^ \t\n]+\\)[ \t\n]+RET"
5672      0 (>= gnus-button-emacs-level 1) gnus-button-handle-apropos-variable 1)
5673     ("M-x[ \t\n]+apropos-documentation[ \t\n]+RET[ \t\n]+\\([^ \t\n]+\\)[ \t\n]+RET"
5674      0 (>= gnus-button-emacs-level 1) gnus-button-handle-apropos-documentation 1)
5675     ("\\b\\(C-h\\|<?[Ff]1>?\\)[ \t\n]+f[ \t\n]+\\([^ \t\n]+\\)[ \t\n]+RET"
5676      0 (>= gnus-button-emacs-level 1) gnus-button-handle-describe-function 2)
5677     ("\\b\\(C-h\\|<?[Ff]1>?\\)[ \t\n]+v[ \t\n]+\\([^ \t\n]+\\)[ \t\n]+RET"
5678      0 (>= gnus-button-emacs-level 1) gnus-button-handle-describe-variable 2)
5679     ("\\b\\(C-h\\|<?[Ff]1>?\\)[ \t\n]+k[ \t\n]+\\([^ \t\n]+\\)[ \t\n]+" 0
5680      ;; this regexp needs to be fixed!
5681      (>= gnus-button-emacs-level 9) gnus-button-handle-describe-key 2)
5682     ;; This is how URLs _should_ be embedded in text...
5683     ("<URL: *\\([^<>]*\\)>" 1 t gnus-button-embedded-url 1)
5684     ;; Raw URLs.
5685     (gnus-button-url-regexp 0 t browse-url 0)
5686     ;; man pages
5687     ("\\b\\([a-z][a-z]+\\)([1-9])\\W" 0
5688      (and (>= gnus-button-man-level 1) (< gnus-button-man-level 3))
5689      gnus-button-handle-man 1)
5690     ;; more man pages: resolv.conf(5), iso_8859-1(7), xterm(1x)
5691     ("\\b\\([a-z][-_.a-z0-9]+\\)([1-9])\\W" 0
5692      (and (>= gnus-button-man-level 3) (< gnus-button-man-level 5))
5693      gnus-button-handle-man 1)
5694     ;; even more: Apache::PerlRun(3pm), PDL::IO::FastRaw(3pm),
5695     ;; SoWWWAnchor(3iv), XSelectInput(3X11)
5696     ("\\b\\([a-z][-_.:a-z0-9]+\\)([1-9][X1a-z]*)\\W" 0
5697      (>= gnus-button-man-level 5) gnus-button-handle-man 1)
5698     ;; MID or mail: To avoid too many false positives we don't try to catch
5699     ;; all kind of allowed MIDs or mail addresses.  Domain part must contain
5700     ;; at least one dot.  TLD must contain two or three chars or be a know TLD
5701     ;; (info|name|...).  Put this entry near the _end_ of `gnus-button-alist'
5702     ;; so that non-ambiguous entries (see above) match first.
5703     (gnus-button-mid-or-mail-regexp
5704      0 (>= gnus-button-mail-level 5) gnus-button-handle-mid-or-mail 1))
5705   "*Alist of regexps matching buttons in article bodies.
5706
5707 Each entry has the form (REGEXP BUTTON FORM CALLBACK PAR...), where
5708 REGEXP: is the string (case insensitive) matching text around the button (can
5709 also be lisp expression evaluating to a string),
5710 BUTTON: is the number of the regexp grouping actually matching the button,
5711 FORM: is a lisp expression which must eval to true for the button to
5712 be added,
5713 CALLBACK: is the function to call when the user push this button, and each
5714 PAR: is a number of a regexp grouping whose text will be passed to CALLBACK.
5715
5716 CALLBACK can also be a variable, in that case the value of that
5717 variable it the real callback function."
5718   :group 'gnus-article-buttons
5719   :type '(repeat (list (choice regexp variable)
5720                        (integer :tag "Button")
5721                        (sexp :tag "Form")
5722                        (function :tag "Callback")
5723                        (repeat :tag "Par"
5724                                :inline t
5725                                (integer :tag "Regexp group")))))
5726
5727 (defcustom gnus-header-button-alist
5728   '(("^\\(References\\|Message-I[Dd]\\):" "<[^<>]+>"
5729      0 t gnus-button-message-id 0)
5730     ("^\\(From\\|Reply-To\\):" ": *\\(.+\\)$" 1 t gnus-button-reply 1)
5731     ("^\\(Cc\\|To\\):" "[^ \t\n<>,()\"]+@[^ \t\n<>,()\"]+"
5732      0 t gnus-button-mailto 0)
5733     ("^X-[Uu][Rr][Ll]:" gnus-button-url-regexp 0 t browse-url 0)
5734     ("^Subject:" gnus-button-url-regexp 0 t browse-url 0)
5735     ("^[^:]+:" gnus-button-url-regexp 0 t browse-url 0)
5736     ("^[^:]+:" "\\bmailto:\\([-a-z.@_+0-9%=?]+\\)" 0 t gnus-url-mailto 1)
5737     ("^[^:]+:" "\\(<\\(url: \\)?news:\\([^>\n ]*\\)>\\)" 1 t
5738      gnus-button-message-id 3))
5739   "*Alist of headers and regexps to match buttons in article heads.
5740
5741 This alist is very similar to `gnus-button-alist', except that each
5742 alist has an additional HEADER element first in each entry:
5743
5744 \(HEADER REGEXP BUTTON FORM CALLBACK PAR)
5745
5746 HEADER is a regexp to match a header.  For a fuller explanation, see
5747 `gnus-button-alist'."
5748   :group 'gnus-article-buttons
5749   :group 'gnus-article-headers
5750   :type '(repeat (list (regexp :tag "Header")
5751                        regexp
5752                        (integer :tag "Button")
5753                        (sexp :tag "Form")
5754                        (function :tag "Callback")
5755                        (repeat :tag "Par"
5756                                :inline t
5757                                (integer :tag "Regexp group")))))
5758
5759 (defvar gnus-button-regexp nil)
5760 (defvar gnus-button-marker-list nil)
5761 ;; Regexp matching any of the regexps from `gnus-button-alist'.
5762
5763 (defvar gnus-button-last nil)
5764 ;; The value of `gnus-button-alist' when `gnus-button-regexp' was build.
5765
5766 ;;; Commands:
5767
5768 (defun gnus-article-push-button (event)
5769   "Check text under the mouse pointer for a callback function.
5770 If the text under the mouse pointer has a `gnus-callback' property,
5771 call it with the value of the `gnus-data' text property."
5772   (interactive "e")
5773   (set-buffer (window-buffer (posn-window (event-start event))))
5774   (let* ((pos (posn-point (event-start event)))
5775          (data (get-text-property pos 'gnus-data))
5776          (fun (get-text-property pos 'gnus-callback)))
5777     (goto-char pos)
5778     (when fun
5779       (funcall fun data))))
5780
5781 (defun gnus-article-press-button ()
5782   "Check text at point for a callback function.
5783 If the text at point has a `gnus-callback' property,
5784 call it with the value of the `gnus-data' text property."
5785   (interactive)
5786   (let* ((data (get-text-property (point) 'gnus-data))
5787          (fun (get-text-property (point) 'gnus-callback)))
5788     (when fun
5789       (funcall fun data))))
5790
5791 (defun gnus-article-highlight (&optional force)
5792   "Highlight current article.
5793 This function calls `gnus-article-highlight-headers',
5794 `gnus-article-highlight-citation',
5795 `gnus-article-highlight-signature', and `gnus-article-add-buttons' to
5796 do the highlighting.  See the documentation for those functions."
5797   (interactive (list 'force))
5798   (gnus-article-highlight-headers)
5799   (gnus-article-highlight-citation force)
5800   (gnus-article-highlight-signature)
5801   (gnus-article-add-buttons force)
5802   (gnus-article-add-buttons-to-head))
5803
5804 (defun gnus-article-highlight-some (&optional force)
5805   "Highlight current article.
5806 This function calls `gnus-article-highlight-headers',
5807 `gnus-article-highlight-signature', and `gnus-article-add-buttons' to
5808 do the highlighting.  See the documentation for those functions."
5809   (interactive (list 'force))
5810   (gnus-article-highlight-headers)
5811   (gnus-article-highlight-signature)
5812   (gnus-article-add-buttons))
5813
5814 (defun gnus-article-highlight-headers ()
5815   "Highlight article headers as specified by `gnus-header-face-alist'."
5816   (interactive)
5817   (save-excursion
5818     (set-buffer gnus-article-buffer)
5819     (save-restriction
5820       (let ((alist gnus-header-face-alist)
5821             (buffer-read-only nil)
5822             (case-fold-search t)
5823             (inhibit-point-motion-hooks t)
5824             entry regexp header-face field-face from hpoints fpoints)
5825         (article-narrow-to-head)
5826         (while (setq entry (pop alist))
5827           (goto-char (point-min))
5828           (setq regexp (concat "^\\("
5829                                (if (string-equal "" (nth 0 entry))
5830                                    "[^\t ]"
5831                                  (nth 0 entry))
5832                                "\\)")
5833                 header-face (nth 1 entry)
5834                 field-face (nth 2 entry))
5835           (while (and (re-search-forward regexp nil t)
5836                       (not (eobp)))
5837             (beginning-of-line)
5838             (setq from (point))
5839             (unless (search-forward ":" nil t)
5840               (forward-char 1))
5841             (when (and header-face
5842                        (not (memq (point) hpoints)))
5843               (push (point) hpoints)
5844               (gnus-put-text-property from (point) 'face header-face))
5845             (when (and field-face
5846                        (not (memq (setq from (point)) fpoints)))
5847               (push from fpoints)
5848               (if (re-search-forward "^[^ \t]" nil t)
5849                   (forward-char -2)
5850                 (goto-char (point-max)))
5851               (gnus-put-text-property from (point) 'face field-face))))))))
5852
5853 (defun gnus-article-highlight-signature ()
5854   "Highlight the signature in an article.
5855 It does this by highlighting everything after
5856 `gnus-signature-separator' using `gnus-signature-face'."
5857   (interactive)
5858   (save-excursion
5859     (set-buffer gnus-article-buffer)
5860     (let ((buffer-read-only nil)
5861           (inhibit-point-motion-hooks t))
5862       (save-restriction
5863         (when (and gnus-signature-face
5864                    (gnus-article-narrow-to-signature))
5865           (gnus-overlay-put (gnus-make-overlay (point-min) (point-max))
5866                             'face gnus-signature-face)
5867           (widen)
5868           (gnus-article-search-signature)
5869           (let ((start (match-beginning 0))
5870                 (end (set-marker (make-marker) (1+ (match-end 0)))))
5871             (gnus-article-add-button start (1- end) 'gnus-signature-toggle
5872                                      end)))))))
5873
5874 (defun gnus-button-in-region-p (b e prop)
5875   "Say whether PROP exists in the region."
5876   (text-property-not-all b e prop nil))
5877
5878 (defun gnus-article-add-buttons (&optional force)
5879   "Find external references in the article and make buttons of them.
5880 \"External references\" are things like Message-IDs and URLs, as
5881 specified by `gnus-button-alist'."
5882   (interactive (list 'force))
5883   (save-excursion
5884     (set-buffer gnus-article-buffer)
5885     (let ((buffer-read-only nil)
5886           (inhibit-point-motion-hooks t)
5887           (case-fold-search t)
5888           (alist gnus-button-alist)
5889           beg entry regexp)
5890       ;; Remove all old markers.
5891       (let (marker entry new-list)
5892         (while (setq marker (pop gnus-button-marker-list))
5893           (if (or (< marker (point-min)) (>= marker (point-max)))
5894               (push marker new-list)
5895             (goto-char marker)
5896             (when (setq entry (gnus-button-entry))
5897               (put-text-property (match-beginning (nth 1 entry))
5898                                  (match-end (nth 1 entry))
5899                                  'gnus-callback nil))
5900             (set-marker marker nil)))
5901         (setq gnus-button-marker-list new-list))
5902       ;; We skip the headers.
5903       (article-goto-body)
5904       (setq beg (point))
5905       (while (setq entry (pop alist))
5906         (setq regexp (eval (car entry)))
5907         (goto-char beg)
5908         (while (re-search-forward regexp nil t)
5909           (let* ((start (and entry (match-beginning (nth 1 entry))))
5910                  (end (and entry (match-end (nth 1 entry))))
5911                  (from (match-beginning 0)))
5912             (when (and (or (eq t (nth 2 entry))
5913                            (eval (nth 2 entry)))
5914                        (not (gnus-button-in-region-p
5915                              start end 'gnus-callback)))
5916               ;; That optional form returned non-nil, so we add the
5917               ;; button.
5918               (gnus-article-add-button
5919                start end 'gnus-button-push
5920                (car (push (set-marker (make-marker) from)
5921                           gnus-button-marker-list))))))))))
5922
5923 ;; Add buttons to the head of an article.
5924 (defun gnus-article-add-buttons-to-head ()
5925   "Add buttons to the head of the article."
5926   (interactive)
5927   (save-excursion
5928     (set-buffer gnus-article-buffer)
5929     (save-restriction
5930       (let ((buffer-read-only nil)
5931             (inhibit-point-motion-hooks t)
5932             (case-fold-search t)
5933             (alist gnus-header-button-alist)
5934             entry beg end)
5935         (article-narrow-to-head)
5936         (while alist
5937           ;; Each alist entry.
5938           (setq entry (car alist)
5939                 alist (cdr alist))
5940           (goto-char (point-min))
5941           (while (re-search-forward (car entry) nil t)
5942             ;; Each header matching the entry.
5943             (setq beg (match-beginning 0))
5944             (setq end (or (and (re-search-forward "^[^ \t]" nil t)
5945                                (match-beginning 0))
5946                           (point-max)))
5947             (goto-char beg)
5948             (while (re-search-forward (eval (nth 1 entry)) end t)
5949               ;; Each match within a header.
5950               (let* ((entry (cdr entry))
5951                      (start (match-beginning (nth 1 entry)))
5952                      (end (match-end (nth 1 entry)))
5953                      (form (nth 2 entry)))
5954                 (goto-char (match-end 0))
5955                 (when (eval form)
5956                   (gnus-article-add-button
5957                    start end (nth 3 entry)
5958                    (buffer-substring (match-beginning (nth 4 entry))
5959                                      (match-end (nth 4 entry)))))))
5960             (goto-char end)))))))
5961
5962 ;;; External functions:
5963
5964 (defun gnus-article-add-button (from to fun &optional data)
5965   "Create a button between FROM and TO with callback FUN and data DATA."
5966   (when gnus-article-button-face
5967     (gnus-overlay-put (gnus-make-overlay from to)
5968                       'face gnus-article-button-face))
5969   (gnus-add-text-properties
5970    from to
5971    (nconc (and gnus-article-mouse-face
5972                (list gnus-mouse-face-prop gnus-article-mouse-face))
5973           (list 'gnus-callback fun)
5974           (and data (list 'gnus-data data))))
5975   (widget-convert-button 'link from to :action 'gnus-widget-press-button
5976                          :button-keymap gnus-widget-button-keymap))
5977
5978 ;;; Internal functions:
5979
5980 (defun gnus-article-set-globals ()
5981   (save-excursion
5982     (set-buffer gnus-summary-buffer)
5983     (gnus-set-global-variables)))
5984
5985 (defun gnus-signature-toggle (end)
5986   (save-excursion
5987     (set-buffer gnus-article-buffer)
5988     (let ((buffer-read-only nil)
5989           (inhibit-point-motion-hooks t))
5990       (if (text-property-any end (point-max) 'article-type 'signature)
5991           (progn
5992             (gnus-delete-wash-type 'signature)
5993             (gnus-remove-text-properties-when
5994              'article-type 'signature end (point-max)
5995              (cons 'article-type (cons 'signature
5996                                        gnus-hidden-properties))))
5997         (gnus-add-wash-type 'signature)
5998         (gnus-add-text-properties-when
5999          'article-type nil end (point-max)
6000          (cons 'article-type (cons 'signature
6001                                    gnus-hidden-properties)))))
6002     (let ((gnus-article-mime-handle-alist-1 gnus-article-mime-handle-alist))
6003       (gnus-set-mode-line 'article))))
6004
6005 (defun gnus-button-entry ()
6006   ;; Return the first entry in `gnus-button-alist' matching this place.
6007   (let ((alist gnus-button-alist)
6008         (entry nil))
6009     (while alist
6010       (setq entry (pop alist))
6011       (if (looking-at (eval (car entry)))
6012           (setq alist nil)
6013         (setq entry nil)))
6014     entry))
6015
6016 (defun gnus-button-push (marker)
6017   ;; Push button starting at MARKER.
6018   (save-excursion
6019     (goto-char marker)
6020     (let* ((entry (gnus-button-entry))
6021            (inhibit-point-motion-hooks t)
6022            (fun (nth 3 entry))
6023            (args (mapcar (lambda (group)
6024                            (let ((string (match-string group)))
6025                              (gnus-set-text-properties
6026                               0 (length string) nil string)
6027                              string))
6028                          (nthcdr 4 entry))))
6029       (cond
6030        ((fboundp fun)
6031         (apply fun args))
6032        ((and (boundp fun)
6033              (fboundp (symbol-value fun)))
6034         (apply (symbol-value fun) args))
6035        (t
6036         (gnus-message 1 "You must define `%S' to use this button"
6037                       (cons fun args)))))))
6038
6039 (defun gnus-parse-news-url (url)
6040   (let (scheme server group message-id articles)
6041     (with-temp-buffer
6042       (insert url)
6043       (goto-char (point-min))
6044       (when (looking-at "\\([A-Za-z]+\\):")
6045         (setq scheme (match-string 1))
6046         (goto-char (match-end 0)))
6047       (when (looking-at "//\\([^/]+\\)/")
6048         (setq server (match-string 1))
6049         (goto-char (match-end 0)))
6050
6051       (cond
6052        ((looking-at "\\(.*@.*\\)")
6053         (setq message-id (match-string 1)))
6054        ((looking-at "\\([^/]+\\)/\\([-0-9]+\\)")
6055         (setq group (match-string 1)
6056               articles (split-string (match-string 2) "-")))
6057        ((looking-at "\\([^/]+\\)/?")
6058         (setq group (match-string 1)))
6059        (t
6060         (error "Unknown news URL syntax"))))
6061     (list scheme server group message-id articles)))
6062
6063 (defun gnus-button-handle-news (url)
6064   "Fetch a news URL."
6065   (destructuring-bind (scheme server group message-id articles)
6066       (gnus-parse-news-url url)
6067     (cond
6068      (message-id
6069       (save-excursion
6070         (set-buffer gnus-summary-buffer)
6071         (if server
6072             (let ((gnus-refer-article-method (list (list 'nntp server))))
6073               (gnus-summary-refer-article message-id))
6074           (gnus-summary-refer-article message-id))))
6075      (group
6076       (gnus-button-fetch-group url)))))
6077
6078 (defun gnus-button-handle-man (url)
6079   "Fetch a man page."
6080   (funcall gnus-button-man-handler url))
6081
6082 (defun gnus-button-handle-info (url)
6083   "Fetch an info URL."
6084   (if (string-match
6085        "^\\([^:/]+\\)?/\\(.*\\)"
6086        url)
6087       (gnus-info-find-node
6088        (concat "(" (or (gnus-url-unhex-string (match-string 1 url))
6089                        "Gnus")
6090                ")"
6091                (gnus-url-unhex-string (match-string 2 url))))
6092     (error "Can't parse %s" url)))
6093
6094 (defun gnus-button-message-id (message-id)
6095   "Fetch MESSAGE-ID."
6096   (save-excursion
6097     (set-buffer gnus-summary-buffer)
6098     (gnus-summary-refer-article message-id)))
6099
6100 (defun gnus-button-fetch-group (address)
6101   "Fetch GROUP specified by ADDRESS."
6102   (if (not (string-match "[:/]" address))
6103       ;; This is just a simple group url.
6104       (gnus-group-read-ephemeral-group address gnus-select-method)
6105     (if (not
6106          (string-match
6107           "^\\([^:/]+\\)\\(:\\([^/]+\\)\\)?/\\([^/]+\\)\\(/\\([0-9]+\\)\\)?"
6108           address))
6109         (error "Can't parse %s" address)
6110       (gnus-group-read-ephemeral-group
6111        (match-string 4 address)
6112        `(nntp ,(match-string 1 address)
6113               (nntp-address ,(match-string 1 address))
6114               (nntp-port-number ,(if (match-end 3)
6115                                      (match-string 3 address)
6116                                    "nntp")))
6117        nil nil nil
6118        (and (match-end 6) (list (string-to-int (match-string 6 address))))))))
6119
6120 (defun gnus-url-parse-query-string (query &optional downcase)
6121   (let (retval pairs cur key val)
6122     (setq pairs (split-string query "&"))
6123     (while pairs
6124       (setq cur (car pairs)
6125             pairs (cdr pairs))
6126       (if (not (string-match "=" cur))
6127           nil                           ; Grace
6128         (setq key (gnus-url-unhex-string (substring cur 0 (match-beginning 0)))
6129               val (gnus-url-unhex-string (substring cur (match-end 0) nil) t))
6130         (if downcase
6131             (setq key (downcase key)))
6132         (setq cur (assoc key retval))
6133         (if cur
6134             (setcdr cur (cons val (cdr cur)))
6135           (setq retval (cons (list key val) retval)))))
6136     retval))
6137
6138 (defun gnus-url-mailto (url)
6139   ;; Send mail to someone
6140   (when (string-match "mailto:/*\\(.*\\)" url)
6141     (setq url (substring url (match-beginning 1) nil)))
6142   (let (to args subject func)
6143     (if (string-match (regexp-quote "?") url)
6144         (setq to (gnus-url-unhex-string (substring url 0 (match-beginning 0)))
6145               args (gnus-url-parse-query-string
6146                     (substring url (match-end 0) nil) t))
6147       (setq to (gnus-url-unhex-string url)))
6148     (setq args (cons (list "to" to) args)
6149           subject (cdr-safe (assoc "subject" args)))
6150     (gnus-msg-mail)
6151     (while args
6152       (setq func (intern-soft (concat "message-goto-" (downcase (caar args)))))
6153       (if (fboundp func)
6154           (funcall func)
6155         (message-position-on-field (caar args)))
6156       (insert (mapconcat 'identity (cdar args) ", "))
6157       (setq args (cdr args)))
6158     (if subject
6159         (message-goto-body)
6160       (message-goto-subject))))
6161
6162 (defun gnus-button-embedded-url (address)
6163   "Activate ADDRESS with `browse-url'."
6164   (browse-url (gnus-strip-whitespace address)))
6165
6166 ;;; Next/prev buttons in the article buffer.
6167
6168 (defvar gnus-next-page-line-format "%{%(Next page...%)%}\n")
6169 (defvar gnus-prev-page-line-format "%{%(Previous page...%)%}\n")
6170
6171 (defvar gnus-prev-page-map
6172   (let ((map (make-sparse-keymap)))
6173     (unless (>= emacs-major-version 21)
6174       ;; XEmacs doesn't care.
6175       (set-keymap-parent map gnus-article-mode-map))
6176     (define-key map gnus-mouse-2 'gnus-button-prev-page)
6177     (define-key map "\r" 'gnus-button-prev-page)
6178     map))
6179
6180 (defun gnus-insert-prev-page-button ()
6181   (let ((b (point))
6182         (buffer-read-only nil))
6183     (gnus-eval-format
6184      gnus-prev-page-line-format nil
6185      `(,@(gnus-local-map-property gnus-prev-page-map)
6186          gnus-prev t
6187          gnus-callback gnus-article-button-prev-page
6188          article-type annotation))
6189     (widget-convert-button
6190      'link b (point)
6191      :action 'gnus-button-prev-page
6192      :button-keymap gnus-prev-page-map)))
6193
6194 (defvar gnus-prev-page-map
6195   (let ((map (make-sparse-keymap)))
6196     (unless (>= emacs-major-version 21)
6197       ;; XEmacs doesn't care.
6198       (set-keymap-parent map gnus-article-mode-map))
6199     (define-key map gnus-mouse-2 'gnus-button-prev-page)
6200     (define-key map "\r" 'gnus-button-prev-page)
6201     map))
6202
6203 (defvar gnus-next-page-map
6204   (let ((map (make-sparse-keymap)))
6205     (unless (>= emacs-major-version 21)
6206       ;; XEmacs doesn't care.
6207       (set-keymap-parent map gnus-article-mode-map))
6208     (define-key map gnus-mouse-2 'gnus-button-next-page)
6209     (define-key map "\r" 'gnus-button-next-page)
6210     map))
6211
6212 (defun gnus-button-next-page (&optional args more-args)
6213   "Go to the next page."
6214   (interactive)
6215   (let ((win (selected-window)))
6216     (select-window (gnus-get-buffer-window gnus-article-buffer t))
6217     (gnus-article-next-page)
6218     (select-window win)))
6219
6220 (defun gnus-button-prev-page (&optional args more-args)
6221   "Go to the prev page."
6222   (interactive)
6223   (let ((win (selected-window)))
6224     (select-window (gnus-get-buffer-window gnus-article-buffer t))
6225     (gnus-article-prev-page)
6226     (select-window win)))
6227
6228 (defun gnus-insert-next-page-button ()
6229   (let ((b (point))
6230         (buffer-read-only nil))
6231     (gnus-eval-format gnus-next-page-line-format nil
6232                       `(,@(gnus-local-map-property gnus-next-page-map)
6233                           gnus-next t
6234                           gnus-callback gnus-article-button-next-page
6235                           article-type annotation))
6236     (widget-convert-button
6237      'link b (point)
6238      :action 'gnus-button-next-page
6239      :button-keymap gnus-next-page-map)))
6240
6241 (defun gnus-article-button-next-page (arg)
6242   "Go to the next page."
6243   (interactive "P")
6244   (let ((win (selected-window)))
6245     (select-window (gnus-get-buffer-window gnus-article-buffer t))
6246     (gnus-article-next-page)
6247     (select-window win)))
6248
6249 (defun gnus-article-button-prev-page (arg)
6250   "Go to the prev page."
6251   (interactive "P")
6252   (let ((win (selected-window)))
6253     (select-window (gnus-get-buffer-window gnus-article-buffer t))
6254     (gnus-article-prev-page)
6255     (select-window win)))
6256
6257 (defvar gnus-decode-header-methods
6258   '(mail-decode-encoded-word-region)
6259   "List of methods used to decode headers.
6260
6261 This variable is a list of FUNCTION or (REGEXP . FUNCTION).  If item
6262 is FUNCTION, FUNCTION will be apply to all newsgroups.  If item is a
6263 \(REGEXP . FUNCTION), FUNCTION will be only apply to thes newsgroups
6264 whose names match REGEXP.
6265
6266 For example:
6267 \((\"chinese\" . gnus-decode-encoded-word-region-by-guess)
6268  mail-decode-encoded-word-region
6269  (\"chinese\" . rfc1843-decode-region))
6270 ")
6271
6272 (defvar gnus-decode-header-methods-cache nil)
6273
6274 (defun gnus-multi-decode-header (start end)
6275   "Apply the functions from `gnus-encoded-word-methods' that match."
6276   (unless (and gnus-decode-header-methods-cache
6277                (eq gnus-newsgroup-name
6278                    (car gnus-decode-header-methods-cache)))
6279     (setq gnus-decode-header-methods-cache (list gnus-newsgroup-name))
6280     (mapcar (lambda (x)
6281               (if (symbolp x)
6282                   (nconc gnus-decode-header-methods-cache (list x))
6283                 (if (and gnus-newsgroup-name
6284                          (string-match (car x) gnus-newsgroup-name))
6285                     (nconc gnus-decode-header-methods-cache
6286                            (list (cdr x))))))
6287           gnus-decode-header-methods))
6288   (let ((xlist gnus-decode-header-methods-cache))
6289     (pop xlist)
6290     (save-restriction
6291       (narrow-to-region start end)
6292       (while xlist
6293         (funcall (pop xlist) (point-min) (point-max))))))
6294
6295 ;;;
6296 ;;; Treatment top-level handling.
6297 ;;;
6298
6299 (defun gnus-treat-article (condition &optional part-number total-parts type)
6300   (let ((length (- (point-max) (point-min)))
6301         (alist gnus-treatment-function-alist)
6302         (article-goto-body-goes-to-point-min-p t)
6303         (treated-type
6304          (or (not type)
6305              (catch 'found
6306                (let ((list gnus-article-treat-types))
6307                  (while list
6308                    (when (string-match (pop list) type)
6309                      (throw 'found t)))))))
6310         (highlightp (gnus-visual-p 'article-highlight 'highlight))
6311         val elem)
6312     (gnus-run-hooks 'gnus-part-display-hook)
6313     (dolist (elem alist)
6314       (setq val
6315             (save-excursion
6316               (when (gnus-buffer-live-p gnus-summary-buffer)
6317                 (set-buffer gnus-summary-buffer))
6318               (symbol-value (car elem))))
6319       (when (and (or (consp val)
6320                      treated-type)
6321                  (gnus-treat-predicate val)
6322                  (or (not (get (car elem) 'highlight))
6323                      highlightp))
6324         (save-restriction
6325           (funcall (cadr elem)))))))
6326
6327 ;; Dynamic variables.
6328 (eval-when-compile
6329   (defvar part-number)
6330   (defvar total-parts)
6331   (defvar type)
6332   (defvar condition)
6333   (defvar length))
6334
6335 (defun gnus-treat-predicate (val)
6336   (cond
6337    ((null val)
6338     nil)
6339    (condition
6340     (eq condition val))
6341    ((and (listp val)
6342          (stringp (car val)))
6343     (apply 'gnus-or (mapcar `(lambda (s)
6344                                (string-match s ,(or gnus-newsgroup-name "")))
6345                             val)))
6346    ((listp val)
6347     (let ((pred (pop val)))
6348       (cond
6349        ((eq pred 'or)
6350         (apply 'gnus-or (mapcar 'gnus-treat-predicate val)))
6351        ((eq pred 'and)
6352         (apply 'gnus-and (mapcar 'gnus-treat-predicate val)))
6353        ((eq pred 'not)
6354         (not (gnus-treat-predicate (car val))))
6355        ((eq pred 'typep)
6356         (equal (car val) type))
6357        (t
6358         (error "%S is not a valid predicate" pred)))))
6359    ((eq val t)
6360     t)
6361    ((eq val 'head)
6362     nil)
6363    ((eq val 'last)
6364     (eq part-number total-parts))
6365    ((numberp val)
6366     (< length val))
6367    (t
6368     (error "%S is not a valid value" val))))
6369
6370 (defun gnus-article-encrypt-body (protocol &optional n)
6371   "Encrypt the article body."
6372   (interactive
6373    (list
6374     (or gnus-article-encrypt-protocol
6375         (completing-read "Encrypt protocol: "
6376                          gnus-article-encrypt-protocol-alist
6377                          nil t))
6378     current-prefix-arg))
6379   (let ((func (cdr (assoc protocol gnus-article-encrypt-protocol-alist))))
6380     (unless func
6381       (error (format "Can't find the encrypt protocol %s" protocol)))
6382     (if (member gnus-newsgroup-name '("nndraft:delayed"
6383                                       "nndraft:drafts"
6384                                       "nndraft:queue"))
6385         (error "Can't encrypt the article in group %s"
6386                gnus-newsgroup-name))
6387     (gnus-summary-iterate n
6388       (save-excursion
6389         (set-buffer gnus-summary-buffer)
6390         (let ((mail-parse-charset gnus-newsgroup-charset)
6391               (mail-parse-ignored-charsets gnus-newsgroup-ignored-charsets)
6392               (summary-buffer gnus-summary-buffer)
6393               references point)
6394           (gnus-set-global-variables)
6395           (when (gnus-group-read-only-p)
6396             (error "The current newsgroup does not support article encrypt"))
6397           (gnus-summary-show-article t)
6398           (setq references
6399               (or (mail-header-references gnus-current-headers) ""))
6400           (set-buffer gnus-article-buffer)
6401           (let* ((buffer-read-only nil)
6402                  (headers
6403                   (mapcar (lambda (field)
6404                             (and (save-restriction
6405                                    (message-narrow-to-head)
6406                                    (goto-char (point-min))
6407                                    (search-forward field nil t))
6408                                  (prog2
6409                                      (message-narrow-to-field)
6410                                      (buffer-substring (point-min) (point-max))
6411                                    (delete-region (point-min) (point-max))
6412                                    (widen))))
6413                           '("Content-Type:" "Content-Transfer-Encoding:"
6414                             "Content-Disposition:"))))
6415             (message-narrow-to-head)
6416             (message-remove-header "MIME-Version")
6417             (goto-char (point-max))
6418             (setq point (point))
6419             (insert (apply 'concat headers))
6420             (widen)
6421             (narrow-to-region point (point-max))
6422             (let ((message-options message-options))
6423               (message-options-set 'message-sender user-mail-address)
6424               (message-options-set 'message-recipients user-mail-address)
6425               (message-options-set 'message-sign-encrypt 'not)
6426               (funcall func))
6427             (goto-char (point-min))
6428             (insert "MIME-Version: 1.0\n")
6429             (widen)
6430             (gnus-summary-edit-article-done
6431              references nil summary-buffer t))
6432           (when gnus-keep-backlog
6433             (gnus-backlog-remove-article
6434              (car gnus-article-current) (cdr gnus-article-current)))
6435           (save-excursion
6436             (when (get-buffer gnus-original-article-buffer)
6437               (set-buffer gnus-original-article-buffer)
6438               (setq gnus-original-article nil)))
6439           (when gnus-use-cache
6440             (gnus-cache-update-article
6441              (car gnus-article-current) (cdr gnus-article-current))))))))
6442
6443 (defvar gnus-mime-security-button-line-format "%{%([[%t:%i]%D]%)%}\n"
6444   "The following specs can be used:
6445 %t  The security MIME type
6446 %i  Additional info
6447 %d  Details
6448 %D  Details if button is pressed")
6449
6450 (defvar gnus-mime-security-button-end-line-format "%{%([[End of %t]%D]%)%}\n"
6451   "The following specs can be used:
6452 %t  The security MIME type
6453 %i  Additional info
6454 %d  Details
6455 %D  Details if button is pressed")
6456
6457 (defvar gnus-mime-security-button-line-format-alist
6458   '((?t gnus-tmp-type ?s)
6459     (?i gnus-tmp-info ?s)
6460     (?d gnus-tmp-details ?s)
6461     (?D gnus-tmp-pressed-details ?s)))
6462
6463 (defvar gnus-mime-security-button-map
6464   (let ((map (make-sparse-keymap)))
6465     (unless (>= (string-to-number emacs-version) 21)
6466       (set-keymap-parent map gnus-article-mode-map))
6467     (define-key map gnus-mouse-2 'gnus-article-push-button)
6468     (define-key map "\r" 'gnus-article-press-button)
6469     map))
6470
6471 (defvar gnus-mime-security-details-buffer nil)
6472
6473 (defvar gnus-mime-security-button-pressed nil)
6474
6475 (defvar gnus-mime-security-show-details-inline t
6476   "If non-nil, show details in the article buffer.")
6477
6478 (defun gnus-mime-security-verify-or-decrypt (handle)
6479   (mm-remove-parts (cdr handle))
6480   (let ((region (mm-handle-multipart-ctl-parameter handle 'gnus-region))
6481         point buffer-read-only)
6482     (if region
6483         (goto-char (car region)))
6484     (save-restriction
6485       (narrow-to-region (point) (point))
6486       (with-current-buffer (mm-handle-multipart-original-buffer handle)
6487         (let* ((mm-verify-option 'known)
6488                (mm-decrypt-option 'known)
6489                (nparts (mm-possibly-verify-or-decrypt (cdr handle) handle)))
6490           (unless (eq nparts (cdr handle))
6491             (mm-destroy-parts (cdr handle))
6492             (setcdr handle nparts))))
6493       (setq point (point))
6494       (gnus-mime-display-security handle)
6495       (goto-char (point-max)))
6496     (when region
6497       (delete-region (point) (cdr region))
6498       (set-marker (car region) nil)
6499       (set-marker (cdr region) nil))
6500     (goto-char point)))
6501
6502 (defun gnus-mime-security-show-details (handle)
6503   (let ((details (mm-handle-multipart-ctl-parameter handle 'gnus-details)))
6504     (if details
6505         (if gnus-mime-security-show-details-inline
6506             (let ((gnus-mime-security-button-pressed t)
6507                   (gnus-mime-security-button-line-format
6508                    (get-text-property (point) 'gnus-line-format))
6509                 buffer-read-only)
6510               (forward-char -1)
6511               (while (eq (get-text-property (point) 'gnus-line-format)
6512                          gnus-mime-security-button-line-format)
6513                 (forward-char -1))
6514               (forward-char)
6515               (save-restriction
6516                 (narrow-to-region (point) (point))
6517                 (gnus-insert-mime-security-button handle))
6518               (delete-region (point)
6519                              (or (text-property-not-all
6520                                   (point) (point-max)
6521                                   'gnus-line-format
6522                                   gnus-mime-security-button-line-format)
6523                                  (point-max))))
6524           (if (gnus-buffer-live-p gnus-mime-security-details-buffer)
6525               (with-current-buffer gnus-mime-security-details-buffer
6526                 (erase-buffer)
6527                 t)
6528             (setq gnus-mime-security-details-buffer
6529                   (gnus-get-buffer-create "*MIME Security Details*")))
6530           (with-current-buffer gnus-mime-security-details-buffer
6531             (insert details)
6532             (goto-char (point-min)))
6533           (pop-to-buffer gnus-mime-security-details-buffer))
6534       (gnus-message 5 "No details."))))
6535
6536 (defun gnus-mime-security-press-button (handle)
6537   (if (mm-handle-multipart-ctl-parameter handle 'gnus-info)
6538       (gnus-mime-security-show-details handle)
6539     (gnus-mime-security-verify-or-decrypt handle)))
6540
6541 (defun gnus-insert-mime-security-button (handle &optional displayed)
6542   (let* ((protocol (mm-handle-multipart-ctl-parameter handle 'protocol))
6543          (gnus-tmp-type
6544           (concat
6545            (or (nth 2 (assoc protocol mm-verify-function-alist))
6546                (nth 2 (assoc protocol mm-decrypt-function-alist))
6547                "Unknown")
6548            (if (equal (car handle) "multipart/signed")
6549                " Signed" " Encrypted")
6550            " Part"))
6551          (gnus-tmp-info
6552           (or (mm-handle-multipart-ctl-parameter handle 'gnus-info)
6553               "Undecided"))
6554          (gnus-tmp-details
6555           (mm-handle-multipart-ctl-parameter handle 'gnus-details))
6556          gnus-tmp-pressed-details
6557          b e)
6558     (setq gnus-tmp-details
6559           (if gnus-tmp-details
6560               (concat "\n" gnus-tmp-details) ""))
6561     (setq gnus-tmp-pressed-details
6562           (if gnus-mime-security-button-pressed gnus-tmp-details ""))
6563     (unless (bolp)
6564       (insert "\n"))
6565     (setq b (point))
6566     (gnus-eval-format
6567      gnus-mime-security-button-line-format
6568      gnus-mime-security-button-line-format-alist
6569      `(,@(gnus-local-map-property gnus-mime-security-button-map)
6570          gnus-callback gnus-mime-security-press-button
6571          gnus-line-format ,gnus-mime-security-button-line-format
6572          article-type annotation
6573          gnus-data ,handle))
6574     (setq e (point))
6575     (widget-convert-button
6576      'link b e
6577      :mime-handle handle
6578      :action 'gnus-widget-press-button
6579      :button-keymap gnus-mime-security-button-map
6580      :help-echo
6581      (lambda (widget/window &optional overlay pos)
6582        ;; Needed to properly clear the message due to a bug in
6583        ;; wid-edit (XEmacs only).
6584        (if (boundp 'help-echo-owns-message)
6585            (setq help-echo-owns-message t))
6586        (format
6587         "%S: show detail"
6588         (aref gnus-mouse-2 0))))))
6589
6590 (defun gnus-mime-display-security (handle)
6591   (save-restriction
6592     (narrow-to-region (point) (point))
6593     (unless (gnus-unbuttonized-mime-type-p (car handle))
6594       (gnus-insert-mime-security-button handle))
6595     (gnus-mime-display-mixed (cdr handle))
6596     (unless (bolp)
6597       (insert "\n"))
6598     (unless (gnus-unbuttonized-mime-type-p (car handle))
6599       (let ((gnus-mime-security-button-line-format
6600              gnus-mime-security-button-end-line-format))
6601         (gnus-insert-mime-security-button handle)))
6602     (mm-set-handle-multipart-parameter
6603      handle 'gnus-region
6604      (cons (set-marker (make-marker) (point-min))
6605            (set-marker (make-marker) (point-max))))))
6606
6607 (gnus-ems-redefine)
6608
6609 (provide 'gnus-art)
6610
6611 (run-hooks 'gnus-art-load-hook)
6612
6613 ;;; gnus-art.el ends here