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               (select-window window))))
4058       (goto-char point)
4059       (delete-region (gnus-point-at-bol) (progn (forward-line 1) (point)))
4060       (gnus-insert-mime-button
4061        handle id (list (mm-handle-displayed-p handle)))
4062       (goto-char point))))
4063
4064 (defun gnus-article-goto-part (n)
4065   "Go to MIME part N."
4066   (let ((point (text-property-any (point-min) (point-max) 'gnus-part n)))
4067     (when point
4068       (goto-char point))))
4069
4070 (defun gnus-insert-mime-button (handle gnus-tmp-id &optional displayed)
4071   (let ((gnus-tmp-name
4072          (or (mail-content-type-get (mm-handle-type handle)
4073                                     'name)
4074              (mail-content-type-get (mm-handle-disposition handle)
4075                                     'filename)
4076              (mail-content-type-get (mm-handle-type handle)
4077                                     'url)
4078              ""))
4079         (gnus-tmp-type (mm-handle-media-type handle))
4080         (gnus-tmp-description
4081          (mail-decode-encoded-word-string (or (mm-handle-description handle)
4082                                               "")))
4083         (gnus-tmp-dots
4084          (if (if displayed (car displayed)
4085                (mm-handle-displayed-p handle))
4086              "" "..."))
4087         (gnus-tmp-length (with-current-buffer (mm-handle-buffer handle)
4088                            (buffer-size)))
4089         gnus-tmp-type-long b e)
4090     (when (string-match ".*/" gnus-tmp-name)
4091       (setq gnus-tmp-name (replace-match "" t t gnus-tmp-name)))
4092     (setq gnus-tmp-type-long (concat gnus-tmp-type
4093                                      (and (not (equal gnus-tmp-name ""))
4094                                           (concat "; " gnus-tmp-name))))
4095     (or (equal gnus-tmp-description "")
4096         (setq gnus-tmp-type-long (concat " --- " gnus-tmp-type-long)))
4097     (unless (bolp)
4098       (insert "\n"))
4099     (setq b (point))
4100     (gnus-eval-format
4101      gnus-mime-button-line-format gnus-mime-button-line-format-alist
4102      `(keymap ,gnus-mime-button-map
4103               ,@(if (>= (string-to-number emacs-version) 21)
4104                     nil
4105                   (list 'local-map gnus-mime-button-map))
4106               gnus-callback gnus-mm-display-part
4107               gnus-part ,gnus-tmp-id
4108               article-type annotation
4109               gnus-data ,handle))
4110     (setq e (point))
4111     (widget-convert-button
4112      'link b e
4113      :mime-handle handle
4114      :action 'gnus-widget-press-button
4115      :button-keymap gnus-mime-button-map
4116      :help-echo
4117      (lambda (widget/window &optional overlay pos)
4118        ;; Needed to properly clear the message due to a bug in
4119        ;; wid-edit (XEmacs only).
4120        (if (boundp 'help-echo-owns-message)
4121            (setq help-echo-owns-message t))
4122        (format
4123         "%S: %s the MIME part; %S: more options"
4124         (aref gnus-mouse-2 0)
4125         ;; XEmacs will get a single widget arg; Emacs 21 will get
4126         ;; window, overlay, position.
4127         (if (mm-handle-displayed-p
4128              (if overlay
4129                  (with-current-buffer (gnus-overlay-buffer overlay)
4130                    (widget-get (widget-at (gnus-overlay-start overlay))
4131                                :mime-handle))
4132                (widget-get widget/window :mime-handle)))
4133             "hide" "show")
4134         (aref gnus-down-mouse-3 0))))))
4135
4136 (defun gnus-widget-press-button (elems el)
4137   (goto-char (widget-get elems :from))
4138   (gnus-article-press-button))
4139
4140 (defvar gnus-displaying-mime nil)
4141
4142 (defun gnus-display-mime (&optional ihandles)
4143   "Display the MIME parts."
4144   (save-excursion
4145     (save-selected-window
4146       (let ((window (get-buffer-window gnus-article-buffer))
4147             (point (point)))
4148         (when window
4149           (select-window window)
4150           ;; We have to do this since selecting the window
4151           ;; may change the point.  So we set the window point.
4152           (set-window-point window point)))
4153       (let* ((handles (or ihandles (mm-dissect-buffer) (mm-uu-dissect)))
4154              buffer-read-only handle name type b e display)
4155         (when (and (not ihandles)
4156                    (not gnus-displaying-mime))
4157           ;; Top-level call; we clean up.
4158           (when gnus-article-mime-handles
4159             (mm-destroy-parts gnus-article-mime-handles)
4160             (setq gnus-article-mime-handle-alist nil));; A trick.
4161           (setq gnus-article-mime-handles handles)
4162           ;; We allow users to glean info from the handles.
4163           (when gnus-article-mime-part-function
4164             (gnus-mime-part-function handles)))
4165         (if (and handles
4166                  (or (not (stringp (car handles)))
4167                      (cdr handles)))
4168             (progn
4169               (when (and (not ihandles)
4170                          (not gnus-displaying-mime))
4171                 ;; Clean up for mime parts.
4172                 (article-goto-body)
4173                 (delete-region (point) (point-max)))
4174               (let ((gnus-displaying-mime t))
4175                 (gnus-mime-display-part handles)))
4176           (save-restriction
4177             (article-goto-body)
4178             (narrow-to-region (point) (point-max))
4179             (gnus-treat-article nil 1 1)
4180             (widen)))
4181         (unless ihandles
4182           ;; Highlight the headers.
4183           (save-excursion
4184             (save-restriction
4185               (article-goto-body)
4186               (narrow-to-region (point-min) (point))
4187               (gnus-treat-article 'head))))))))
4188
4189 (defvar gnus-mime-display-multipart-as-mixed nil)
4190 (defvar gnus-mime-display-multipart-alternative-as-mixed nil)
4191 (defvar gnus-mime-display-multipart-related-as-mixed nil)
4192
4193 (defun gnus-mime-display-part (handle)
4194   (cond
4195    ;; Single part.
4196    ((not (stringp (car handle)))
4197     (gnus-mime-display-single handle))
4198    ;; User-defined multipart
4199    ((cdr (assoc (car handle) gnus-mime-multipart-functions))
4200     (funcall (cdr (assoc (car handle) gnus-mime-multipart-functions))
4201              handle))
4202    ;; multipart/alternative
4203    ((and (equal (car handle) "multipart/alternative")
4204          (not (or gnus-mime-display-multipart-as-mixed
4205                   gnus-mime-display-multipart-alternative-as-mixed)))
4206     (let ((id (1+ (length gnus-article-mime-handle-alist))))
4207       (push (cons id handle) gnus-article-mime-handle-alist)
4208       (gnus-mime-display-alternative (cdr handle) nil nil id)))
4209    ;; multipart/related
4210    ((and (equal (car handle) "multipart/related")
4211          (not (or gnus-mime-display-multipart-as-mixed
4212                   gnus-mime-display-multipart-related-as-mixed)))
4213     ;;;!!!We should find the start part, but we just default
4214     ;;;!!!to the first part.
4215     ;;(gnus-mime-display-part (cadr handle))
4216     ;;;!!! Most multipart/related is an HTML message plus images.
4217     ;;;!!! Unfortunately we are unable to let W3 display those
4218     ;;;!!! included images, so we just display it as a mixed multipart.
4219     ;;(gnus-mime-display-mixed (cdr handle))
4220     ;;;!!! No, w3 can display everything just fine.
4221     (gnus-mime-display-part (cadr handle)))
4222    ((equal (car handle) "multipart/signed")
4223     (or (memq 'signed gnus-article-wash-types)
4224         (push 'signed gnus-article-wash-types))
4225     (gnus-mime-display-security handle))
4226    ((equal (car handle) "multipart/encrypted")
4227     (or (memq 'encrypted gnus-article-wash-types)
4228         (push 'encrypted gnus-article-wash-types))
4229     (gnus-mime-display-security handle))
4230    ;; Other multiparts are handled like multipart/mixed.
4231    (t
4232     (gnus-mime-display-mixed (cdr handle)))))
4233
4234 (defun gnus-mime-part-function (handles)
4235   (if (stringp (car handles))
4236       (mapcar 'gnus-mime-part-function (cdr handles))
4237     (funcall gnus-article-mime-part-function handles)))
4238
4239 (defun gnus-mime-display-mixed (handles)
4240   (mapcar 'gnus-mime-display-part handles))
4241
4242 (defun gnus-mime-display-single (handle)
4243   (let ((type (mm-handle-media-type handle))
4244         (ignored gnus-ignored-mime-types)
4245         (not-attachment t)
4246         (move nil)
4247         display text)
4248     (catch 'ignored
4249       (progn
4250         (while ignored
4251           (when (string-match (pop ignored) type)
4252             (throw 'ignored nil)))
4253         (if (and (setq not-attachment
4254                        (and (not (mm-inline-override-p handle))
4255                             (or (not (mm-handle-disposition handle))
4256                                 (equal (car (mm-handle-disposition handle))
4257                                        "inline")
4258                                 (mm-attachment-override-p handle))))
4259                  (mm-automatic-display-p handle)
4260                  (or (and
4261                       (mm-inlinable-p handle)
4262                       (mm-inlined-p handle))
4263                      (mm-automatic-external-display-p type)))
4264             (setq display t)
4265           (when (equal (mm-handle-media-supertype handle) "text")
4266             (setq text t)))
4267         (let ((id (1+ (length gnus-article-mime-handle-alist)))
4268               beg)
4269           (push (cons id handle) gnus-article-mime-handle-alist)
4270           (when (or (not display)
4271                     (not (gnus-unbuttonized-mime-type-p type)))
4272             ;(gnus-article-insert-newline)
4273             (gnus-insert-mime-button
4274              handle id (list (or display (and not-attachment text))))
4275             (gnus-article-insert-newline)
4276             ;(gnus-article-insert-newline)
4277             ;; Remember modify the number of forward lines.
4278             (setq move t))
4279           (setq beg (point))
4280           (cond
4281            (display
4282             (when move
4283               (forward-line -1)
4284               (setq beg (point)))
4285             (let ((mail-parse-charset gnus-newsgroup-charset)
4286                   (mail-parse-ignored-charsets
4287                    (save-excursion (condition-case ()
4288                                        (set-buffer gnus-summary-buffer)
4289                                      (error))
4290                                    gnus-newsgroup-ignored-charsets)))
4291               (mm-display-part handle t))
4292             (goto-char (point-max)))
4293            ((and text not-attachment)
4294             (when move
4295               (forward-line -1)
4296               (setq beg (point)))
4297             (gnus-article-insert-newline)
4298             (mm-insert-inline handle (mm-get-part handle))
4299             (goto-char (point-max))))
4300           ;; Do highlighting.
4301           (save-excursion
4302             (save-restriction
4303               (narrow-to-region beg (point))
4304               (gnus-treat-article
4305                nil id
4306                (gnus-article-mime-total-parts)
4307                (mm-handle-media-type handle)))))))))
4308
4309 (defun gnus-unbuttonized-mime-type-p (type)
4310   "Say whether TYPE is to be unbuttonized."
4311   (unless gnus-inhibit-mime-unbuttonizing
4312     (catch 'found
4313       (let ((types gnus-unbuttonized-mime-types))
4314         (while types
4315           (when (string-match (pop types) type)
4316             (throw 'found t)))))))
4317
4318 (defun gnus-article-insert-newline ()
4319   "Insert a newline, but mark it as undeletable."
4320   (gnus-put-text-property
4321    (point) (progn (insert "\n") (point)) 'gnus-undeletable t))
4322
4323 (defun gnus-mime-display-alternative (handles &optional preferred ibegend id)
4324   (let* ((preferred (or preferred (mm-preferred-alternative handles)))
4325          (ihandles handles)
4326          (point (point))
4327          handle buffer-read-only from props begend not-pref)
4328     (save-window-excursion
4329       (save-restriction
4330         (when ibegend
4331           (narrow-to-region (car ibegend)
4332                             (or (cdr ibegend)
4333                                 (progn
4334                                   (goto-char (car ibegend))
4335                                   (forward-line 2)
4336                                   (point))))
4337           (delete-region (point-min) (point-max))
4338           (mm-remove-parts handles))
4339         (setq begend (list (point-marker)))
4340         ;; Do the toggle.
4341         (unless (setq not-pref (cadr (member preferred ihandles)))
4342           (setq not-pref (car ihandles)))
4343         (when (or ibegend
4344                   (not preferred)
4345                   (not (gnus-unbuttonized-mime-type-p
4346                         "multipart/alternative")))
4347           (gnus-add-text-properties
4348            (setq from (point))
4349            (progn
4350              (insert (format "%d.  " id))
4351              (point))
4352            `(gnus-callback
4353              (lambda (handles)
4354                (unless ,(not ibegend)
4355                  (setq gnus-article-mime-handle-alist
4356                        ',gnus-article-mime-handle-alist))
4357                (gnus-mime-display-alternative
4358                 ',ihandles ',not-pref ',begend ,id))
4359              ,@(if (>= (string-to-number emacs-version) 21)
4360                    nil ;; XEmacs doesn't care
4361                  (list 'local-map gnus-mime-button-map))
4362              ,gnus-mouse-face-prop ,gnus-article-mouse-face
4363              face ,gnus-article-button-face
4364              keymap ,gnus-mime-button-map
4365              gnus-part ,id
4366              gnus-data ,handle))
4367           (widget-convert-button 'link from (point)
4368                                  :action 'gnus-widget-press-button
4369                                  :button-keymap gnus-widget-button-keymap)
4370           ;; Do the handles
4371           (while (setq handle (pop handles))
4372             (gnus-add-text-properties
4373              (setq from (point))
4374              (progn
4375                (insert (format "(%c) %-18s"
4376                                (if (equal handle preferred) ?* ? )
4377                                (mm-handle-media-type handle)))
4378                (point))
4379              `(gnus-callback
4380                (lambda (handles)
4381                  (unless ,(not ibegend)
4382                    (setq gnus-article-mime-handle-alist
4383                          ',gnus-article-mime-handle-alist))
4384                  (gnus-mime-display-alternative
4385                   ',ihandles ',handle ',begend ,id))
4386                ,@(if (>= (string-to-number emacs-version) 21)
4387                      nil ;; XEmacs doesn't care
4388                    (list 'local-map gnus-mime-button-map))
4389                ,gnus-mouse-face-prop ,gnus-article-mouse-face
4390                face ,gnus-article-button-face
4391                keymap ,gnus-mime-button-map
4392                gnus-part ,id
4393                gnus-data ,handle))
4394             (widget-convert-button 'link from (point)
4395                                    :action 'gnus-widget-press-button
4396                                    :button-keymap gnus-widget-button-keymap)
4397             (insert "  "))
4398           (insert "\n\n"))
4399         (when preferred
4400           (if (stringp (car preferred))
4401               (gnus-display-mime preferred)
4402             (let ((mail-parse-charset gnus-newsgroup-charset)
4403                   (mail-parse-ignored-charsets
4404                    (save-excursion (set-buffer gnus-summary-buffer)
4405                                    gnus-newsgroup-ignored-charsets)))
4406               (mm-display-part preferred)
4407               ;; Do highlighting.
4408               (save-excursion
4409                 (save-restriction
4410                   (narrow-to-region (car begend) (point-max))
4411                   (gnus-treat-article
4412                    nil (length gnus-article-mime-handle-alist)
4413                    (gnus-article-mime-total-parts)
4414                    (mm-handle-media-type handle))))))
4415           (goto-char (point-max))
4416           (setcdr begend (point-marker)))))
4417     (when ibegend
4418       (goto-char point))))
4419
4420 (defun gnus-article-wash-status ()
4421   "Return a string which display status of article washing."
4422   (save-excursion
4423     (set-buffer gnus-article-buffer)
4424     (let ((cite (memq 'cite gnus-article-wash-types))
4425           (headers (memq 'headers gnus-article-wash-types))
4426           (boring (memq 'boring-headers gnus-article-wash-types))
4427           (pgp (memq 'pgp gnus-article-wash-types))
4428           (pem (memq 'pem gnus-article-wash-types))
4429           (signed (memq 'signed gnus-article-wash-types))
4430           (encrypted (memq 'encrypted gnus-article-wash-types))
4431           (signature (memq 'signature gnus-article-wash-types))
4432           (overstrike (memq 'overstrike gnus-article-wash-types))
4433           (emphasis (memq 'emphasis gnus-article-wash-types)))
4434       (format "%c%c%c%c%c%c%c"
4435               (if cite ?c ? )
4436               (if (or headers boring) ?h ? )
4437               (if (or pgp pem signed encrypted) ?p ? )
4438               (if signature ?s ? )
4439               (if overstrike ?o ? )
4440               (if gnus-show-mime ?m ? )
4441               (if emphasis ?e ? )))))
4442
4443 (defalias 'gnus-article-hide-headers-if-wanted 'gnus-article-maybe-hide-headers)
4444
4445 (defun gnus-article-maybe-hide-headers ()
4446   "Hide unwanted headers if `gnus-have-all-headers' is nil.
4447 Provided for backwards compatibility."
4448   (when (and (or (not (gnus-buffer-live-p gnus-summary-buffer))
4449                  (not (save-excursion (set-buffer gnus-summary-buffer)
4450                                       gnus-have-all-headers)))
4451              (not gnus-inhibit-hiding))
4452     (gnus-article-hide-headers)))
4453
4454 ;;; Article savers.
4455
4456 (defun gnus-output-to-file (file-name)
4457   "Append the current article to a file named FILE-NAME."
4458   (let ((artbuf (current-buffer)))
4459     (with-temp-buffer
4460       (insert-buffer-substring artbuf)
4461       ;; Append newline at end of the buffer as separator, and then
4462       ;; save it to file.
4463       (goto-char (point-max))
4464       (insert "\n")
4465       (write-region-as-binary (point-min) (point-max) file-name 'append)
4466       t)))
4467
4468 (defun gnus-narrow-to-page (&optional arg)
4469   "Narrow the article buffer to a page.
4470 If given a numerical ARG, move forward ARG pages."
4471   (interactive "P")
4472   (setq arg (if arg (prefix-numeric-value arg) 0))
4473   (save-excursion
4474     (set-buffer gnus-article-buffer)
4475     (goto-char (point-min))
4476     (widen)
4477     ;; Remove any old next/prev buttons.
4478     (when (gnus-visual-p 'page-marker)
4479       (let ((buffer-read-only nil))
4480         (gnus-remove-text-with-property 'gnus-prev)
4481         (gnus-remove-text-with-property 'gnus-next)))
4482     (when
4483         (cond ((< arg 0)
4484                (re-search-backward page-delimiter nil 'move (1+ (abs arg))))
4485               ((> arg 0)
4486                (re-search-forward page-delimiter nil 'move arg)))
4487       (goto-char (match-end 0)))
4488     (narrow-to-region
4489      (point)
4490      (if (re-search-forward page-delimiter nil 'move)
4491          (match-beginning 0)
4492        (point)))
4493     (when (and (gnus-visual-p 'page-marker)
4494                (not (= (point-min) 1)))
4495       (save-excursion
4496         (goto-char (point-min))
4497         (gnus-insert-prev-page-button)))
4498     (when (and (gnus-visual-p 'page-marker)
4499                (< (+ (point-max) 2) (buffer-size)))
4500       (save-excursion
4501         (goto-char (point-max))
4502         (gnus-insert-next-page-button)))))
4503
4504 ;; Article mode commands
4505
4506 (defun gnus-article-goto-next-page ()
4507   "Show the next page of the article."
4508   (interactive)
4509   (when (gnus-article-next-page)
4510     (goto-char (point-min))
4511     (gnus-article-read-summary-keys nil (gnus-character-to-event ?n))))
4512
4513 (defun gnus-article-goto-prev-page ()
4514   "Show the next page of the article."
4515   (interactive)
4516   (if (bobp) (gnus-article-read-summary-keys nil (gnus-character-to-event ?p))
4517     (gnus-article-prev-page nil)))
4518
4519 (defun gnus-article-next-page (&optional lines)
4520   "Show the next page of the current article.
4521 If end of article, return non-nil.  Otherwise return nil.
4522 Argument LINES specifies lines to be scrolled up."
4523   (interactive "p")
4524   (let ((start (window-start))
4525         end-of-buffer end-of-page)
4526     (save-excursion
4527       (move-to-window-line -1)
4528       (if (<= (point) start)
4529           (progn
4530             (forward-line 2)
4531             (setq start (point)))
4532         (forward-line 1)
4533         (setq start nil))
4534       (unless (or (cond ((eq (1+ (buffer-size)) (point))
4535                          (and (pos-visible-in-window-p)
4536                               (setq end-of-buffer t)))
4537                         ((eobp)
4538                          (setq end-of-page t)))
4539                   (not lines))
4540         (move-to-window-line lines)
4541         (unless (search-backward "\n\n" nil t)
4542           (setq start (point)))))
4543     (cond (end-of-buffer t)
4544           (end-of-page
4545            (gnus-narrow-to-page 1)
4546            nil)
4547           (t
4548            (if start
4549                (set-window-start (selected-window) start)
4550              (let (window-pixel-scroll-increment)
4551                (scroll-up lines)))
4552            nil))))
4553
4554 (defun gnus-article-prev-page (&optional lines)
4555   "Show previous page of current article.
4556 Argument LINES specifies lines to be scrolled down."
4557   (interactive "p")
4558   (let (beginning-of-buffer beginning-of-page)
4559     (save-excursion
4560       (move-to-window-line 0)
4561       (cond ((eq 1 (point))
4562              (setq beginning-of-buffer t))
4563             ((bobp)
4564              (setq beginning-of-page t))))
4565     (cond (beginning-of-buffer)
4566           (beginning-of-page
4567            (gnus-narrow-to-page -1))
4568           (t
4569            (condition-case nil
4570                (let (window-pixel-scroll-increment)
4571                  (scroll-down lines))
4572              (beginning-of-buffer
4573               (goto-char (point-min))))))))
4574
4575 (defun gnus-article-refer-article ()
4576   "Read article specified by message-id around point."
4577   (interactive)
4578   (let ((point (point)))
4579     (search-forward ">" nil t)          ;Move point to end of "<....>".
4580     (if (re-search-backward "\\(<[^<> \t\n]+>\\)" nil t)
4581         (let ((message-id (match-string 1)))
4582           (goto-char point)
4583           (set-buffer gnus-summary-buffer)
4584           (gnus-summary-refer-article message-id))
4585       (goto-char (point))
4586       (error "No references around point"))))
4587
4588 (defun gnus-article-show-summary ()
4589   "Reconfigure windows to show summary buffer."
4590   (interactive)
4591   (if (not (gnus-buffer-live-p gnus-summary-buffer))
4592       (error "There is no summary buffer for this article buffer")
4593     (gnus-article-set-globals)
4594     (gnus-configure-windows 'article)
4595     (gnus-summary-goto-subject gnus-current-article)
4596     (gnus-summary-position-point)))
4597
4598 (defun gnus-article-describe-briefly ()
4599   "Describe article mode commands briefly."
4600   (interactive)
4601   (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")))
4602
4603 (defun gnus-article-summary-command ()
4604   "Execute the last keystroke in the summary buffer."
4605   (interactive)
4606   (let ((obuf (current-buffer))
4607         (owin (current-window-configuration))
4608         func)
4609     (switch-to-buffer gnus-article-current-summary 'norecord)
4610     (setq func (lookup-key (current-local-map) (this-command-keys)))
4611     (call-interactively func)
4612     (set-buffer obuf)
4613     (set-window-configuration owin)
4614     (set-window-point (get-buffer-window (current-buffer)) (point))))
4615
4616 (defun gnus-article-summary-command-nosave ()
4617   "Execute the last keystroke in the summary buffer."
4618   (interactive)
4619   (let (func)
4620     (pop-to-buffer gnus-article-current-summary 'norecord)
4621     (setq func (lookup-key (current-local-map) (this-command-keys)))
4622     (call-interactively func)))
4623
4624 (defun gnus-article-check-buffer ()
4625   "Beep if not in an article buffer."
4626   (unless (eq (get-buffer gnus-article-buffer) (current-buffer))
4627     (error "Command invoked outside of a Gnus article buffer")))
4628
4629 (defun gnus-article-read-summary-keys (&optional arg key not-restore-window)
4630   "Read a summary buffer key sequence and execute it from the article buffer."
4631   (interactive "P")
4632   (gnus-article-check-buffer)
4633   (let ((nosaves
4634          '("q" "Q"  "c" "r" "R" "\C-c\C-f" "m"  "a" "f" "F"
4635            "Zc" "ZC" "ZE" "ZJ" "ZQ" "ZZ" "Zn" "ZR" "ZG" "ZN" "ZP"
4636            "=" "^" "\M-^" "|"))
4637         (nosave-but-article
4638          '("A\r"))
4639         (nosave-in-article
4640          '("\C-d"))
4641         (up-to-top
4642          '("n" "Gn" "p" "Gp"))
4643         keys new-sum-point)
4644     (save-excursion
4645       (set-buffer gnus-article-current-summary)
4646       (let (gnus-pick-mode)
4647         (push (or key last-command-event) unread-command-events)
4648         (setq keys (static-if (featurep 'xemacs)
4649                        (events-to-keys (read-key-sequence nil))
4650                      (read-key-sequence nil)))))
4651     (message "")
4652
4653     (if (or (member keys nosaves)
4654             (member keys nosave-but-article)
4655             (member keys nosave-in-article))
4656         (let (func)
4657           (save-window-excursion
4658             (pop-to-buffer gnus-article-current-summary 'norecord)
4659             ;; We disable the pick minor mode commands.
4660             (let (gnus-pick-mode)
4661               (setq func (lookup-key (current-local-map) keys))))
4662           (if (or (not func)
4663                   (numberp func))
4664               (ding)
4665             (unless (member keys nosave-in-article)
4666               (set-buffer gnus-article-current-summary))
4667             (call-interactively func)
4668             (setq new-sum-point (point)))
4669           (when (member keys nosave-but-article)
4670             (pop-to-buffer gnus-article-buffer 'norecord)))
4671       ;; These commands should restore window configuration.
4672       (let ((obuf (current-buffer))
4673             (owin (current-window-configuration))
4674             (opoint (point))
4675             (summary gnus-article-current-summary)
4676             func in-buffer selected)
4677         (if not-restore-window
4678             (pop-to-buffer summary 'norecord)
4679           (switch-to-buffer summary 'norecord))
4680         (setq in-buffer (current-buffer))
4681         ;; We disable the pick minor mode commands.
4682         (if (and (setq func (let (gnus-pick-mode)
4683                               (lookup-key (current-local-map) keys)))
4684                  (functionp func))
4685             (progn
4686               (call-interactively func)
4687               (setq new-sum-point (point))
4688               (when (eq in-buffer (current-buffer))
4689                 (setq selected (gnus-summary-select-article))
4690                 (set-buffer obuf)
4691                 (unless not-restore-window
4692                   (set-window-configuration owin))
4693                 (when (eq selected 'old)
4694                   (article-goto-body)
4695                   (set-window-start (get-buffer-window (current-buffer))
4696                                     1)
4697                   (set-window-point (get-buffer-window (current-buffer))
4698                                     (point)))
4699                 (let ((win (get-buffer-window gnus-article-current-summary)))
4700                   (when win
4701                     (set-window-point win new-sum-point))))    )
4702           (switch-to-buffer gnus-article-buffer)
4703           (ding))))))
4704
4705 (defun gnus-article-describe-key (key)
4706   "Display documentation of the function invoked by KEY.  KEY is a string."
4707   (interactive "kDescribe key: ")
4708   (gnus-article-check-buffer)
4709   (if (eq (key-binding key) 'gnus-article-read-summary-keys)
4710       (save-excursion
4711         (set-buffer gnus-article-current-summary)
4712         (let (gnus-pick-mode)
4713           (if (featurep 'xemacs)
4714               (progn
4715                 (push (elt key 0) unread-command-events)
4716                 (setq key (events-to-keys
4717                            (read-key-sequence "Describe key: "))))
4718             (setq unread-command-events
4719                   (mapcar
4720                    (lambda (x) (if (>= x 128) (list 'meta (- x 128)) x))
4721                    key))
4722             (setq key (read-key-sequence "Describe key: "))))
4723         (describe-key key))
4724     (describe-key key)))
4725
4726 (defun gnus-article-describe-key-briefly (key &optional insert)
4727   "Display documentation of the function invoked by KEY.  KEY is a string."
4728   (interactive "kDescribe key: \nP")
4729   (gnus-article-check-buffer)
4730   (if (eq (key-binding key) 'gnus-article-read-summary-keys)
4731       (save-excursion
4732         (set-buffer gnus-article-current-summary)
4733         (let (gnus-pick-mode)
4734           (if (featurep 'xemacs)
4735               (progn
4736                 (push (elt key 0) unread-command-events)
4737                 (setq key (events-to-keys
4738                            (read-key-sequence "Describe key: "))))
4739             (setq unread-command-events
4740                   (mapcar
4741                    (lambda (x) (if (>= x 128) (list 'meta (- x 128)) x))
4742                    key))
4743             (setq key (read-key-sequence "Describe key: "))))
4744         (describe-key-briefly key insert))
4745     (describe-key-briefly key insert)))
4746
4747 (defun gnus-article-hide (&optional arg force)
4748   "Hide all the gruft in the current article.
4749 This means that PGP stuff, signatures, cited text and (some)
4750 headers will be hidden.
4751 If given a prefix, show the hidden text instead."
4752   (interactive (append (gnus-article-hidden-arg) (list 'force)))
4753   (gnus-article-hide-headers arg)
4754   (gnus-article-hide-list-identifiers arg)
4755   (gnus-article-hide-pgp arg)
4756   (gnus-article-hide-citation-maybe arg force)
4757   (gnus-article-hide-signature arg))
4758
4759 (defun gnus-article-maybe-highlight ()
4760   "Do some article highlighting if article highlighting is requested."
4761   (when (gnus-visual-p 'article-highlight 'highlight)
4762     (gnus-article-highlight-some)))
4763
4764 (defun gnus-check-group-server ()
4765   ;; Make sure the connection to the server is alive.
4766   (unless (gnus-server-opened
4767            (gnus-find-method-for-group gnus-newsgroup-name))
4768     (gnus-check-server (gnus-find-method-for-group gnus-newsgroup-name))
4769     (gnus-request-group gnus-newsgroup-name t)))
4770
4771 (defun gnus-request-article-this-buffer (article group)
4772   "Get an article and insert it into this buffer."
4773   (let (do-update-line sparse-header)
4774     (prog1
4775         (save-excursion
4776           (erase-buffer)
4777           (gnus-kill-all-overlays)
4778           (setq group (or group gnus-newsgroup-name))
4779
4780           ;; Using `gnus-request-article' directly will insert the article into
4781           ;; `nntp-server-buffer' - so we'll save some time by not having to
4782           ;; copy it from the server buffer into the article buffer.
4783
4784           ;; We only request an article by message-id when we do not have the
4785           ;; headers for it, so we'll have to get those.
4786           (when (stringp article)
4787             (gnus-read-header article))
4788
4789           ;; If the article number is negative, that means that this article
4790           ;; doesn't belong in this newsgroup (possibly), so we find its
4791           ;; message-id and request it by id instead of number.
4792           (when (and (numberp article)
4793                      gnus-summary-buffer
4794                      (get-buffer gnus-summary-buffer)
4795                      (gnus-buffer-exists-p gnus-summary-buffer))
4796             (save-excursion
4797               (set-buffer gnus-summary-buffer)
4798               (let ((header (gnus-summary-article-header article)))
4799                 (when (< article 0)
4800                   (cond
4801                    ((memq article gnus-newsgroup-sparse)
4802                     ;; This is a sparse gap article.
4803                     (setq do-update-line article)
4804                     (setq article (mail-header-id header))
4805                     (setq sparse-header (gnus-read-header article))
4806                     (setq gnus-newsgroup-sparse
4807                           (delq article gnus-newsgroup-sparse)))
4808                    ((vectorp header)
4809                     ;; It's a real article.
4810                     (setq article (mail-header-id header)))
4811                    (t
4812                     ;; It is an extracted pseudo-article.
4813                     (setq article 'pseudo)
4814                     (gnus-request-pseudo-article header))))
4815
4816                 (let ((method (gnus-find-method-for-group
4817                                gnus-newsgroup-name)))
4818                   (when (and (eq (car method) 'nneething)
4819                              (vectorp header))
4820                     (let ((dir (expand-file-name
4821                                 (mail-header-subject header)
4822                                 (file-name-as-directory
4823                                  (or (cadr (assq 'nneething-address method))
4824                                      (nth 1 method))))))
4825                       (when (file-directory-p dir)
4826                         (setq article 'nneething)
4827                         (gnus-group-enter-directory dir))))))))
4828
4829           (cond
4830            ;; Refuse to select canceled articles.
4831            ((and (numberp article)
4832                  gnus-summary-buffer
4833                  (get-buffer gnus-summary-buffer)
4834                  (gnus-buffer-exists-p gnus-summary-buffer)
4835                  (eq (cdr (save-excursion
4836                             (set-buffer gnus-summary-buffer)
4837                             (assq article gnus-newsgroup-reads)))
4838                      gnus-canceled-mark))
4839             nil)
4840            ;; Check the backlog.
4841            ((and gnus-keep-backlog
4842                  (gnus-backlog-request-article group article (current-buffer)))
4843             'article)
4844            ;; Check asynchronous pre-fetch.
4845            ((gnus-async-request-fetched-article group article (current-buffer))
4846             (gnus-async-prefetch-next group article gnus-summary-buffer)
4847             (when (and (numberp article) gnus-keep-backlog)
4848               (gnus-backlog-enter-article group article (current-buffer)))
4849             'article)
4850            ;; Check the cache.
4851            ((and gnus-use-cache
4852                  (numberp article)
4853                  (gnus-cache-request-article article group))
4854             'article)
4855            ;; Get the article and put into the article buffer.
4856            ((or (stringp article)
4857                 (numberp article))
4858             (let ((gnus-override-method gnus-override-method)
4859                   (methods (and (stringp article)
4860                                 gnus-refer-article-method))
4861                   result
4862                   (buffer-read-only nil))
4863               (if (or (not (listp methods))
4864                       (and (symbolp (car methods))
4865                            (assq (car methods) nnoo-definition-alist)))
4866                   (setq methods (list methods)))
4867               (when (and (null gnus-override-method)
4868                          methods)
4869                 (setq gnus-override-method (pop methods)))
4870               (while (not result)
4871                 (when (eq gnus-override-method 'current)
4872                   (setq gnus-override-method gnus-current-select-method))
4873                 (erase-buffer)
4874                 (gnus-kill-all-overlays)
4875                 (let ((gnus-newsgroup-name group))
4876                   (gnus-check-group-server))
4877                 (when (gnus-request-article article group (current-buffer))
4878                   (when (numberp article)
4879                     (gnus-async-prefetch-next group article
4880                                               gnus-summary-buffer)
4881                     (when gnus-keep-backlog
4882                       (gnus-backlog-enter-article
4883                        group article (current-buffer))))
4884                   (setq result 'article))
4885                 (if (not result)
4886                     (if methods
4887                         (setq gnus-override-method (pop methods))
4888                       (setq result 'done))))
4889               (and (eq result 'article) 'article)))
4890            ;; It was a pseudo.
4891            (t article)))
4892
4893       ;; Associate this article with the current summary buffer.
4894       (setq gnus-article-current-summary gnus-summary-buffer)
4895
4896       ;; Take the article from the original article buffer
4897       ;; and place it in the buffer it's supposed to be in.
4898       (when (and (get-buffer gnus-article-buffer)
4899                  (equal (buffer-name (current-buffer))
4900                         (buffer-name (get-buffer gnus-article-buffer))))
4901         (save-excursion
4902           (if (get-buffer gnus-original-article-buffer)
4903               (set-buffer gnus-original-article-buffer)
4904             (set-buffer (gnus-get-buffer-create gnus-original-article-buffer))
4905             (set-buffer-multibyte nil)
4906             (buffer-disable-undo)
4907             (setq major-mode 'gnus-original-article-mode)
4908             (setq buffer-read-only t))
4909           (let (buffer-read-only)
4910             (erase-buffer)
4911             (insert-buffer-substring gnus-article-buffer))
4912           (setq gnus-original-article (cons group article)))
4913
4914         ;; Decode charsets.
4915         (run-hooks 'gnus-article-decode-hook)
4916         ;; Mark article as decoded or not.
4917         (setq gnus-article-decoded-p gnus-article-decode-hook))
4918
4919       ;; Update sparse articles.
4920       (when (and do-update-line
4921                  (or (numberp article)
4922                      (stringp article)))
4923         (let ((buf (current-buffer)))
4924           (set-buffer gnus-summary-buffer)
4925           (gnus-summary-update-article do-update-line sparse-header)
4926           (gnus-summary-goto-subject do-update-line nil t)
4927           (set-window-point (get-buffer-window (current-buffer) t)
4928                             (point))
4929           (set-buffer buf))))))
4930
4931 ;;;
4932 ;;; Article editing
4933 ;;;
4934
4935 (defcustom gnus-article-edit-mode-hook nil
4936   "Hook run in article edit mode buffers."
4937   :group 'gnus-article-various
4938   :type 'hook)
4939
4940 (defcustom gnus-article-edit-article-setup-function
4941   'gnus-article-mime-edit-article-setup
4942   "Function called to setup an editing article buffer."
4943   :group 'gnus-article-various
4944   :type 'function)
4945
4946 (defvar gnus-article-edit-done-function nil)
4947
4948 (defvar gnus-article-edit-mode-map nil)
4949
4950 ;; Should we be using derived.el for this?
4951 (unless gnus-article-edit-mode-map
4952   (setq gnus-article-edit-mode-map (make-sparse-keymap))
4953   (set-keymap-parent gnus-article-edit-mode-map text-mode-map)
4954
4955   (gnus-define-keys gnus-article-edit-mode-map
4956     "\C-c\C-c" gnus-article-edit-done
4957     "\C-c\C-k" gnus-article-edit-exit)
4958
4959   (gnus-define-keys (gnus-article-edit-wash-map
4960                      "\C-c\C-w" gnus-article-edit-mode-map)
4961     "f" gnus-article-edit-full-stops))
4962
4963 (define-derived-mode gnus-article-edit-mode text-mode "Article Edit"
4964   "Major mode for editing articles.
4965 This is an extended text-mode.
4966
4967 \\{gnus-article-edit-mode-map}"
4968   (make-local-variable 'gnus-article-edit-done-function)
4969   (make-local-variable 'gnus-prev-winconf)
4970   (set (make-local-variable 'font-lock-defaults)
4971        '(message-font-lock-keywords t))
4972   (setq buffer-read-only nil)
4973   (buffer-enable-undo)
4974   (widen))
4975
4976 (defun gnus-article-edit (&optional force)
4977   "Edit the current article.
4978 This will have permanent effect only in mail groups.
4979 If FORCE is non-nil, allow editing of articles even in read-only
4980 groups."
4981   (interactive "P")
4982   (when (and (not force)
4983              (gnus-group-read-only-p))
4984     (error "The current newsgroup does not support article editing"))
4985   (gnus-article-date-original)
4986   (gnus-article-edit-article
4987    'ignore
4988    `(lambda (no-highlight)
4989       'ignore
4990       (gnus-summary-edit-article-done
4991        ,(or (mail-header-references gnus-current-headers) "")
4992        ,(gnus-group-read-only-p) ,gnus-summary-buffer no-highlight))))
4993
4994 (defun gnus-article-edit-article (start-func exit-func)
4995   "Start editing the contents of the current article buffer."
4996   (let ((winconf (current-window-configuration)))
4997     (set-buffer gnus-article-buffer)
4998     (gnus-article-edit-mode)
4999     (funcall start-func)
5000     (set-buffer-modified-p nil)
5001     (gnus-configure-windows 'edit-article)
5002     (setq gnus-article-edit-done-function exit-func)
5003     (setq gnus-prev-winconf winconf)
5004     (when gnus-article-edit-article-setup-function
5005       (funcall gnus-article-edit-article-setup-function))
5006     (gnus-message 6 "C-c C-c to end edits; C-c C-k to exit")))
5007
5008 (defun gnus-article-edit-done (&optional arg)
5009   "Update the article edits and exit."
5010   (interactive "P")
5011   (let ((func gnus-article-edit-done-function)
5012         (buf (current-buffer))
5013         (start (window-start)))
5014     (remove-hook 'gnus-article-mode-hook
5015                  'gnus-article-mime-edit-article-unwind)
5016     ;; We remove all text props from the article buffer.
5017     (let ((content
5018            (buffer-substring-no-properties (point-min) (point-max)))
5019           (p (point)))
5020       (erase-buffer)
5021       (insert content)
5022       (let ((winconf gnus-prev-winconf))
5023         (gnus-article-mode)
5024         (set-window-configuration winconf)
5025         ;; Tippy-toe some to make sure that point remains where it was.
5026         (save-current-buffer
5027           (set-buffer buf)
5028           (set-window-start (get-buffer-window (current-buffer)) start)
5029           (goto-char p))))
5030     (save-excursion
5031       (set-buffer buf)
5032       (let ((buffer-read-only nil))
5033         (funcall func arg))
5034       ;; The cache and backlog have to be flushed somewhat.
5035       (when gnus-keep-backlog
5036         (gnus-backlog-remove-article
5037          (car gnus-article-current) (cdr gnus-article-current)))
5038       ;; Flush original article as well.
5039       (save-excursion
5040         (when (get-buffer gnus-original-article-buffer)
5041           (set-buffer gnus-original-article-buffer)
5042           (setq gnus-original-article nil)))
5043       (when gnus-use-cache
5044         (gnus-cache-update-article
5045          (car gnus-article-current) (cdr gnus-article-current))))
5046     (set-buffer buf)
5047     (set-window-start (get-buffer-window buf) start)
5048     (set-window-point (get-buffer-window buf) (point))))
5049
5050 (defun gnus-article-edit-exit ()
5051   "Exit the article editing without updating."
5052   (interactive)
5053   (when (or (not (buffer-modified-p))
5054             (yes-or-no-p "Article modified; kill anyway? "))
5055     (let ((curbuf (current-buffer))
5056           (p (point))
5057           (window-start (window-start)))
5058       (erase-buffer)
5059       (if (gnus-buffer-live-p gnus-original-article-buffer)
5060           (insert-buffer gnus-original-article-buffer))
5061       (let ((winconf gnus-prev-winconf))
5062         (gnus-article-mode)
5063         (set-window-configuration winconf)
5064         ;; Tippy-toe some to make sure that point remains where it was.
5065         (save-current-buffer
5066           (set-buffer curbuf)
5067           (set-window-start (get-buffer-window (current-buffer)) window-start)
5068           (goto-char p))))))
5069
5070 (defun gnus-article-edit-full-stops ()
5071   "Interactively repair spacing at end of sentences."
5072   (interactive)
5073   (save-excursion
5074     (goto-char (point-min))
5075     (search-forward-regexp "^$" nil t)
5076     (let ((case-fold-search nil))
5077       (query-replace-regexp "\\([.!?][])}]* \\)\\([[({A-Z]\\)" "\\1 \\2"))))
5078
5079 ;;;
5080 ;;; Article editing with MIME-Edit
5081 ;;;
5082
5083 (defcustom gnus-article-mime-edit-article-setup-hook nil
5084   "Hook run after setting up a MIME editing article buffer."
5085   :group 'gnus-article-various
5086   :type 'hook)
5087
5088 (defun gnus-article-mime-edit-article-unwind ()
5089   "Unwind `gnus-article-buffer' if article editing was given up."
5090   (remove-hook 'gnus-article-mode-hook 'gnus-article-mime-edit-article-unwind)
5091   (when (featurep 'font-lock)
5092     (setq font-lock-defaults nil)
5093     (font-lock-mode -1))
5094   (when mime-edit-mode-flag
5095     (mime-edit-exit 'nomime 'no-error)
5096     (message "")))
5097
5098 (defun gnus-article-mime-edit-article-setup ()
5099   "Convert current buffer to MIME-Edit buffer and turn on MIME-Edit mode
5100 after replacing with the original article."
5101   (setq gnus-show-mime t)
5102   (setq gnus-article-edit-done-function
5103         `(lambda (&rest args)
5104            (when mime-edit-mode-flag
5105              (let (mime-edit-insert-user-agent-field)
5106                (mime-edit-exit))
5107              (message ""))
5108            (goto-char (point-min))
5109            (let (case-fold-search)
5110              (when (re-search-forward
5111                     (format "^%s$" (regexp-quote mail-header-separator))
5112                     nil t)
5113                (replace-match "")))
5114            (apply ,gnus-article-edit-done-function args)
5115            (insert
5116             (prog1
5117                 (buffer-substring-no-properties (point-min) (point-max))
5118               (set-buffer (get-buffer-create gnus-original-article-buffer))
5119               (erase-buffer)))
5120            (setq gnus-current-headers (gnus-article-make-full-mail-header))
5121            (set-buffer gnus-article-buffer)
5122            (gnus-article-prepare-display)))
5123   (substitute-key-definition 'gnus-article-edit-done
5124                              'gnus-article-mime-edit-done
5125                              gnus-article-edit-mode-map)
5126   (substitute-key-definition 'gnus-article-edit-exit
5127                              'gnus-article-mime-edit-exit
5128                              gnus-article-edit-mode-map)
5129   (erase-buffer)
5130   (insert-buffer gnus-original-article-buffer)
5131   (let ((ofn (symbol-function 'mime-edit-decode-single-part-in-buffer)))
5132     (fset 'mime-edit-decode-single-part-in-buffer
5133           (lambda (&rest args)
5134             (if (let ((content-type (car args)))
5135                   (and (eq 'message (mime-content-type-primary-type
5136                                      content-type))
5137                        (eq 'rfc822 (mime-content-type-subtype content-type))))
5138                 (setcar (cdr args) 'not-decode-text))
5139             (apply ofn args)))
5140     (unwind-protect
5141         (mime-edit-again)
5142       (fset 'mime-edit-decode-single-part-in-buffer ofn)))
5143   (when (featurep 'font-lock)
5144     (set (make-local-variable 'font-lock-defaults)
5145          '(message-font-lock-keywords t))
5146     (font-lock-set-defaults)
5147     (turn-on-font-lock))
5148   (set-buffer-modified-p nil)
5149   (delete-other-windows)
5150   (add-hook 'gnus-article-mode-hook 'gnus-article-mime-edit-article-unwind)
5151   (gnus-run-hooks 'gnus-article-mime-edit-article-setup-hook))
5152
5153 (defun gnus-article-mime-edit-done (&optional arg)
5154   "Update the article MIME edits and exit."
5155   (interactive "P")
5156   (when (featurep 'font-lock)
5157     (setq font-lock-defaults nil)
5158     (font-lock-mode -1))
5159   (gnus-article-edit-done arg))
5160
5161 (defun gnus-article-mime-edit-exit ()
5162   "Exit the article MIME editing without updating."
5163   (interactive)
5164   (when (or (not (buffer-modified-p))
5165             (yes-or-no-p "Article modified; kill anyway? "))
5166     (when (featurep 'font-lock)
5167       (setq font-lock-defaults nil)
5168       (font-lock-mode -1))
5169     (when mime-edit-mode-flag
5170       (let (mime-edit-insert-user-agent-field)
5171         (mime-edit-exit))
5172       (message ""))
5173     (goto-char (point-min))
5174     (let (case-fold-search)
5175       (when (re-search-forward
5176              (format "^%s$" (regexp-quote mail-header-separator)) nil t)
5177         (replace-match "")))
5178     (let ((winconf gnus-prev-winconf))
5179       (insert (prog1
5180                   (buffer-substring-no-properties (point-min) (point-max))
5181                 (set-buffer (get-buffer-create gnus-original-article-buffer))
5182                 (erase-buffer)))
5183       (setq gnus-current-headers (gnus-article-make-full-mail-header))
5184       (set-buffer gnus-article-buffer)
5185       (gnus-article-prepare-display)
5186       (set-window-configuration winconf))))
5187
5188 ;;;
5189 ;;; Article highlights
5190 ;;;
5191
5192 ;; Written by Per Abrahamsen <abraham@iesd.auc.dk>.
5193
5194 ;;; Internal Variables:
5195
5196 (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\\)\\)"
5197   "Regular expression that matches URLs."
5198   :group 'gnus-article-buttons
5199   :type 'regexp)
5200
5201 (defcustom gnus-button-alist
5202   `(("<\\(url:[>\n\t ]*?\\)?\\(nntp\\|news\\):[>\n\t ]*\\([^>\n\t ]*@[^>\n\t ]*\\)>"
5203      0 t gnus-button-handle-news 3)
5204     ("\\b\\(nntp\\|news\\):\\([^>\n\t ]*@[^>)!;:,\n\t ]*\\)" 0 t
5205      gnus-button-handle-news 2)
5206     ("\\(\\b<\\(url:[>\n\t ]*\\)?news:[>\n\t ]*\\(//\\)?\\([^>\n\t ]*\\)>\\)"
5207      1 t
5208      gnus-button-fetch-group 4)
5209     ("\\bnews:\\(//\\)?\\([^'\">\n\t ]+\\)" 0 t gnus-button-fetch-group 2)
5210     ("\\bin\\( +article\\| +message\\)? +\\(<\\([^\n @<>]+@[^\n @<>]+\\)>\\)" 2
5211      t gnus-button-message-id 3)
5212     ("\\(<URL: *\\)mailto: *\\([^> \n\t]+\\)>" 0 t gnus-url-mailto 2)
5213     ("mailto:\\([-a-zA-Z.@_+0-9%=?]+\\)" 0 t gnus-url-mailto 1)
5214     ("\\bmailto:\\([^ \n\t]+\\)" 0 t gnus-url-mailto 1)
5215     ;; This is how URLs _should_ be embedded in text...
5216     ("<URL: *\\([^<>]*\\)>" 0 t gnus-button-embedded-url 1)
5217     ;; Raw URLs.
5218     (,gnus-button-url-regexp 0 t browse-url 0))
5219   "*Alist of regexps matching buttons in article bodies.
5220
5221 Each entry has the form (REGEXP BUTTON FORM CALLBACK PAR...), where
5222 REGEXP: is the string matching text around the button,
5223 BUTTON: is the number of the regexp grouping actually matching the button,
5224 FORM: is a lisp expression which must eval to true for the button to
5225 be added,
5226 CALLBACK: is the function to call when the user push this button, and each
5227 PAR: is a number of a regexp grouping whose text will be passed to CALLBACK.
5228
5229 CALLBACK can also be a variable, in that case the value of that
5230 variable it the real callback function."
5231   :group 'gnus-article-buttons
5232   :type '(repeat (list regexp
5233                        (integer :tag "Button")
5234                        (sexp :tag "Form")
5235                        (function :tag "Callback")
5236                        (repeat :tag "Par"
5237                                :inline t
5238                                (integer :tag "Regexp group")))))
5239
5240 (defcustom gnus-header-button-alist
5241   `(("^\\(References\\|Message-I[Dd]\\):" "<[^>]+>"
5242      0 t gnus-button-message-id 0)
5243     ("^\\(From\\|Reply-To\\):" ": *\\(.+\\)$" 1 t gnus-button-reply 1)
5244     ("^\\(Cc\\|To\\):" "[^ \t\n<>,()\"]+@[^ \t\n<>,()\"]+"
5245      0 t gnus-button-mailto 0)
5246     ("^X-[Uu][Rr][Ll]:" ,gnus-button-url-regexp 0 t browse-url 0)
5247     ("^Subject:" ,gnus-button-url-regexp 0 t browse-url 0)
5248     ("^[^:]+:" ,gnus-button-url-regexp 0 t browse-url 0)
5249     ("^[^:]+:" "\\(<\\(url: \\)?news:\\([^>\n ]*\\)>\\)" 1 t
5250      gnus-button-message-id 3))
5251   "*Alist of headers and regexps to match buttons in article heads.
5252
5253 This alist is very similar to `gnus-button-alist', except that each
5254 alist has an additional HEADER element first in each entry:
5255
5256 \(HEADER REGEXP BUTTON FORM CALLBACK PAR)
5257
5258 HEADER is a regexp to match a header.  For a fuller explanation, see
5259 `gnus-button-alist'."
5260   :group 'gnus-article-buttons
5261   :group 'gnus-article-headers
5262   :type '(repeat (list (regexp :tag "Header")
5263                        regexp
5264                        (integer :tag "Button")
5265                        (sexp :tag "Form")
5266                        (function :tag "Callback")
5267                        (repeat :tag "Par"
5268                                :inline t
5269                                (integer :tag "Regexp group")))))
5270
5271 (defvar gnus-button-regexp nil)
5272 (defvar gnus-button-marker-list nil)
5273 ;; Regexp matching any of the regexps from `gnus-button-alist'.
5274
5275 (defvar gnus-button-last nil)
5276 ;; The value of `gnus-button-alist' when `gnus-button-regexp' was build.
5277
5278 ;;; Commands:
5279
5280 (defun gnus-article-push-button (event)
5281   "Check text under the mouse pointer for a callback function.
5282 If the text under the mouse pointer has a `gnus-callback' property,
5283 call it with the value of the `gnus-data' text property."
5284   (interactive "e")
5285   (set-buffer (window-buffer (posn-window (event-start event))))
5286   (let* ((pos (posn-point (event-start event)))
5287          (data (get-text-property pos 'gnus-data))
5288          (fun (get-text-property pos 'gnus-callback)))
5289     (goto-char pos)
5290     (when fun
5291       (funcall fun data))))
5292
5293 (defun gnus-article-press-button ()
5294   "Check text at point for a callback function.
5295 If the text at point has a `gnus-callback' property,
5296 call it with the value of the `gnus-data' text property."
5297   (interactive)
5298   (let* ((data (get-text-property (point) 'gnus-data))
5299          (fun (get-text-property (point) 'gnus-callback)))
5300     (when fun
5301       (funcall fun data))))
5302
5303 (defun gnus-article-prev-button (n)
5304   "Move point to N buttons backward.
5305 If N is negative, move forward instead."
5306   (interactive "p")
5307   (gnus-article-next-button (- n)))
5308
5309 (defun gnus-article-next-button (n)
5310   "Move point to N buttons forward.
5311 If N is negative, move backward instead."
5312   (interactive "p")
5313   (let ((function (if (< n 0) 'previous-single-property-change
5314                     'next-single-property-change))
5315         (inhibit-point-motion-hooks t)
5316         (backward (< n 0))
5317         (limit (if (< n 0) (point-min) (point-max))))
5318     (setq n (abs n))
5319     (while (and (not (= limit (point)))
5320                 (> n 0))
5321       ;; Skip past the current button.
5322       (when (get-text-property (point) 'gnus-callback)
5323         (goto-char (funcall function (point) 'gnus-callback nil limit)))
5324       ;; Go to the next (or previous) button.
5325       (gnus-goto-char (funcall function (point) 'gnus-callback nil limit))
5326       ;; Put point at the start of the button.
5327       (when (and backward (not (get-text-property (point) 'gnus-callback)))
5328         (goto-char (funcall function (point) 'gnus-callback nil limit)))
5329       ;; Skip past intangible buttons.
5330       (when (get-text-property (point) 'intangible)
5331         (incf n))
5332       (decf n))
5333     (unless (zerop n)
5334       (gnus-message 5 "No more buttons"))
5335     n))
5336
5337 (defun gnus-article-highlight (&optional force)
5338   "Highlight current article.
5339 This function calls `gnus-article-highlight-headers',
5340 `gnus-article-highlight-citation',
5341 `gnus-article-highlight-signature', and `gnus-article-add-buttons' to
5342 do the highlighting.  See the documentation for those functions."
5343   (interactive (list 'force))
5344   (gnus-article-highlight-headers)
5345   (gnus-article-highlight-citation force)
5346   (gnus-article-highlight-signature)
5347   (gnus-article-add-buttons force)
5348   (gnus-article-add-buttons-to-head))
5349
5350 (defun gnus-article-highlight-some (&optional force)
5351   "Highlight current article.
5352 This function calls `gnus-article-highlight-headers',
5353 `gnus-article-highlight-signature', and `gnus-article-add-buttons' to
5354 do the highlighting.  See the documentation for those functions."
5355   (interactive (list 'force))
5356   (gnus-article-highlight-headers)
5357   (gnus-article-highlight-signature)
5358   (gnus-article-add-buttons))
5359
5360 (defun gnus-article-highlight-headers ()
5361   "Highlight article headers as specified by `gnus-header-face-alist'."
5362   (interactive)
5363   (save-excursion
5364     (set-buffer gnus-article-buffer)
5365     (save-restriction
5366       (let ((alist gnus-header-face-alist)
5367             (buffer-read-only nil)
5368             (case-fold-search t)
5369             (inhibit-point-motion-hooks t)
5370             entry regexp header-face field-face from hpoints fpoints)
5371         (article-narrow-to-head)
5372         (while (setq entry (pop alist))
5373           (goto-char (point-min))
5374           (setq regexp (concat "^\\("
5375                                (if (string-equal "" (nth 0 entry))
5376                                    "[^\t ]"
5377                                  (nth 0 entry))
5378                                "\\)")
5379                 header-face (nth 1 entry)
5380                 field-face (nth 2 entry))
5381           (while (and (re-search-forward regexp nil t)
5382                       (not (eobp)))
5383             (beginning-of-line)
5384             (setq from (point))
5385             (unless (search-forward ":" nil t)
5386               (forward-char 1))
5387             (when (and header-face
5388                        (not (memq (point) hpoints)))
5389               (push (point) hpoints)
5390               (gnus-put-text-property from (point) 'face header-face))
5391             (when (and field-face
5392                        (not (memq (setq from (point)) fpoints)))
5393               (push from fpoints)
5394               (if (re-search-forward "^[^ \t]" nil t)
5395                   (forward-char -2)
5396                 (goto-char (point-max)))
5397               (gnus-put-text-property from (point) 'face field-face))))))))
5398
5399 (defun gnus-article-highlight-signature ()
5400   "Highlight the signature in an article.
5401 It does this by highlighting everything after
5402 `gnus-signature-separator' using `gnus-signature-face'."
5403   (interactive)
5404   (when gnus-signature-face
5405     (save-excursion
5406       (set-buffer gnus-article-buffer)
5407       (let ((buffer-read-only nil)
5408             (inhibit-point-motion-hooks t))
5409         (save-restriction
5410           (when (gnus-article-narrow-to-signature)
5411             (gnus-overlay-put (gnus-make-overlay (point-min) (point-max))
5412                               'face gnus-signature-face)))))))
5413
5414 (defun gnus-article-buttonize-signature ()
5415   "Add button to the signature."
5416   (interactive)
5417   (save-excursion
5418     (set-buffer gnus-article-buffer)
5419     (let ((buffer-read-only nil)
5420           (inhibit-point-motion-hooks t))
5421       (when (gnus-article-search-signature)
5422         (gnus-article-add-button (match-beginning 0) (match-end 0)
5423                                  'gnus-signature-toggle
5424                                  (set-marker (make-marker)
5425                                              (1+ (match-end 0))))))))
5426
5427 (defun gnus-button-in-region-p (b e prop)
5428   "Say whether PROP exists in the region."
5429   (text-property-not-all b e prop nil))
5430
5431 (defun gnus-article-add-buttons (&optional force)
5432   "Find external references in the article and make buttons of them.
5433 \"External references\" are things like Message-IDs and URLs, as
5434 specified by `gnus-button-alist'."
5435   (interactive (list 'force))
5436   (save-excursion
5437     (set-buffer gnus-article-buffer)
5438     (let ((buffer-read-only nil)
5439           (inhibit-point-motion-hooks t)
5440           (case-fold-search t)
5441           (alist gnus-button-alist)
5442           beg entry regexp)
5443       ;; Remove all old markers.
5444       (let (marker entry new-list)
5445         (while (setq marker (pop gnus-button-marker-list))
5446           (if (or (< marker (point-min)) (>= marker (point-max)))
5447               (push marker new-list)
5448             (goto-char marker)
5449             (when (setq entry (gnus-button-entry))
5450               (put-text-property (match-beginning (nth 1 entry))
5451                                  (match-end (nth 1 entry))
5452                                  'gnus-callback nil))
5453             (set-marker marker nil)))
5454         (setq gnus-button-marker-list new-list))
5455       ;; We skip the headers.
5456       (article-goto-body)
5457       (setq beg (point))
5458       (while (setq entry (pop alist))
5459         (setq regexp (car entry))
5460         (goto-char beg)
5461         (while (re-search-forward regexp nil t)
5462           (let* ((start (and entry (match-beginning (nth 1 entry))))
5463                  (end (and entry (match-end (nth 1 entry))))
5464                  (from (match-beginning 0)))
5465             (when (and (or (eq t (nth 2 entry))
5466                            (eval (nth 2 entry)))
5467                        (not (gnus-button-in-region-p
5468                              start end 'gnus-callback)))
5469               ;; That optional form returned non-nil, so we add the
5470               ;; button.
5471               (gnus-article-add-button
5472                start end 'gnus-button-push
5473                (car (push (set-marker (make-marker) from)
5474                           gnus-button-marker-list))))))))))
5475
5476 ;; Add buttons to the head of an article.
5477 (defun gnus-article-add-buttons-to-head ()
5478   "Add buttons to the head of the article."
5479   (interactive)
5480   (save-excursion
5481     (set-buffer gnus-article-buffer)
5482     (save-restriction
5483       (let ((buffer-read-only nil)
5484             (inhibit-point-motion-hooks t)
5485             (case-fold-search t)
5486             (alist gnus-header-button-alist)
5487             entry beg end)
5488         (article-narrow-to-head)
5489         (while alist
5490           ;; Each alist entry.
5491           (setq entry (car alist)
5492                 alist (cdr alist))
5493           (goto-char (point-min))
5494           (while (re-search-forward (car entry) nil t)
5495             ;; Each header matching the entry.
5496             (setq beg (match-beginning 0))
5497             (setq end (or (and (re-search-forward "^[^ \t]" nil t)
5498                                (match-beginning 0))
5499                           (point-max)))
5500             (goto-char beg)
5501             (while (re-search-forward (nth 1 entry) end t)
5502               ;; Each match within a header.
5503               (let* ((entry (cdr entry))
5504                      (start (match-beginning (nth 1 entry)))
5505                      (end (match-end (nth 1 entry)))
5506                      (form (nth 2 entry)))
5507                 (goto-char (match-end 0))
5508                 (when (eval form)
5509                   (gnus-article-add-button
5510                    start end (nth 3 entry)
5511                    (buffer-substring (match-beginning (nth 4 entry))
5512                                      (match-end (nth 4 entry)))))))
5513             (goto-char end)))))))
5514
5515 ;;; External functions:
5516
5517 (defun gnus-article-add-button (from to fun &optional data)
5518   "Create a button between FROM and TO with callback FUN and data DATA."
5519   (when gnus-article-button-face
5520     (gnus-overlay-put (gnus-make-overlay from to)
5521                       'face gnus-article-button-face))
5522   (gnus-add-text-properties
5523    from to
5524    (nconc (and gnus-article-mouse-face
5525                (list gnus-mouse-face-prop gnus-article-mouse-face))
5526           (list 'gnus-callback fun)
5527           (and data (list 'gnus-data data))))
5528   (widget-convert-button 'link from to :action 'gnus-widget-press-button
5529                          ;; Quote `:button-keymap' for Mule 2.3
5530                          ;; but it won't work.
5531                          ':button-keymap gnus-widget-button-keymap))
5532
5533 ;;; Internal functions:
5534
5535 (defun gnus-article-set-globals ()
5536   (save-excursion
5537     (set-buffer gnus-summary-buffer)
5538     (gnus-set-global-variables)))
5539
5540 (defun gnus-signature-toggle (end)
5541   (save-excursion
5542     (set-buffer gnus-article-buffer)
5543     (let ((buffer-read-only nil)
5544           (inhibit-point-motion-hooks t)
5545           (limit (next-single-property-change end 'mime-view-entity
5546                                               nil (point-max))))
5547       (if (text-property-any end limit 'article-type 'signature)
5548           (gnus-remove-text-properties-when
5549            'article-type 'signature end limit
5550            (cons 'article-type (cons 'signature
5551                                      gnus-hidden-properties)))
5552         (gnus-add-text-properties-when
5553          'article-type nil end limit
5554          (cons 'article-type (cons 'signature
5555                                    gnus-hidden-properties)))))))
5556
5557 (defun gnus-button-entry ()
5558   ;; Return the first entry in `gnus-button-alist' matching this place.
5559   (let ((alist gnus-button-alist)
5560         (entry nil))
5561     (while alist
5562       (setq entry (pop alist))
5563       (if (looking-at (car entry))
5564           (setq alist nil)
5565         (setq entry nil)))
5566     entry))
5567
5568 (defun gnus-button-push (marker)
5569   ;; Push button starting at MARKER.
5570   (save-excursion
5571     (goto-char marker)
5572     (let* ((entry (gnus-button-entry))
5573            (inhibit-point-motion-hooks t)
5574            (fun (nth 3 entry))
5575            (args (mapcar (lambda (group)
5576                            (let ((string (match-string group)))
5577                              (gnus-set-text-properties
5578                               0 (length string) nil string)
5579                              string))
5580                          (nthcdr 4 entry))))
5581       (cond
5582        ((fboundp fun)
5583         (apply fun args))
5584        ((and (boundp fun)
5585              (fboundp (symbol-value fun)))
5586         (apply (symbol-value fun) args))
5587        (t
5588         (gnus-message 1 "You must define `%S' to use this button"
5589                       (cons fun args)))))))
5590
5591 (defun gnus-parse-news-url (url)
5592   (let (scheme server group message-id articles)
5593     (with-temp-buffer
5594       (insert url)
5595       (goto-char (point-min))
5596       (when (looking-at "\\([A-Za-z]+\\):")
5597         (setq scheme (match-string 1))
5598         (goto-char (match-end 0)))
5599       (when (looking-at "//\\([^/]+\\)/")
5600         (setq server (match-string 1))
5601         (goto-char (match-end 0)))
5602         
5603       (cond
5604        ((looking-at "\\(.*@.*\\)")
5605         (setq message-id (match-string 1)))
5606        ((looking-at "\\([^/]+\\)/\\([-0-9]+\\)")
5607         (setq group (match-string 1)
5608               articles (split-string (match-string 2) "-")))
5609        ((looking-at "\\([^/]+\\)/?")
5610         (setq group (match-string 1)))
5611        (t
5612         (error "Unknown news URL syntax"))))
5613     (list scheme server group message-id articles)))
5614
5615 (defun gnus-button-handle-news (url)
5616   "Fetch a news URL."
5617   (destructuring-bind (scheme server group message-id articles)
5618       (gnus-parse-news-url url)
5619     (cond
5620      (message-id
5621       (save-excursion
5622         (set-buffer gnus-summary-buffer)
5623         (if server
5624             (let ((gnus-refer-article-method (list (list 'nntp server))))
5625               (gnus-summary-refer-article message-id))
5626           (gnus-summary-refer-article message-id))))
5627      (group
5628       (gnus-button-fetch-group url)))))
5629
5630 (defun gnus-button-message-id (message-id)
5631   "Fetch MESSAGE-ID."
5632   (save-excursion
5633     (set-buffer gnus-summary-buffer)
5634     (gnus-summary-refer-article message-id)))
5635
5636 (defun gnus-button-fetch-group (address)
5637   "Fetch GROUP specified by ADDRESS."
5638   (if (not (string-match "[:/]" address))
5639       ;; This is just a simple group url.
5640       (gnus-group-read-ephemeral-group address gnus-select-method)
5641     (if (not (string-match "^\\([^:/]+\\)\\(:\\([^/]+\\)/\\)?\\(.*\\)$"
5642                            address))
5643         (error "Can't parse %s" address)
5644       (gnus-group-read-ephemeral-group
5645        (match-string 4 address)
5646        `(nntp ,(match-string 1 address)
5647               (nntp-address ,(match-string 1 address))
5648               (nntp-port-number ,(if (match-end 3)
5649                                      (match-string 3 address)
5650                                    "nntp")))))))
5651
5652 (defun gnus-url-parse-query-string (query &optional downcase)
5653   (let (retval pairs cur key val)
5654     (setq pairs (split-string query "&"))
5655     (while pairs
5656       (setq cur (car pairs)
5657             pairs (cdr pairs))
5658       (if (not (string-match "=" cur))
5659           nil                           ; Grace
5660         (setq key (gnus-url-unhex-string (substring cur 0 (match-beginning 0)))
5661               val (gnus-url-unhex-string (substring cur (match-end 0) nil)))
5662         (if downcase
5663             (setq key (downcase key)))
5664         (setq cur (assoc key retval))
5665         (if cur
5666             (setcdr cur (cons val (cdr cur)))
5667           (setq retval (cons (list key val) retval)))))
5668     retval))
5669
5670 (defun gnus-url-unhex (x)
5671   (if (> x ?9)
5672       (if (>= x ?a)
5673           (+ 10 (- x ?a))
5674         (+ 10 (- x ?A)))
5675     (- x ?0)))
5676
5677 (defun gnus-url-unhex-string (str &optional allow-newlines)
5678   "Remove %XXX embedded spaces, etc in a url.
5679 If optional second argument ALLOW-NEWLINES is non-nil, then allow the
5680 decoding of carriage returns and line feeds in the string, which is normally
5681 forbidden in URL encoding."
5682   (setq str (or str ""))
5683   (let ((tmp "")
5684         (case-fold-search t))
5685     (while (string-match "%[0-9a-f][0-9a-f]" str)
5686       (let* ((start (match-beginning 0))
5687              (ch1 (gnus-url-unhex (elt str (+ start 1))))
5688              (code (+ (* 16 ch1)
5689                       (gnus-url-unhex (elt str (+ start 2))))))
5690         (setq tmp (concat
5691                    tmp (substring str 0 start)
5692                    (cond
5693                     (allow-newlines
5694                      (char-to-string code))
5695                     ((or (= code ?\n) (= code ?\r))
5696                      " ")
5697                     (t (char-to-string code))))
5698               str (substring str (match-end 0)))))
5699     (setq tmp (concat tmp str))
5700     tmp))
5701
5702 (defun gnus-url-mailto (url)
5703   ;; Send mail to someone
5704   (when (string-match "mailto:/*\\(.*\\)" url)
5705     (setq url (substring url (match-beginning 1) nil)))
5706   (let (to args subject func)
5707     (if (string-match (regexp-quote "?") url)
5708         (setq to (gnus-url-unhex-string (substring url 0 (match-beginning 0)))
5709               args (gnus-url-parse-query-string
5710                     (substring url (match-end 0) nil) t))
5711       (setq to (gnus-url-unhex-string url)))
5712     (setq args (cons (list "to" to) args)
5713           subject (cdr-safe (assoc "subject" args)))
5714     (gnus-msg-mail)
5715     (while args
5716       (setq func (intern-soft (concat "message-goto-" (downcase (caar args)))))
5717       (if (fboundp func)
5718           (funcall func)
5719         (message-position-on-field (caar args)))
5720       (insert (mapconcat 'identity (cdar args) ", "))
5721       (setq args (cdr args)))
5722     (if subject
5723         (message-goto-body)
5724       (message-goto-subject))))
5725
5726 (defun gnus-button-embedded-url (address)
5727   "Activate ADDRESS with `browse-url'."
5728   (browse-url (gnus-strip-whitespace address)))
5729
5730 (eval-when-compile
5731   ;; Silence the byte-compiler.
5732   (autoload 'smiley-toggle-buffer "gnus-bitmap"))
5733 (defun gnus-article-smiley-display ()
5734   "Display \"smileys\" as small graphical icons."
5735   (smiley-toggle-buffer 1 (current-buffer) (point-min) (point-max)))
5736
5737 ;;; Next/prev buttons in the article buffer.
5738
5739 (defvar gnus-next-page-line-format "%{%(Next page...%)%}\n")
5740 (defvar gnus-prev-page-line-format "%{%(Previous page...%)%}\n")
5741
5742 (defvar gnus-prev-page-map nil)
5743 (unless gnus-prev-page-map
5744   (setq gnus-prev-page-map (make-sparse-keymap))
5745   (define-key gnus-prev-page-map gnus-mouse-2 'gnus-button-prev-page)
5746   (define-key gnus-prev-page-map "\r" 'gnus-button-prev-page))
5747
5748 (static-if (featurep 'xemacs)
5749     (defun gnus-insert-prev-page-button ()
5750       (let ((buffer-read-only nil))
5751         (gnus-eval-format
5752          gnus-prev-page-line-format nil
5753          `(gnus-prev t local-map ,gnus-prev-page-map
5754                      gnus-callback gnus-article-button-prev-page
5755                      article-type annotation))))
5756   (defun gnus-insert-prev-page-button ()
5757     (let ((buffer-read-only nil)
5758           (situation (get-text-property (point-min) 'mime-view-situation)))
5759       (set-keymap-parent gnus-prev-page-map (current-local-map))
5760       (gnus-eval-format
5761        gnus-prev-page-line-format nil
5762        `(gnus-prev t local-map ,gnus-prev-page-map
5763                    gnus-callback gnus-article-button-prev-page
5764                    article-type annotation
5765                    mime-view-situation ,situation))))
5766   )
5767
5768 (defvar gnus-next-page-map nil)
5769 (unless gnus-next-page-map
5770   (setq gnus-next-page-map (make-sparse-keymap))
5771   (define-key gnus-next-page-map gnus-mouse-2 'gnus-button-next-page)
5772   (define-key gnus-next-page-map "\r" 'gnus-button-next-page))
5773
5774 (defun gnus-button-next-page ()
5775   "Go to the next page."
5776   (interactive)
5777   (let ((win (selected-window)))
5778     (select-window (get-buffer-window gnus-article-buffer t))
5779     (gnus-article-next-page)
5780     (select-window win)))
5781
5782 (defun gnus-button-prev-page ()
5783   "Go to the prev page."
5784   (interactive)
5785   (let ((win (selected-window)))
5786     (select-window (get-buffer-window gnus-article-buffer t))
5787     (gnus-article-prev-page)
5788     (select-window win)))
5789
5790 (static-if (featurep 'xemacs)
5791     (defun gnus-insert-next-page-button ()
5792       (let ((buffer-read-only nil))
5793         (gnus-eval-format gnus-next-page-line-format nil
5794                           `(gnus-next
5795                             t local-map ,gnus-next-page-map
5796                             gnus-callback gnus-article-button-next-page
5797                             article-type annotation))))
5798   (defun gnus-insert-next-page-button ()
5799     (let ((buffer-read-only nil)
5800           (situation (get-text-property (point-min) 'mime-view-situation)))
5801       (set-keymap-parent gnus-next-page-map (current-local-map))
5802       (gnus-eval-format gnus-next-page-line-format nil
5803                         `(gnus-next
5804                           t local-map ,gnus-next-page-map
5805                           gnus-callback gnus-article-button-next-page
5806                           article-type annotation
5807                           mime-view-situation ,situation))))
5808   )
5809
5810 (defun gnus-article-button-next-page (arg)
5811   "Go to the next page."
5812   (interactive "P")
5813   (let ((win (selected-window)))
5814     (select-window (get-buffer-window gnus-article-buffer t))
5815     (gnus-article-next-page)
5816     (select-window win)))
5817
5818 (defun gnus-article-button-prev-page (arg)
5819   "Go to the prev page."
5820   (interactive "P")
5821   (let ((win (selected-window)))
5822     (select-window (get-buffer-window gnus-article-buffer t))
5823     (gnus-article-prev-page)
5824     (select-window win)))
5825
5826 (defvar gnus-decode-header-methods
5827   '(mail-decode-encoded-word-region)
5828   "List of methods used to decode headers.
5829
5830 This variable is a list of FUNCTION or (REGEXP . FUNCTION).  If item
5831 is FUNCTION, FUNCTION will be apply to all newsgroups.  If item is a
5832 (REGEXP . FUNCTION), FUNCTION will be only apply to thes newsgroups
5833 whose names match REGEXP.
5834
5835 For example:
5836 ((\"chinese\" . gnus-decode-encoded-word-region-by-guess)
5837  mail-decode-encoded-word-region
5838  (\"chinese\" . rfc1843-decode-region))
5839 ")
5840
5841 (defvar gnus-decode-header-methods-cache nil)
5842
5843 (defun gnus-multi-decode-header (start end)
5844   "Apply the functions from `gnus-encoded-word-methods' that match."
5845   (unless (and gnus-decode-header-methods-cache
5846                (eq gnus-newsgroup-name
5847                    (car gnus-decode-header-methods-cache)))
5848     (setq gnus-decode-header-methods-cache (list gnus-newsgroup-name))
5849     (mapcar (lambda (x)
5850               (if (symbolp x)
5851                   (nconc gnus-decode-header-methods-cache (list x))
5852                 (if (and gnus-newsgroup-name
5853                          (string-match (car x) gnus-newsgroup-name))
5854                     (nconc gnus-decode-header-methods-cache
5855                            (list (cdr x))))))
5856           gnus-decode-header-methods))
5857   (let ((xlist gnus-decode-header-methods-cache))
5858     (pop xlist)
5859     (save-restriction
5860       (narrow-to-region start end)
5861       (while xlist
5862         (funcall (pop xlist) (point-min) (point-max))))))
5863
5864 ;;;
5865 ;;; Treatment top-level handling.
5866 ;;;
5867
5868 (defun gnus-treat-article (condition &optional part-number total-parts type)
5869   (let ((length (- (point-max) (point-min)))
5870         (alist gnus-treatment-function-alist)
5871         (article-goto-body-goes-to-point-min-p t)
5872         (treated-type
5873          (or (not type)
5874              (catch 'found
5875                (let ((list gnus-article-treat-types))
5876                  (while list
5877                    (when (string-match (pop list) type)
5878                      (throw 'found t)))))))
5879         (highlightp (gnus-visual-p 'article-highlight 'highlight))
5880         (entity (static-unless (featurep 'xemacs)
5881                   (when (eq 'head condition)
5882                     (get-text-property (point-min) 'mime-view-entity))))
5883         val elem buttonized)
5884     (gnus-run-hooks 'gnus-part-display-hook)
5885     (unless gnus-inhibit-treatment
5886       (while (setq elem (pop alist))
5887         (setq val
5888               (save-excursion
5889                 (if (gnus-buffer-live-p gnus-summary-buffer)
5890                     (set-buffer gnus-summary-buffer))
5891                 (symbol-value (car elem))))
5892         (when (and (or (consp val)
5893                        treated-type)
5894                    (gnus-treat-predicate val)
5895                    (or (not (get (car elem) 'highlight))
5896                        highlightp))
5897           (when (and (not buttonized)
5898                      (memq (car elem)
5899                            '(gnus-treat-hide-signature
5900                              gnus-treat-highlight-signature)))
5901             (gnus-article-buttonize-signature)
5902             (setq buttonized t))
5903           (save-restriction
5904             (funcall (cadr elem)))))
5905       ;; FSF Emacsen does not inherit the existing text properties
5906       ;; in the new text, so we should do it for `mime-view-entity'.
5907       (static-unless (featurep 'xemacs)
5908         (when entity
5909           (put-text-property (point-min) (point-max)
5910                              'mime-view-entity entity))))))
5911
5912 ;; Dynamic variables.
5913 (eval-when-compile
5914   (defvar part-number)
5915   (defvar total-parts)
5916   (defvar type)
5917   (defvar condition)
5918   (defvar length))
5919
5920 (defun gnus-treat-predicate (val)
5921   (cond
5922    ((null val)
5923     nil)
5924    ((and (listp val)
5925          (stringp (car val)))
5926     (apply 'gnus-or (mapcar `(lambda (s)
5927                                (string-match s ,(or gnus-newsgroup-name "")))
5928                             val)))
5929    ((listp val)
5930     (let ((pred (pop val)))
5931       (cond
5932        ((eq pred 'or)
5933         (apply 'gnus-or (mapcar 'gnus-treat-predicate val)))
5934        ((eq pred 'and)
5935         (apply 'gnus-and (mapcar 'gnus-treat-predicate val)))
5936        ((eq pred 'not)
5937         (not (gnus-treat-predicate (car val))))
5938        ((eq pred 'typep)
5939         (equal (car val) type))
5940        (t
5941         (error "%S is not a valid predicate" pred)))))
5942    ((eq val 'mime)
5943     gnus-show-mime)
5944    (condition
5945     (eq condition val))
5946    ((eq val t)
5947     t)
5948    ((eq val 'head)
5949     nil)
5950    ((eq val 'last)
5951     (eq part-number total-parts))
5952    ((numberp val)
5953     (< length val))
5954    (t
5955     (error "%S is not a valid value" val))))
5956
5957 (defun gnus-article-encrypt-body (protocol &optional n)
5958   "Encrypt the article body."
5959   (interactive
5960    (list
5961     (or gnus-article-encrypt-protocol
5962         (completing-read "Encrypt protocol: "
5963                          gnus-article-encrypt-protocol-alist
5964                          nil t))
5965     current-prefix-arg))
5966   (let ((func (cdr (assoc protocol gnus-article-encrypt-protocol-alist))))
5967     (unless func
5968       (error (format "Can't find the encrypt protocol %s" protocol)))
5969     (if (equal gnus-newsgroup-name "nndraft:drafts")
5970         (error "Can't encrypt the article in group nndraft:drafts"))
5971     (if (equal gnus-newsgroup-name "nndraft:queue")
5972         (error "Don't encrypt the article in group nndraft:queue"))
5973     (gnus-summary-iterate n
5974       (save-excursion
5975         (set-buffer gnus-summary-buffer)
5976         (let ((mail-parse-charset gnus-newsgroup-charset)
5977               (mail-parse-ignored-charsets gnus-newsgroup-ignored-charsets)
5978               (summary-buffer gnus-summary-buffer)
5979               references point)
5980           (gnus-set-global-variables)
5981           (when (gnus-group-read-only-p)
5982             (error "The current newsgroup does not support article encrypt"))
5983           (gnus-summary-show-article t)
5984           (setq references
5985               (or (mail-header-references gnus-current-headers) ""))
5986           (set-buffer gnus-article-buffer)
5987           (let* ((buffer-read-only nil)
5988                  (headers
5989                   (mapcar (lambda (field)
5990                             (and (save-restriction
5991                                    (message-narrow-to-head)
5992                                    (goto-char (point-min))
5993                                    (search-forward field nil t))
5994                                  (prog2
5995                                      (message-narrow-to-field)
5996                                      (buffer-substring (point-min) (point-max))
5997                                    (delete-region (point-min) (point-max))
5998                                    (widen))))
5999                           '("Content-Type:" "Content-Transfer-Encoding:"
6000                             "Content-Disposition:"))))
6001             (message-narrow-to-head)
6002             (message-remove-header "MIME-Version")
6003             (goto-char (point-max))
6004             (setq point (point))
6005             (insert (apply 'concat headers))
6006             (widen)
6007             (narrow-to-region point (point-max))
6008             (let ((message-options message-options))
6009               (message-options-set 'message-sender user-mail-address)
6010               (message-options-set 'message-recipients user-mail-address)
6011               (message-options-set 'message-sign-encrypt 'not)
6012               (funcall func))
6013             (goto-char (point-min))
6014             (insert "MIME-Version: 1.0\n")
6015             (widen)
6016             (gnus-summary-edit-article-done
6017              references nil summary-buffer t))
6018           (when gnus-keep-backlog
6019             (gnus-backlog-remove-article
6020              (car gnus-article-current) (cdr gnus-article-current)))
6021           (save-excursion
6022             (when (get-buffer gnus-original-article-buffer)
6023               (set-buffer gnus-original-article-buffer)
6024               (setq gnus-original-article nil)))
6025           (when gnus-use-cache
6026             (gnus-cache-update-article
6027              (car gnus-article-current) (cdr gnus-article-current))))))))
6028
6029 (defvar gnus-mime-security-button-line-format "%{%([[%t:%i]%D]%)%}\n"
6030   "The following specs can be used:
6031 %t  The security MIME type
6032 %i  Additional info
6033 %d  Details
6034 %D  Details if button is pressed")
6035
6036 (defvar gnus-mime-security-button-end-line-format "%{%([[End of %t]%D]%)%}\n"
6037   "The following specs can be used:
6038 %t  The security MIME type
6039 %i  Additional info
6040 %d  Details
6041 %D  Details if button is pressed")
6042
6043 (defvar gnus-mime-security-button-line-format-alist
6044   '((?t gnus-tmp-type ?s)
6045     (?i gnus-tmp-info ?s)
6046     (?d gnus-tmp-details ?s)
6047     (?D gnus-tmp-pressed-details ?s)))
6048
6049 (defvar gnus-mime-security-button-map
6050   (let ((map (make-sparse-keymap)))
6051     (unless (>= (string-to-number emacs-version) 21)
6052       (set-keymap-parent map gnus-article-mode-map))
6053     (define-key map gnus-mouse-2 'gnus-article-push-button)
6054     (define-key map "\r" 'gnus-article-press-button)
6055     map))
6056
6057 (defvar gnus-mime-security-details-buffer nil)
6058
6059 (defvar gnus-mime-security-button-pressed nil)
6060
6061 (defvar gnus-mime-security-show-details-inline t
6062   "If non-nil, show details in the article buffer.")
6063
6064 (defun gnus-mime-security-verify-or-decrypt (handle)
6065   (mm-remove-parts (cdr handle))
6066   (let ((region (mm-handle-multipart-ctl-parameter handle 'gnus-region))
6067         buffer-read-only)
6068     (when region
6069       (delete-region (car region) (cdr region))
6070       (set-marker (car region) nil)
6071       (set-marker (cdr region) nil)))
6072   (with-current-buffer (mm-handle-multipart-original-buffer handle)
6073     (let* ((mm-verify-option 'known)
6074            (mm-decrypt-option 'known)
6075            (nparts (mm-possibly-verify-or-decrypt (cdr handle) handle)))
6076       (unless (eq nparts (cdr handle))
6077         (mm-destroy-parts (cdr handle))
6078         (setcdr handle nparts))))
6079   (let ((point (point))
6080         buffer-read-only)
6081     (gnus-mime-display-security handle)
6082     (goto-char point)))
6083
6084 (defun gnus-mime-security-show-details (handle)
6085   (let ((details (mm-handle-multipart-ctl-parameter handle 'gnus-details)))
6086     (if details
6087         (if gnus-mime-security-show-details-inline
6088             (let ((gnus-mime-security-button-pressed t)
6089                   (gnus-mime-security-button-line-format
6090                    (get-text-property (point) 'gnus-line-format))
6091                 buffer-read-only)
6092               (forward-char -1)
6093               (while (eq (get-text-property (point) 'gnus-line-format)
6094                          gnus-mime-security-button-line-format)
6095                 (forward-char -1))
6096               (forward-char)
6097               (delete-region (point)
6098                              (or (text-property-not-all
6099                                   (point) (point-max)
6100                                 'gnus-line-format
6101                                 gnus-mime-security-button-line-format)
6102                                  (point-max)))
6103               (gnus-insert-mime-security-button handle))
6104           (if (gnus-buffer-live-p gnus-mime-security-details-buffer)
6105               (with-current-buffer gnus-mime-security-details-buffer
6106                 (erase-buffer)
6107                 t)
6108             (setq gnus-mime-security-details-buffer
6109                   (gnus-get-buffer-create "*MIME Security Details*")))
6110           (with-current-buffer gnus-mime-security-details-buffer
6111             (insert details)
6112             (goto-char (point-min)))
6113           (pop-to-buffer gnus-mime-security-details-buffer))
6114       (gnus-message 5 "No details."))))
6115
6116 (defun gnus-mime-security-press-button (handle)
6117   (if (mm-handle-multipart-ctl-parameter handle 'gnus-info)
6118       (gnus-mime-security-show-details handle)
6119     (gnus-mime-security-verify-or-decrypt handle)))
6120
6121 (defun gnus-insert-mime-security-button (handle &optional displayed)
6122   (let* ((protocol (mm-handle-multipart-ctl-parameter handle 'protocol))
6123          (gnus-tmp-type
6124           (concat
6125            (or (nth 2 (assoc protocol mm-verify-function-alist))
6126                (nth 2 (assoc protocol mm-decrypt-function-alist))
6127                "Unknown")
6128            (if (equal (car handle) "multipart/signed")
6129                " Signed" " Encrypted")
6130            " Part"))
6131          (gnus-tmp-info
6132           (or (mm-handle-multipart-ctl-parameter handle 'gnus-info)
6133               "Undecided"))
6134          (gnus-tmp-details
6135           (mm-handle-multipart-ctl-parameter handle 'gnus-details))
6136          gnus-tmp-pressed-details
6137          b e)
6138     (setq gnus-tmp-details
6139           (if gnus-tmp-details
6140               (concat "\n" gnus-tmp-details) ""))
6141     (setq gnus-tmp-pressed-details
6142           (if gnus-mime-security-button-pressed gnus-tmp-details ""))
6143     (unless (bolp)
6144       (insert "\n"))
6145     (setq b (point))
6146     (gnus-eval-format
6147      gnus-mime-security-button-line-format
6148      gnus-mime-security-button-line-format-alist
6149      `(keymap ,gnus-mime-security-button-map
6150               ,@(if (>= (string-to-number emacs-version) 21)
6151                     nil ;; XEmacs doesn't care
6152                   (list 'local-map gnus-mime-security-button-map))
6153               gnus-callback gnus-mime-security-press-button
6154               gnus-line-format ,gnus-mime-security-button-line-format
6155               article-type annotation
6156               gnus-data ,handle))
6157     (setq e (point))
6158     (widget-convert-button
6159      'link b e
6160      :mime-handle handle
6161      :action 'gnus-widget-press-button
6162      :button-keymap gnus-mime-security-button-map
6163      :help-echo
6164      (lambda (widget/window &optional overlay pos)
6165        ;; Needed to properly clear the message due to a bug in
6166        ;; wid-edit (XEmacs only).
6167        (if (boundp 'help-echo-owns-message)
6168            (setq help-echo-owns-message t))
6169        (format
6170         "%S: show detail"
6171         (aref gnus-mouse-2 0))))))
6172
6173 (defun gnus-mime-display-security (handle)
6174   (save-restriction
6175     (narrow-to-region (point) (point))
6176     (gnus-insert-mime-security-button handle)
6177     (gnus-mime-display-mixed (cdr handle))
6178     (unless (bolp)
6179       (insert "\n"))
6180     (let ((gnus-mime-security-button-line-format
6181            gnus-mime-security-button-end-line-format))
6182       (gnus-insert-mime-security-button handle))
6183     (mm-set-handle-multipart-parameter
6184      handle 'gnus-region
6185      (cons (set-marker (make-marker) (point-min))
6186            (set-marker (make-marker) (point-max))))))
6187
6188
6189 ;;; @ for mime-view
6190 ;;;
6191
6192 (defun gnus-article-header-presentation-method (entity situation)
6193   (mime-insert-header entity)
6194   )
6195
6196 (set-alist 'mime-header-presentation-method-alist
6197            'gnus-original-article-mode
6198            #'gnus-article-header-presentation-method)
6199
6200 (defun gnus-mime-preview-quitting-method ()
6201   (mime-preview-kill-buffer)
6202   (delete-other-windows)
6203   (gnus-article-show-summary)
6204   (gnus-summary-select-article gnus-show-all-headers t))
6205
6206 (set-alist 'mime-preview-quitting-method-alist
6207            'gnus-original-article-mode #'gnus-mime-preview-quitting-method)
6208
6209 (set-alist 'mime-preview-following-method-alist
6210            'gnus-original-article-mode #'gnus-following-method)
6211
6212 (set-alist 'mime-preview-over-to-previous-method-alist
6213            'gnus-original-article-mode
6214            (lambda ()
6215              (if (> (point-min) 1)
6216                  (gnus-article-prev-page)
6217                (gnus-article-read-summary-keys
6218                 nil (gnus-character-to-event ?P)))))
6219
6220 (set-alist 'mime-preview-over-to-next-method-alist
6221            'gnus-original-article-mode'
6222            (lambda ()
6223              (if (< (point-max) (buffer-size))
6224                  (gnus-article-next-page)
6225                (gnus-article-read-summary-keys
6226                 nil (gnus-character-to-event ?N)))))
6227
6228
6229 ;;; @ end
6230 ;;;
6231
6232 (gnus-ems-redefine)
6233
6234 (provide 'gnus-art)
6235
6236 (run-hooks 'gnus-art-load-hook)
6237
6238 ;;; gnus-art.el ends here