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