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