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