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