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