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