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