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