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