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