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