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