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