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