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