*** empty log message ***
[gnus] / lisp / gnus-art.el
1 ;;; gnus-art.el --- article mode commands for Gnus
2 ;; Copyright (C) 1996 Free Software Foundation, Inc.
3
4 ;; Author: Lars Magne Ingebrigtsen <larsi@ifi.uio.no>
5 ;; Keywords: news
6
7 ;; This file is part of GNU Emacs.
8
9 ;; GNU Emacs is free software; you can redistribute it and/or modify
10 ;; it under the terms of the GNU General Public License as published by
11 ;; the Free Software Foundation; either version 2, or (at your option)
12 ;; any later version.
13
14 ;; GNU Emacs is distributed in the hope that it will be useful,
15 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
16 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 ;; GNU General Public License for more details.
18
19 ;; You should have received a copy of the GNU General Public License
20 ;; along with GNU Emacs; see the file COPYING.  If not, write to the
21 ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
22 ;; Boston, MA 02111-1307, USA.
23
24 ;;; Commentary:
25
26 ;;; Code:
27
28 (require 'gnus-load)
29 (require 'gnus-sum)
30 (require 'article)
31 (require 'gnus-spec)
32 (require 'gnus-int)
33 (require 'browse-url)
34
35 (defvar gnus-article-save-directory gnus-directory
36   "*Name of the directory articles will be saved in (default \"~/News\").")
37
38 (defvar gnus-save-all-headers t
39   "*If non-nil, don't remove any headers before saving.")
40
41 (defvar gnus-prompt-before-saving 'always
42   "*This variable says how much prompting is to be done when saving articles.
43 If it is nil, no prompting will be done, and the articles will be
44 saved to the default files.  If this variable is `always', each and
45 every article that is saved will be preceded by a prompt, even when
46 saving large batches of articles.  If this variable is neither nil not
47 `always', there the user will be prompted once for a file name for
48 each invocation of the saving commands.")
49
50 (defvar gnus-saved-headers gnus-visible-headers
51   "*Headers to keep if `gnus-save-all-headers' is nil.
52 If `gnus-save-all-headers' is non-nil, this variable will be ignored.
53 If that variable is nil, however, all headers that match this regexp
54 will be kept while the rest will be deleted before saving.")
55
56 (defvar gnus-default-article-saver 'gnus-summary-save-in-rmail
57   "*A function to save articles in your favourite format.
58 The function must be interactively callable (in other words, it must
59 be an Emacs command).
60
61 Gnus provides the following functions:
62
63 * gnus-summary-save-in-rmail (Rmail format)
64 * gnus-summary-save-in-mail (Unix mail format)
65 * gnus-summary-save-in-folder (MH folder)
66 * gnus-summary-save-in-file (article format).
67 * gnus-summary-save-in-vm (use VM's folder format).")
68
69 (defvar gnus-rmail-save-name (function gnus-plain-save-name)
70   "*A function generating a file name to save articles in Rmail format.
71 The function is called with NEWSGROUP, HEADERS, and optional LAST-FILE.")
72
73 (defvar gnus-mail-save-name (function gnus-plain-save-name)
74   "*A function generating a file name to save articles in Unix mail format.
75 The function is called with NEWSGROUP, HEADERS, and optional LAST-FILE.")
76
77 (defvar gnus-folder-save-name (function gnus-folder-save-name)
78   "*A function generating a file name to save articles in MH folder.
79 The function is called with NEWSGROUP, HEADERS, and optional LAST-FOLDER.")
80
81 (defvar gnus-file-save-name (function gnus-numeric-save-name)
82   "*A function generating a file name to save articles in article format.
83 The function is called with NEWSGROUP, HEADERS, and optional
84 LAST-FILE.")
85
86 (defvar gnus-split-methods
87   '((gnus-article-archive-name))
88   "*Variable used to suggest where articles are to be saved.
89 For instance, if you would like to save articles related to Gnus in
90 the file \"gnus-stuff\", and articles related to VM in \"vm-stuff\",
91 you could set this variable to something like:
92
93  '((\"^Subject:.*gnus\\|^Newsgroups:.*gnus\" \"gnus-stuff\")
94    (\"^Subject:.*vm\\|^Xref:.*vm\" \"vm-stuff\"))
95
96 This variable is an alist where the where the key is the match and the
97 value is a list of possible files to save in if the match is non-nil.
98
99 If the match is a string, it is used as a regexp match on the
100 article.  If the match is a symbol, that symbol will be funcalled
101 from the buffer of the article to be saved with the newsgroup as the
102 parameter.  If it is a list, it will be evaled in the same buffer.
103
104 If this form or function returns a string, this string will be used as
105 a possible file name; and if it returns a non-nil list, that list will
106 be used as possible file names.")
107
108 (defvar gnus-strict-mime t
109   "*If nil, MIME-decode even if there is no Mime-Version header in the article.")
110
111 (defvar gnus-show-mime-method 'metamail-buffer
112   "*Function to process a MIME message.
113 The function is called from the article buffer.")
114
115 (defvar gnus-decode-encoded-word-method (lambda ())
116   "*Function to decode a MIME encoded-words.
117 The function is called from the article buffer.")
118
119 (defvar gnus-page-delimiter "^\^L"
120   "*Regexp describing what to use as article page delimiters.
121 The default value is \"^\^L\", which is a form linefeed at the
122 beginning of a line.")
123
124 (defvar gnus-article-mode-line-format "Gnus: %%b %S"
125   "*The format specification for the article mode line.
126 See `gnus-summary-mode-line-format' for a closer description.")
127
128 (defvar gnus-article-mode-hook nil
129   "*A hook for Gnus article mode.")
130
131 (defvar gnus-article-menu-hook nil
132   "*Hook run after the creation of the article mode menu.")
133
134 (defvar gnus-article-prepare-hook nil
135   "*A hook called after an article has been prepared in the article buffer.
136 If you want to run a special decoding program like nkf, use this hook.")
137
138 (defvar gnus-article-button-face 'bold
139   "Face used for highlighting buttons in the article buffer.
140
141 An article button is a piece of text that you can activate by pressing
142 `RET' or `mouse-2' above it.")
143
144 (defvar gnus-article-mouse-face 'highlight
145   "Face used for mouse highlighting in the article buffer.
146
147 Article buttons will be displayed in this face when the cursor is
148 above them.")
149
150 (defvar gnus-signature-face 'italic
151   "Face used for highlighting a signature in the article buffer.")
152
153 (defvar gnus-header-face-alist
154   (cond 
155    ((not (eq gnus-display-type 'color))
156     '(("" bold italic)))
157    ((eq gnus-background-mode 'dark)
158     (list 
159      (list "From" nil 
160            (custom-face-lookup "light blue" nil nil t t nil))
161      (list "Subject" nil 
162            (custom-face-lookup "pink" nil nil t t nil))
163      (list "Newsgroups:.*," nil
164            (custom-face-lookup "yellow" nil nil t t nil))
165      (list 
166       "" 
167       (custom-face-lookup "cyan" nil nil t nil nil)
168       (custom-face-lookup "forestgreen" nil nil nil t 
169                           nil))))
170    (t
171     (list
172      (list "From" nil
173            (custom-face-lookup "MidnightBlue" nil nil t t nil))
174      (list "Subject" nil 
175            (custom-face-lookup "firebrick" nil nil t t nil))
176      (list "Newsgroups:.*," nil
177            (custom-face-lookup "indianred" nil nil t t nil))
178      (list ""
179            (custom-face-lookup 
180             "DarkGreen" nil nil t nil nil)
181            (custom-face-lookup "DarkGreen" nil nil
182                                nil t nil)))))
183   "Controls highlighting of article header.
184
185 [ This needs to be rewritten in lisp-talk ]
186
187 Below is a list of article header names, and the faces used for
188 displaying the name and content of the header.  The `Header' field
189 should contain the name of the header.  The field actually contains a
190 regular expression that should match the beginning of the header line,
191 but if you don't know what a regular expression is, just write the
192 name of the header.  The second field is the `Name' field, which
193 determines how the header name (i. e., the part of the header left
194 of the `:') is displayed.  The third field is the `Content' field,
195 which determines how the content (i. e., the part of the header right of
196 the `:') is displayed.  
197
198 If you leave the last `Header' field in the list empty, the `Name' and
199 `Content' fields will determine how headers not listed above are
200 displayed.  
201
202 If you only want to change the display of the name part for a specific
203 header, specify `None' in the `Content' field.  Similarly, specify
204 `None' in the `Name' field if you only want to leave the name part
205 alone.")
206
207
208 ;;; Internal variables
209
210 (defvar gnus-article-mode-line-format-alist
211     (nconc '((?w (gnus-article-wash-status) ?s))
212            gnus-summary-mode-line-format-alist))
213
214 (defvar gnus-number-of-articles-to-be-saved nil)
215
216 ;;; Provide a mapping from `gnus-*' commands to Article commands.
217
218 (eval-and-compile
219   (mapcar
220    (lambda (func)
221      (let (afunc gfunc)
222        (if (consp func)
223            (setq afunc (car func)
224                  gfunc (cdr func))
225          (setq afunc func
226                gfunc (intern (format "gnus-%s" func))))
227        (fset gfunc 
228              `(lambda (&optional interactive &rest args)
229                 ,(documentation afunc t)
230                 (interactive (list t))
231                 (save-excursion
232                   (set-buffer gnus-article-buffer)
233                   (if interactive
234                       (call-interactively ',afunc)
235                     (apply ',afunc args)))))))
236    '(article-hide-headers
237      article-hide-boring-headers
238      article-treat-overstrike
239      (article-fill . gnus-article-word-wrap)
240      article-remove-cr
241      article-display-x-face
242      article-de-quoted-unreadable
243      article-mime-decode-quoted-printable
244      article-hide-pgp
245      article-hide-pem
246      article-hide-signature
247      article-remove-trailing-blank-lines
248      article-strip-leading-blank-lines
249      article-strip-multiple-blank-lines
250      article-strip-blank-lines
251      article-date-local
252      article-date-original
253      article-date-lapsed
254      article-emphasize
255      (article-show-all . gnus-article-show-all-headers))))
256
257 (defalias 'gnus-decode-rfc1522 'article-decode-rfc1522)
258
259 ;;; Saving functions.
260
261 (defun gnus-article-save (save-buffer file &optional num)
262   "Save the currently selected article."
263   (unless gnus-save-all-headers
264     ;; Remove headers according to `gnus-saved-headers'.
265     (let ((gnus-visible-headers
266            (or gnus-saved-headers gnus-visible-headers))
267           (gnus-article-buffer save-buffer))
268       (gnus-article-hide-headers 1 t)))
269   (save-window-excursion
270     (if (not gnus-default-article-saver)
271         (error "No default saver is defined.")
272       ;; !!! Magic!  The saving functions all save
273       ;; `gnus-original-article-buffer' (or so they think),
274       ;; but we bind that variable to our save-buffer.
275       (set-buffer gnus-article-buffer)
276       (let* ((gnus-original-article-buffer save-buffer)
277              (filename
278               (cond
279                ((not gnus-prompt-before-saving)
280                 'default)
281                ((eq gnus-prompt-before-saving 'always)
282                 nil)
283                (t file)))
284              (gnus-number-of-articles-to-be-saved
285               (when (eq gnus-prompt-before-saving t) num))) ; Magic
286         (set-buffer gnus-summary-buffer)
287         (funcall gnus-default-article-saver filename)))))
288
289 (defun gnus-read-save-file-name (prompt default-name &optional filename)
290   (cond
291    ((eq filename 'default)
292     default-name)
293    (filename filename)
294    (t
295     (let* ((split-name (gnus-get-split-value gnus-split-methods))
296            (prompt
297             (format prompt (if (and gnus-number-of-articles-to-be-saved
298                                     (> gnus-number-of-articles-to-be-saved 1))
299                                (format "these %d articles"
300                                        gnus-number-of-articles-to-be-saved)
301                              "this article")))
302            (file
303             ;; Let the split methods have their say.
304             (cond
305              ;; No split name was found.
306              ((null split-name)
307               (read-file-name
308                (concat prompt " (default "
309                        (file-name-nondirectory default-name) ") ")
310                (file-name-directory default-name)
311                default-name))
312              ;; A single split name was found
313              ((= 1 (length split-name))
314               (let* ((name (car split-name))
315                      (dir (cond ((file-directory-p name)
316                                  (file-name-as-directory name))
317                                 ((file-exists-p name) name)
318                                 (t gnus-article-save-directory))))
319                 (read-file-name
320                  (concat prompt " (default " name ") ")
321                  dir name)))
322              ;; A list of splits was found.
323              (t
324               (setq split-name (nreverse split-name))
325               (let (result)
326                 (let ((file-name-history (nconc split-name file-name-history)))
327                   (setq result
328                         (read-file-name
329                          (concat prompt " (`M-p' for defaults) ")
330                          gnus-article-save-directory
331                          (car split-name))))
332                 (car (push result file-name-history)))))))
333       ;; Create the directory.
334       (gnus-make-directory (file-name-directory file))
335       ;; If we have read a directory, we append the default file name.
336       (when (file-directory-p file)
337         (setq file (concat (file-name-as-directory file)
338                            (file-name-nondirectory default-name))))
339       ;; Possibly translate some characters.
340       (nnheader-translate-file-chars file)))))
341
342 (defun gnus-article-archive-name (group)
343   "Return the first instance of an \"Archive-name\" in the current buffer."
344   (let ((case-fold-search t))
345     (when (re-search-forward "archive-name: *\\([^ \n\t]+\\)[ \t]*$" nil t)
346       (nnheader-concat gnus-article-save-directory
347                        (match-string 1)))))
348
349 (defun gnus-summary-save-in-rmail (&optional filename)
350   "Append this article to Rmail file.
351 Optional argument FILENAME specifies file name.
352 Directory to save to is default to `gnus-article-save-directory'."
353   (interactive)
354   (gnus-set-global-variables)
355   (let ((default-name
356           (funcall gnus-rmail-save-name gnus-newsgroup-name
357                    gnus-current-headers gnus-newsgroup-last-rmail)))
358     (setq filename (gnus-read-save-file-name
359                     "Save %s in rmail file:" default-name filename))
360     (gnus-make-directory (file-name-directory filename))
361     (gnus-eval-in-buffer-window gnus-original-article-buffer
362       (save-excursion
363         (save-restriction
364           (widen)
365           (gnus-output-to-rmail filename))))
366     ;; Remember the directory name to save articles
367     (setq gnus-newsgroup-last-rmail filename)))
368
369 (defun gnus-summary-save-in-mail (&optional filename)
370   "Append this article to Unix mail file.
371 Optional argument FILENAME specifies file name.
372 Directory to save to is default to `gnus-article-save-directory'."
373   (interactive)
374   (gnus-set-global-variables)
375   (let ((default-name
376           (funcall gnus-mail-save-name gnus-newsgroup-name
377                    gnus-current-headers gnus-newsgroup-last-mail)))
378     (setq filename (gnus-read-save-file-name
379                     "Save %s in Unix mail file:" default-name filename))
380     (setq filename
381           (expand-file-name filename
382                             (and default-name
383                                  (file-name-directory default-name))))
384     (gnus-make-directory (file-name-directory filename))
385     (gnus-eval-in-buffer-window gnus-original-article-buffer
386       (save-excursion
387         (save-restriction
388           (widen)
389           (if (and (file-readable-p filename) (mail-file-babyl-p filename))
390               (gnus-output-to-rmail filename)
391             (let ((mail-use-rfc822 t))
392               (rmail-output filename 1 t t))))))
393     ;; Remember the directory name to save articles.
394     (setq gnus-newsgroup-last-mail filename)))
395
396 (defun gnus-summary-save-in-file (&optional filename)
397   "Append this article to file.
398 Optional argument FILENAME specifies file name.
399 Directory to save to is default to `gnus-article-save-directory'."
400   (interactive)
401   (gnus-set-global-variables)
402   (let ((default-name
403           (funcall gnus-file-save-name gnus-newsgroup-name
404                    gnus-current-headers gnus-newsgroup-last-file)))
405     (setq filename (gnus-read-save-file-name
406                     "Save %s in file:" default-name filename))
407     (gnus-make-directory (file-name-directory filename))
408     (gnus-eval-in-buffer-window gnus-original-article-buffer
409       (save-excursion
410         (save-restriction
411           (widen)
412           (gnus-output-to-file filename))))
413     ;; Remember the directory name to save articles.
414     (setq gnus-newsgroup-last-file filename)))
415
416 (defun gnus-summary-save-body-in-file (&optional filename)
417   "Append this article body to a file.
418 Optional argument FILENAME specifies file name.
419 The directory to save in defaults to `gnus-article-save-directory'."
420   (interactive)
421   (gnus-set-global-variables)
422   (let ((default-name
423           (funcall gnus-file-save-name gnus-newsgroup-name
424                    gnus-current-headers gnus-newsgroup-last-file)))
425     (setq filename (gnus-read-save-file-name
426                     "Save %s body in file:" default-name filename))
427     (gnus-make-directory (file-name-directory filename))
428     (gnus-eval-in-buffer-window gnus-original-article-buffer
429       (save-excursion
430         (save-restriction
431           (widen)
432           (goto-char (point-min))
433           (and (search-forward "\n\n" nil t)
434                (narrow-to-region (point) (point-max)))
435           (gnus-output-to-file filename))))
436     ;; Remember the directory name to save articles.
437     (setq gnus-newsgroup-last-file filename)))
438
439 (defun gnus-summary-save-in-pipe (&optional command)
440   "Pipe this article to subprocess."
441   (interactive)
442   (gnus-set-global-variables)
443   (setq command
444         (cond ((eq command 'default)
445                gnus-last-shell-command)
446               (command command)
447               (t (read-string "Shell command on article: "
448                               gnus-last-shell-command))))
449   (if (string-equal command "")
450       (setq command gnus-last-shell-command))
451   (gnus-eval-in-buffer-window gnus-article-buffer
452     (save-restriction
453       (widen)
454       (shell-command-on-region (point-min) (point-max) command nil)))
455   (setq gnus-last-shell-command command))
456
457 ;;; Article file names when saving.
458
459 (defun gnus-capitalize-newsgroup (newsgroup)
460   "Capitalize NEWSGROUP name."
461   (and (not (zerop (length newsgroup)))
462        (concat (char-to-string (upcase (aref newsgroup 0)))
463                (substring newsgroup 1))))
464
465 (defun gnus-Numeric-save-name (newsgroup headers &optional last-file)
466   "Generate file name from NEWSGROUP, HEADERS, and optional LAST-FILE.
467 If variable `gnus-use-long-file-name' is nil, it is ~/News/News.group/num.
468 Otherwise, it is like ~/News/news/group/num."
469   (let ((default
470           (expand-file-name
471            (concat (if (gnus-use-long-file-name 'not-save)
472                        (gnus-capitalize-newsgroup newsgroup)
473                      (gnus-newsgroup-directory-form newsgroup))
474                    "/" (int-to-string (mail-header-number headers)))
475            gnus-article-save-directory)))
476     (if (and last-file
477              (string-equal (file-name-directory default)
478                            (file-name-directory last-file))
479              (string-match "^[0-9]+$" (file-name-nondirectory last-file)))
480         default
481       (or last-file default))))
482
483 (defun gnus-numeric-save-name (newsgroup headers &optional last-file)
484   "Generate file name from NEWSGROUP, HEADERS, and optional LAST-FILE.
485 If variable `gnus-use-long-file-name' is non-nil, it is
486 ~/News/news.group/num.  Otherwise, it is like ~/News/news/group/num."
487   (let ((default
488           (expand-file-name
489            (concat (if (gnus-use-long-file-name 'not-save)
490                        newsgroup
491                      (gnus-newsgroup-directory-form newsgroup))
492                    "/" (int-to-string (mail-header-number headers)))
493            gnus-article-save-directory)))
494     (if (and last-file
495              (string-equal (file-name-directory default)
496                            (file-name-directory last-file))
497              (string-match "^[0-9]+$" (file-name-nondirectory last-file)))
498         default
499       (or last-file default))))
500
501 (defun gnus-Plain-save-name (newsgroup headers &optional last-file)
502   "Generate file name from NEWSGROUP, HEADERS, and optional LAST-FILE.
503 If variable `gnus-use-long-file-name' is non-nil, it is
504 ~/News/News.group.  Otherwise, it is like ~/News/news/group/news."
505   (or last-file
506       (expand-file-name
507        (if (gnus-use-long-file-name 'not-save)
508            (gnus-capitalize-newsgroup newsgroup)
509          (concat (gnus-newsgroup-directory-form newsgroup) "/news"))
510        gnus-article-save-directory)))
511
512 (defun gnus-plain-save-name (newsgroup headers &optional last-file)
513   "Generate file name from NEWSGROUP, HEADERS, and optional LAST-FILE.
514 If variable `gnus-use-long-file-name' is non-nil, it is
515 ~/News/news.group.  Otherwise, it is like ~/News/news/group/news."
516   (or last-file
517       (expand-file-name
518        (if (gnus-use-long-file-name 'not-save)
519            newsgroup
520          (concat (gnus-newsgroup-directory-form newsgroup) "/news"))
521        gnus-article-save-directory)))
522
523 \f
524 ;;;
525 ;;; Gnus article mode
526 ;;;
527
528 (put 'gnus-article-mode 'mode-class 'special)
529
530 (when t
531   (gnus-define-keys gnus-article-mode-map
532     " " gnus-article-goto-next-page
533     "\177" gnus-article-goto-prev-page
534     [delete] gnus-article-goto-prev-page
535     "\C-c^" gnus-article-refer-article
536     "h" gnus-article-show-summary
537     "s" gnus-article-show-summary
538     "\C-c\C-m" gnus-article-mail
539     "?" gnus-article-describe-briefly
540     gnus-mouse-2 gnus-article-push-button
541     "\r" gnus-article-press-button
542     "\t" gnus-article-next-button
543     "\M-\t" gnus-article-prev-button
544     "e" gnus-article-edit
545     "<" beginning-of-buffer
546     ">" end-of-buffer
547     "\C-c\C-i" gnus-info-find-node
548     "\C-c\C-b" gnus-bug
549
550     "\C-d" gnus-article-read-summary-keys
551     "\M-g" gnus-article-read-summary-keys)
552
553   (substitute-key-definition
554    'undefined 'gnus-article-read-summary-keys gnus-article-mode-map))
555
556 (defun gnus-article-make-menu-bar ()
557   (gnus-turn-off-edit-menu 'article)
558   (unless (boundp 'gnus-article-article-menu)
559     (easy-menu-define
560      gnus-article-article-menu gnus-article-mode-map ""
561      '("Article"
562        ["Scroll forwards" gnus-article-goto-next-page t]
563        ["Scroll backwards" gnus-article-goto-prev-page t]
564        ["Show summary" gnus-article-show-summary t]
565        ["Fetch Message-ID at point" gnus-article-refer-article t]
566        ["Mail to address at point" gnus-article-mail t]
567        ))
568
569     (easy-menu-define
570      gnus-article-treatment-menu gnus-article-mode-map ""
571      '("Treatment"
572        ["Hide headers" gnus-article-hide-headers t]
573        ["Hide signature" gnus-article-hide-signature t]
574        ["Hide citation" gnus-article-hide-citation t]
575        ["Treat overstrike" gnus-article-treat-overstrike t]
576        ["Remove carriage return" gnus-article-remove-cr t]
577        ["Remove quoted-unreadable" gnus-article-de-quoted-unreadable t]
578        ))
579     (run-hooks 'gnus-article-menu-hook)))
580
581 (defun gnus-article-mode ()
582   "Major mode for displaying an article.
583
584 All normal editing commands are switched off.
585
586 The following commands are available in addition to all summary mode
587 commands:
588 \\<gnus-article-mode-map>
589 \\[gnus-article-next-page]\t Scroll the article one page forwards
590 \\[gnus-article-prev-page]\t Scroll the article one page backwards
591 \\[gnus-article-refer-article]\t Go to the article referred to by an article id near point
592 \\[gnus-article-show-summary]\t Display the summary buffer
593 \\[gnus-article-mail]\t Send a reply to the address near point
594 \\[gnus-article-describe-briefly]\t Describe the current mode briefly
595 \\[gnus-info-find-node]\t Go to the Gnus info node"
596   (interactive)
597   (when (and menu-bar-mode
598              (gnus-visual-p 'article-menu 'menu))
599     (gnus-article-make-menu-bar))
600   (kill-all-local-variables)
601   (gnus-simplify-mode-line)
602   (setq mode-name "Article")
603   (setq major-mode 'gnus-article-mode)
604   (make-local-variable 'minor-mode-alist)
605   (unless (assq 'gnus-show-mime minor-mode-alist)
606     (push (list 'gnus-show-mime " MIME") minor-mode-alist))
607   (use-local-map gnus-article-mode-map)
608   (gnus-update-format-specifications nil 'article-mode)
609   (set (make-local-variable 'page-delimiter) gnus-page-delimiter)
610   (gnus-set-default-directory)
611   (buffer-disable-undo (current-buffer))
612   (setq buffer-read-only t)
613   (run-hooks 'gnus-article-mode-hook))
614
615 (defun gnus-article-setup-buffer ()
616   "Initialize the article buffer."
617   (let* ((name (if gnus-single-article-buffer "*Article*"
618                  (concat "*Article " gnus-newsgroup-name "*")))
619          (original
620           (progn (string-match "\\*Article" name)
621                  (concat " *Original Article"
622                          (substring name (match-end 0))))))
623     (setq gnus-article-buffer name)
624     (setq gnus-original-article-buffer original)
625     ;; This might be a variable local to the summary buffer.
626     (unless gnus-single-article-buffer
627       (save-excursion
628         (set-buffer gnus-summary-buffer)
629         (setq gnus-article-buffer name)
630         (setq gnus-original-article-buffer original)
631         (gnus-set-global-variables)))
632     ;; Init original article buffer.
633     (save-excursion
634       (set-buffer (get-buffer-create gnus-original-article-buffer))
635       (buffer-disable-undo (current-buffer))
636       (setq major-mode 'gnus-original-article-mode)
637       (gnus-add-current-to-buffer-list)
638       (make-local-variable 'gnus-original-article))
639     (if (get-buffer name)
640         (save-excursion
641           (set-buffer name)
642           (buffer-disable-undo (current-buffer))
643           (setq buffer-read-only t)
644           (gnus-add-current-to-buffer-list)
645           (unless (eq major-mode 'gnus-article-mode)
646             (gnus-article-mode))
647           (current-buffer))
648       (save-excursion
649         (set-buffer (get-buffer-create name))
650         (gnus-add-current-to-buffer-list)
651         (gnus-article-mode)
652         (make-local-variable 'gnus-summary-buffer)
653         (current-buffer)))))
654
655 ;; Set article window start at LINE, where LINE is the number of lines
656 ;; from the head of the article.
657 (defun gnus-article-set-window-start (&optional line)
658   (set-window-start
659    (get-buffer-window gnus-article-buffer t)
660    (save-excursion
661      (set-buffer gnus-article-buffer)
662      (goto-char (point-min))
663      (if (not line)
664          (point-min)
665        (gnus-message 6 "Moved to bookmark")
666        (search-forward "\n\n" nil t)
667        (forward-line line)
668        (point)))))
669
670 (defun gnus-article-prepare (article &optional all-headers header)
671   "Prepare ARTICLE in article mode buffer.
672 ARTICLE should either be an article number or a Message-ID.
673 If ARTICLE is an id, HEADER should be the article headers.
674 If ALL-HEADERS is non-nil, no headers are hidden."
675   (save-excursion
676     ;; Make sure we start in a summary buffer.
677     (unless (eq major-mode 'gnus-summary-mode)
678       (set-buffer gnus-summary-buffer))
679     (setq gnus-summary-buffer (current-buffer))
680     ;; Make sure the connection to the server is alive.
681     (unless (gnus-server-opened
682              (gnus-find-method-for-group gnus-newsgroup-name))
683       (gnus-check-server (gnus-find-method-for-group gnus-newsgroup-name))
684       (gnus-request-group gnus-newsgroup-name t))
685     (let* ((article (if header (mail-header-number header) article))
686            (summary-buffer (current-buffer))
687            (internal-hook gnus-article-internal-prepare-hook)
688            (group gnus-newsgroup-name)
689            result)
690       (save-excursion
691         (gnus-article-setup-buffer)
692         (set-buffer gnus-article-buffer)
693         ;; Deactivate active regions.
694         (when (and (boundp 'transient-mark-mode)
695                    transient-mark-mode)
696           (setq mark-active nil))
697         (if (not (setq result (let ((buffer-read-only nil))
698                                 (gnus-request-article-this-buffer
699                                  article group))))
700             ;; There is no such article.
701             (save-excursion
702               (when (and (numberp article)
703                          (not (memq article gnus-newsgroup-sparse)))
704                 (setq gnus-article-current
705                       (cons gnus-newsgroup-name article))
706                 (set-buffer gnus-summary-buffer)
707                 (setq gnus-current-article article)
708                 (gnus-summary-mark-article article gnus-canceled-mark))
709               (unless (memq article gnus-newsgroup-sparse)
710                 (gnus-error
711                  1 "No such article (may have expired or been canceled)")))
712           (if (or (eq result 'pseudo) (eq result 'nneething))
713               (progn
714                 (save-excursion
715                   (set-buffer summary-buffer)
716                   (setq gnus-last-article gnus-current-article
717                         gnus-newsgroup-history (cons gnus-current-article
718                                                      gnus-newsgroup-history)
719                         gnus-current-article 0
720                         gnus-current-headers nil
721                         gnus-article-current nil)
722                   (if (eq result 'nneething)
723                       (gnus-configure-windows 'summary)
724                     (gnus-configure-windows 'article))
725                   (gnus-set-global-variables))
726                 (gnus-set-mode-line 'article))
727             ;; The result from the `request' was an actual article -
728             ;; or at least some text that is now displayed in the
729             ;; article buffer.
730             (if (and (numberp article)
731                      (not (eq article gnus-current-article)))
732                 ;; Seems like a new article has been selected.
733                 ;; `gnus-current-article' must be an article number.
734                 (save-excursion
735                   (set-buffer summary-buffer)
736                   (setq gnus-last-article gnus-current-article
737                         gnus-newsgroup-history (cons gnus-current-article
738                                                      gnus-newsgroup-history)
739                         gnus-current-article article
740                         gnus-current-headers
741                         (gnus-summary-article-header gnus-current-article)
742                         gnus-article-current
743                         (cons gnus-newsgroup-name gnus-current-article))
744                   (unless (vectorp gnus-current-headers)
745                     (setq gnus-current-headers nil))
746                   (gnus-summary-show-thread)
747                   (run-hooks 'gnus-mark-article-hook)
748                   (gnus-set-mode-line 'summary)
749                   (and (gnus-visual-p 'article-highlight 'highlight)
750                        (run-hooks 'gnus-visual-mark-article-hook))
751                   ;; Set the global newsgroup variables here.
752                   ;; Suggested by Jim Sisolak
753                   ;; <sisolak@trans4.neep.wisc.edu>.
754                   (gnus-set-global-variables)
755                   (setq gnus-have-all-headers
756                         (or all-headers gnus-show-all-headers))
757                   (and gnus-use-cache
758                        (vectorp (gnus-summary-article-header article))
759                        (gnus-cache-possibly-enter-article
760                         group article
761                         (gnus-summary-article-header article)
762                         (memq article gnus-newsgroup-marked)
763                         (memq article gnus-newsgroup-dormant)
764                         (memq article gnus-newsgroup-unreads)))))
765             (when (or (numberp article)
766                       (stringp article))
767               ;; Hooks for getting information from the article.
768               ;; This hook must be called before being narrowed.
769               (let (buffer-read-only)
770                 (run-hooks 'internal-hook)
771                 (run-hooks 'gnus-article-prepare-hook)
772                 ;; Decode MIME message.
773                 (if gnus-show-mime
774                     (if (or (not gnus-strict-mime)
775                             (gnus-fetch-field "Mime-Version"))
776                         (funcall gnus-show-mime-method)
777                       (funcall gnus-decode-encoded-word-method)))
778                 ;; Perform the article display hooks.
779                 (run-hooks 'gnus-article-display-hook))
780               ;; Do page break.
781               (goto-char (point-min))
782               (and gnus-break-pages (gnus-narrow-to-page)))
783             (gnus-set-mode-line 'article)
784             (gnus-configure-windows 'article)
785             (goto-char (point-min))
786             t))))))
787
788 (defun gnus-article-wash-status ()
789   "Return a string which display status of article washing."
790   (save-excursion
791     (set-buffer gnus-article-buffer)
792     (let ((cite (article-hidden-text-p 'cite))
793           (headers (article-hidden-text-p 'headers))
794           (boring (article-hidden-text-p 'boring-headers))
795           (pgp (article-hidden-text-p 'pgp))
796           (pem (article-hidden-text-p 'pem))
797           (signature (article-hidden-text-p 'signature))
798           (overstrike (article-hidden-text-p 'overstrike))
799           (emphasis (article-hidden-text-p 'emphasis))
800           (mime gnus-show-mime))
801       (format "%c%c%c%c%c%c%c"
802               (if cite ?c ? )
803               (if (or headers boring) ?h ? )
804               (if (or pgp pem) ?p ? )
805               (if signature ?s ? )
806               (if overstrike ?o ? )
807               (if mime ?m ? )
808               (if emphasis ?e ? )))))
809
810 (defun gnus-article-hide-headers-if-wanted ()
811   "Hide unwanted headers if `gnus-have-all-headers' is nil.
812 Provided for backwards compatibility."
813   (or (save-excursion (set-buffer gnus-summary-buffer) gnus-have-all-headers)
814       gnus-inhibit-hiding
815       (gnus-article-hide-headers)))
816
817 ;;; Article savers.
818
819 (defun gnus-output-to-rmail (file-name)
820   "Append the current article to an Rmail file named FILE-NAME."
821   (require 'rmail)
822   ;; Most of these codes are borrowed from rmailout.el.
823   (setq file-name (expand-file-name file-name))
824   (setq rmail-default-rmail-file file-name)
825   (let ((artbuf (current-buffer))
826         (tmpbuf (get-buffer-create " *Gnus-output*")))
827     (save-excursion
828       (or (get-file-buffer file-name)
829           (file-exists-p file-name)
830           (if (gnus-yes-or-no-p
831                (concat "\"" file-name "\" does not exist, create it? "))
832               (let ((file-buffer (create-file-buffer file-name)))
833                 (save-excursion
834                   (set-buffer file-buffer)
835                   (rmail-insert-rmail-file-header)
836                   (let ((require-final-newline nil))
837                     (write-region (point-min) (point-max) file-name t 1)))
838                 (kill-buffer file-buffer))
839             (error "Output file does not exist")))
840       (set-buffer tmpbuf)
841       (buffer-disable-undo (current-buffer))
842       (erase-buffer)
843       (insert-buffer-substring artbuf)
844       (gnus-convert-article-to-rmail)
845       ;; Decide whether to append to a file or to an Emacs buffer.
846       (let ((outbuf (get-file-buffer file-name)))
847         (if (not outbuf)
848             (append-to-file (point-min) (point-max) file-name)
849           ;; File has been visited, in buffer OUTBUF.
850           (set-buffer outbuf)
851           (let ((buffer-read-only nil)
852                 (msg (and (boundp 'rmail-current-message)
853                           (symbol-value 'rmail-current-message))))
854             ;; If MSG is non-nil, buffer is in RMAIL mode.
855             (if msg
856                 (progn (widen)
857                        (narrow-to-region (point-max) (point-max))))
858             (insert-buffer-substring tmpbuf)
859             (if msg
860                 (progn
861                   (goto-char (point-min))
862                   (widen)
863                   (search-backward "\^_")
864                   (narrow-to-region (point) (point-max))
865                   (goto-char (1+ (point-min)))
866                   (rmail-count-new-messages t)
867                   (rmail-show-message msg)))))))
868     (kill-buffer tmpbuf)))
869
870 (defun gnus-output-to-file (file-name)
871   "Append the current article to a file named FILE-NAME."
872   (let ((artbuf (current-buffer)))
873     (nnheader-temp-write nil
874       (insert-buffer-substring artbuf)
875       ;; Append newline at end of the buffer as separator, and then
876       ;; save it to file.
877       (goto-char (point-max))
878       (insert "\n")
879       (append-to-file (point-min) (point-max) file-name))))
880
881 (defun gnus-convert-article-to-rmail ()
882   "Convert article in current buffer to Rmail message format."
883   (let ((buffer-read-only nil))
884     ;; Convert article directly into Babyl format.
885     ;; Suggested by Rob Austein <sra@lcs.mit.edu>
886     (goto-char (point-min))
887     (insert "\^L\n0, unseen,,\n*** EOOH ***\n")
888     (while (search-forward "\n\^_" nil t) ;single char
889       (replace-match "\n^_" t t))       ;2 chars: "^" and "_"
890     (goto-char (point-max))
891     (insert "\^_")))
892
893 (defun gnus-narrow-to-page (&optional arg)
894   "Narrow the article buffer to a page.
895 If given a numerical ARG, move forward ARG pages."
896   (interactive "P")
897   (setq arg (if arg (prefix-numeric-value arg) 0))
898   (save-excursion
899     (set-buffer gnus-article-buffer)
900     (goto-char (point-min))
901     (widen)
902     ;; Remove any old next/prev buttons.
903     (when (gnus-visual-p 'page-marker)
904       (let ((buffer-read-only nil))
905         (gnus-remove-text-with-property 'gnus-prev)
906         (gnus-remove-text-with-property 'gnus-next)))
907     (when
908         (cond ((< arg 0)
909                (re-search-backward page-delimiter nil 'move (1+ (abs arg))))
910               ((> arg 0)
911                (re-search-forward page-delimiter nil 'move arg)))
912       (goto-char (match-end 0)))
913     (narrow-to-region
914      (point)
915      (if (re-search-forward page-delimiter nil 'move)
916          (match-beginning 0)
917        (point)))
918     (when (and (gnus-visual-p 'page-marker)
919                (not (= (point-min) 1)))
920       (save-excursion
921         (goto-char (point-min))
922         (gnus-insert-prev-page-button)))
923     (when (and (gnus-visual-p 'page-marker)
924                (< (+ (point-max) 2) (buffer-size)))
925       (save-excursion
926         (goto-char (point-max))
927         (gnus-insert-next-page-button)))))
928
929 ;; Article mode commands
930
931 (defun gnus-article-goto-next-page ()
932   "Show the next page of the article."
933   (interactive)
934   (when (gnus-article-next-page)
935     (gnus-article-read-summary-keys nil (gnus-character-to-event ?n))))
936
937 (defun gnus-article-goto-prev-page ()
938   "Show the next page of the article."
939   (interactive)
940   (if (bobp) (gnus-article-read-summary-keys nil (gnus-character-to-event ?n))
941     (gnus-article-prev-page nil)))
942
943 (defun gnus-article-next-page (&optional lines)
944   "Show the next page of the current article.
945 If end of article, return non-nil.  Otherwise return nil.
946 Argument LINES specifies lines to be scrolled up."
947   (interactive "p")
948   (move-to-window-line -1)
949   (if (save-excursion
950         (end-of-line)
951         (and (pos-visible-in-window-p)  ;Not continuation line.
952              (eobp)))
953       ;; Nothing in this page.
954       (if (or (not gnus-break-pages)
955               (save-excursion
956                 (save-restriction
957                   (widen) (forward-line 1) (eobp)))) ;Real end-of-buffer?
958           t                             ;Nothing more.
959         (gnus-narrow-to-page 1)         ;Go to next page.
960         nil)
961     ;; More in this page.
962     (condition-case ()
963         (scroll-up lines)
964       (end-of-buffer
965        ;; Long lines may cause an end-of-buffer error.
966        (goto-char (point-max))))
967     (move-to-window-line 0)
968     nil))
969
970 (defun gnus-article-prev-page (&optional lines)
971   "Show previous page of current article.
972 Argument LINES specifies lines to be scrolled down."
973   (interactive "p")
974   (move-to-window-line 0)
975   (if (and gnus-break-pages
976            (bobp)
977            (not (save-restriction (widen) (bobp)))) ;Real beginning-of-buffer?
978       (progn
979         (gnus-narrow-to-page -1)        ;Go to previous page.
980         (goto-char (point-max))
981         (recenter -1))
982     (prog1
983         (condition-case ()
984             (scroll-down lines)
985           (error nil))
986       (move-to-window-line 0))))
987
988 (defun gnus-article-refer-article ()
989   "Read article specified by message-id around point."
990   (interactive)
991   (let ((point (point)))
992     (search-forward ">" nil t)          ;Move point to end of "<....>".
993     (if (re-search-backward "\\(<[^<> \t\n]+>\\)" nil t)
994         (let ((message-id (match-string 1)))
995           (goto-char point)
996           (set-buffer gnus-summary-buffer)
997           (gnus-summary-refer-article message-id))
998       (goto-char (point))
999       (error "No references around point"))))
1000
1001 (defun gnus-article-show-summary ()
1002   "Reconfigure windows to show summary buffer."
1003   (interactive)
1004   (gnus-configure-windows 'article)
1005   (gnus-summary-goto-subject gnus-current-article))
1006
1007 (defun gnus-article-describe-briefly ()
1008   "Describe article mode commands briefly."
1009   (interactive)
1010   (gnus-message 6
1011                 (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")))
1012
1013 (defun gnus-article-summary-command ()
1014   "Execute the last keystroke in the summary buffer."
1015   (interactive)
1016   (let ((obuf (current-buffer))
1017         (owin (current-window-configuration))
1018         func)
1019     (switch-to-buffer gnus-summary-buffer 'norecord)
1020     (setq func (lookup-key (current-local-map) (this-command-keys)))
1021     (call-interactively func)
1022     (set-buffer obuf)
1023     (set-window-configuration owin)
1024     (set-window-point (get-buffer-window (current-buffer)) (point))))
1025
1026 (defun gnus-article-summary-command-nosave ()
1027   "Execute the last keystroke in the summary buffer."
1028   (interactive)
1029   (let (func)
1030     (pop-to-buffer gnus-summary-buffer 'norecord)
1031     (setq func (lookup-key (current-local-map) (this-command-keys)))
1032     (call-interactively func)))
1033
1034 (defun gnus-article-read-summary-keys (&optional arg key not-restore-window)
1035   "Read a summary buffer key sequence and execute it from the article buffer."
1036   (interactive "P")
1037   (let ((nosaves
1038          '("q" "Q"  "c" "r" "R" "\C-c\C-f" "m"  "a" "f" "F"
1039            "Zc" "ZC" "ZE" "ZQ" "ZZ" "Zn" "ZR" "ZG" "ZN" "ZP"
1040            "=" "^" "\M-^" "|"))
1041         (nosave-but-article
1042          '("A\r"))
1043         (nosave-in-article
1044          '("\C-d"))
1045         keys)
1046     (save-excursion
1047       (set-buffer gnus-summary-buffer)
1048       (push (or key last-command-event) unread-command-events)
1049       (setq keys (read-key-sequence nil)))
1050     (message "")
1051
1052     (if (or (member keys nosaves)
1053             (member keys nosave-but-article)
1054             (member keys nosave-in-article))
1055         (let (func)
1056           (save-window-excursion
1057             (pop-to-buffer gnus-summary-buffer 'norecord)
1058             (setq func (lookup-key (current-local-map) keys)))
1059           (if (not func)
1060               (ding)
1061             (unless (member keys nosave-in-article)
1062               (set-buffer gnus-summary-buffer))
1063             (call-interactively func))
1064           (when (member keys nosave-but-article)
1065             (pop-to-buffer gnus-article-buffer 'norecord)))
1066       ;; These commands should restore window configuration.
1067       (let ((obuf (current-buffer))
1068             (owin (current-window-configuration))
1069             (opoint (point))
1070             func in-buffer)
1071         (if not-restore-window
1072             (pop-to-buffer gnus-summary-buffer 'norecord)
1073           (switch-to-buffer gnus-summary-buffer 'norecord))
1074         (setq in-buffer (current-buffer))
1075         (if (setq func (lookup-key (current-local-map) keys))
1076             (call-interactively func)
1077           (ding))
1078         (when (eq in-buffer (current-buffer))
1079           (set-buffer obuf)
1080           (unless not-restore-window
1081             (set-window-configuration owin))
1082           (set-window-point (get-buffer-window (current-buffer)) opoint))))))
1083
1084 (defun gnus-article-hide (&optional arg force)
1085   "Hide all the gruft in the current article.
1086 This means that PGP stuff, signatures, cited text and (some)
1087 headers will be hidden.
1088 If given a prefix, show the hidden text instead."
1089   (interactive (list current-prefix-arg 'force))
1090   (gnus-article-hide-headers arg)
1091   (gnus-article-hide-pgp arg)
1092   (gnus-article-hide-citation-maybe arg force)
1093   (gnus-article-hide-signature arg))
1094
1095 (defun gnus-article-maybe-highlight ()
1096   "Do some article highlighting if `article-visual' is non-nil."
1097   (if (gnus-visual-p 'article-highlight 'highlight)
1098       (gnus-article-highlight-some)))
1099
1100 (defun gnus-request-article-this-buffer (article group)
1101   "Get an article and insert it into this buffer."
1102   (let (do-update-line)
1103     (prog1
1104         (save-excursion
1105           (erase-buffer)
1106           (gnus-kill-all-overlays)
1107           (setq group (or group gnus-newsgroup-name))
1108
1109           ;; Open server if it has closed.
1110           (gnus-check-server (gnus-find-method-for-group group))
1111
1112           ;; Using `gnus-request-article' directly will insert the article into
1113           ;; `nntp-server-buffer' - so we'll save some time by not having to
1114           ;; copy it from the server buffer into the article buffer.
1115
1116           ;; We only request an article by message-id when we do not have the
1117           ;; headers for it, so we'll have to get those.
1118           (when (stringp article)
1119             (let ((gnus-override-method gnus-refer-article-method))
1120               (gnus-read-header article)))
1121
1122           ;; If the article number is negative, that means that this article
1123           ;; doesn't belong in this newsgroup (possibly), so we find its
1124           ;; message-id and request it by id instead of number.
1125           (when (and (numberp article)
1126                      gnus-summary-buffer
1127                      (get-buffer gnus-summary-buffer)
1128                      (buffer-name (get-buffer gnus-summary-buffer)))
1129             (save-excursion
1130               (set-buffer gnus-summary-buffer)
1131               (let ((header (gnus-summary-article-header article)))
1132                 (if (< article 0)
1133                     (cond 
1134                      ((memq article gnus-newsgroup-sparse)
1135                       ;; This is a sparse gap article.
1136                       (setq do-update-line article)
1137                       (setq article (mail-header-id header))
1138                       (let ((gnus-override-method gnus-refer-article-method))
1139                         (gnus-read-header article))
1140                       (setq gnus-newsgroup-sparse
1141                             (delq article gnus-newsgroup-sparse)))
1142                      ((vectorp header)
1143                       ;; It's a real article.
1144                       (setq article (mail-header-id header)))
1145                      (t
1146                       ;; It is an extracted pseudo-article.
1147                       (setq article 'pseudo)
1148                       (gnus-request-pseudo-article header))))
1149                 
1150                 (let ((method (gnus-find-method-for-group 
1151                                gnus-newsgroup-name)))
1152                   (if (not (eq (car method) 'nneething))
1153                       ()
1154                     (let ((dir (concat (file-name-as-directory (nth 1 method))
1155                                        (mail-header-subject header))))
1156                       (if (file-directory-p dir)
1157                           (progn
1158                             (setq article 'nneething)
1159                             (gnus-group-enter-directory dir)))))))))
1160
1161           (cond
1162            ;; Refuse to select canceled articles.
1163            ((and (numberp article)
1164                  gnus-summary-buffer
1165                  (get-buffer gnus-summary-buffer)
1166                  (buffer-name (get-buffer gnus-summary-buffer))
1167                  (eq (cdr (save-excursion
1168                             (set-buffer gnus-summary-buffer)
1169                             (assq article gnus-newsgroup-reads)))
1170                      gnus-canceled-mark))
1171             nil)
1172            ;; We first check `gnus-original-article-buffer'.
1173            ((and (get-buffer gnus-original-article-buffer)
1174                  (numberp article)
1175                  (save-excursion
1176                    (set-buffer gnus-original-article-buffer)
1177                    (and (equal (car gnus-original-article) group)
1178                         (eq (cdr gnus-original-article) article))))
1179             (insert-buffer-substring gnus-original-article-buffer)
1180             'article)
1181            ;; Check the backlog.
1182            ((and gnus-keep-backlog
1183                  (gnus-backlog-request-article group article (current-buffer)))
1184             'article)
1185            ;; Check asynchronous pre-fetch.
1186            ((gnus-async-request-fetched-article group article (current-buffer))
1187             (gnus-async-prefetch-next group article gnus-summary-buffer)
1188             'article)
1189            ;; Check the cache.
1190            ((and gnus-use-cache
1191                  (numberp article)
1192                  (gnus-cache-request-article article group))
1193             'article)
1194            ;; Get the article and put into the article buffer.
1195            ((or (stringp article) (numberp article))
1196             (let ((gnus-override-method
1197                    (and (stringp article) gnus-refer-article-method))
1198                   (buffer-read-only nil))
1199               (erase-buffer)
1200               (gnus-kill-all-overlays)
1201               (when (gnus-request-article article group (current-buffer))
1202                 (when (numberp article)
1203                   (gnus-async-prefetch-next group article gnus-summary-buffer)
1204                   (when gnus-keep-backlog
1205                     (gnus-backlog-enter-article 
1206                      group article (current-buffer))))
1207                 'article)))
1208            ;; It was a pseudo.
1209            (t article)))
1210
1211       ;; Take the article from the original article buffer
1212       ;; and place it in the buffer it's supposed to be in.
1213       (when (and (get-buffer gnus-article-buffer)
1214                  ;;(numberp article)
1215                  (equal (buffer-name (current-buffer))
1216                         (buffer-name (get-buffer gnus-article-buffer))))
1217         (save-excursion
1218           (if (get-buffer gnus-original-article-buffer)
1219               (set-buffer (get-buffer gnus-original-article-buffer))
1220             (set-buffer (get-buffer-create gnus-original-article-buffer))
1221             (buffer-disable-undo (current-buffer))
1222             (setq major-mode 'gnus-original-article-mode)
1223             (setq buffer-read-only t)
1224             (gnus-add-current-to-buffer-list))
1225           (let (buffer-read-only)
1226             (erase-buffer)
1227             (insert-buffer-substring gnus-article-buffer))
1228           (setq gnus-original-article (cons group article))))
1229     
1230       ;; Update sparse articles.
1231       (when (and do-update-line
1232                  (or (numberp article)
1233                      (stringp article)))
1234         (let ((buf (current-buffer)))
1235           (set-buffer gnus-summary-buffer)
1236           (gnus-summary-update-article do-update-line)
1237           (gnus-summary-goto-subject do-update-line nil t)
1238           (set-window-point (get-buffer-window (current-buffer) t)
1239                             (point))
1240           (set-buffer buf))))))
1241
1242 (defun gnus-article-date-ut (&optional type highlight)
1243   "Convert DATE date to universal time in the current article.
1244 If TYPE is `local', convert to local time; if it is `lapsed', output
1245 how much time has lapsed since DATE."
1246   (interactive (list 'ut t))
1247   (let ((headers (or gnus-current-headers (gnus-summary-article-header))))
1248     (save-excursion
1249       (set-buffer gnus-article-buffer)
1250       (article-date-ut type highlight headers))))
1251
1252 ;;;
1253 ;;; Article editing
1254 ;;;
1255
1256 (defvar gnus-article-edit-mode-hook nil
1257   "*Hook run in article edit mode buffers.")
1258
1259 (defvar gnus-article-edit-done-function nil)
1260
1261 (defvar gnus-article-edit-mode-map nil)
1262
1263 (unless gnus-article-edit-mode-map 
1264   (setq gnus-article-edit-mode-map (copy-keymap text-mode-map))
1265
1266   (gnus-define-keys gnus-article-edit-mode-map
1267     "\C-c\C-c" gnus-article-edit-done
1268     "\C-c\C-k" gnus-article-edit-exit)
1269
1270   (gnus-define-keys (gnus-article-edit-wash-map
1271                      "\C-c\C-w" gnus-article-edit-mode-map)
1272     "f" gnus-article-edit-full-stops))
1273
1274 (defun gnus-article-edit-mode ()
1275   "Major mode for editing articles.
1276 This is an extended text-mode.
1277
1278 \\{gnus-article-edit-mode-map}"
1279   (interactive)
1280   (kill-all-local-variables)
1281   (setq major-mode 'gnus-article-edit-mode)
1282   (setq mode-name "Article Edit")
1283   (use-local-map gnus-article-edit-mode-map)
1284   (make-local-variable 'gnus-article-edit-done-function)
1285   (make-local-variable 'gnus-prev-winconf)
1286   (setq buffer-read-only nil)
1287   (buffer-enable-undo)
1288   (widen)
1289   (run-hooks 'text-mode 'gnus-article-edit-mode-hook))
1290
1291 (defun gnus-article-edit (&optional force)
1292   "Edit the current article.
1293 This will have permanent effect only in mail groups.
1294 If FORCE is non-nil, allow editing of articles even in read-only
1295 groups."
1296   (interactive "P")
1297   (when (and (not force)
1298              (gnus-group-read-only-p))
1299     (error "The current newsgroup does not support article editing."))
1300   (gnus-article-edit-article
1301    `(lambda ()
1302       (gnus-summary-edit-article-done
1303        ,(or (mail-header-references gnus-current-headers) "")
1304        ,(gnus-group-read-only-p) ,gnus-summary-buffer))))
1305
1306 (defun gnus-article-edit-article (exit-func)
1307   "Start editing the contents of the current article buffer."
1308   (let ((winconf (current-window-configuration)))
1309     (set-buffer gnus-article-buffer)
1310     (gnus-article-edit-mode)
1311     (set-text-properties (point-min) (point-max) nil)
1312     (gnus-configure-windows 'edit-article)
1313     (setq gnus-article-edit-done-function exit-func)
1314     (setq gnus-prev-winconf winconf)
1315     (gnus-message 6 "C-c C-c to end edits")))
1316
1317 (defun gnus-article-edit-done ()
1318   "Update the article edits and exit."
1319   (interactive)
1320   (let ((func gnus-article-edit-done-function)
1321         (buf (current-buffer))
1322         (start (window-start)))
1323     (gnus-article-edit-exit)
1324     (save-excursion
1325       (set-buffer buf)
1326       (let ((buffer-read-only nil))
1327         (funcall func)))
1328     (set-buffer buf)
1329     (set-window-start (get-buffer-window buf) start)
1330     (set-window-point (get-buffer-window buf) (point))))
1331
1332 (defun gnus-article-edit-exit ()
1333   "Exit the article editing without updating."
1334   (interactive)
1335   ;; We remove all text props from the article buffer.
1336   (let ((buf (format "%s" (buffer-string)))
1337         (curbuf (current-buffer))
1338         (p (point))
1339         (window-start (window-start)))
1340     (erase-buffer)
1341     (insert buf)
1342     (let ((winconf gnus-prev-winconf))
1343       (gnus-article-mode)
1344       ;; The cache and backlog have to be flushed somewhat.
1345       (when gnus-use-cache
1346         (gnus-cache-update-article      
1347          (car gnus-article-current) (cdr gnus-article-current)))
1348       (when gnus-keep-backlog
1349         (gnus-backlog-remove-article 
1350          (car gnus-article-current) (cdr gnus-article-current)))
1351       ;; Flush original article as well.
1352       (save-excursion
1353         (when (get-buffer gnus-original-article-buffer)
1354           (set-buffer gnus-original-article-buffer)
1355           (setq gnus-original-article nil)))
1356       (set-window-configuration winconf)
1357       ;; Tippy-toe some to make sure that point remains where it was.
1358       (let ((buf (current-buffer)))
1359         (set-buffer curbuf)
1360         (set-window-start (get-buffer-window (current-buffer)) window-start)
1361         (goto-char p)
1362         (set-buffer buf)))))
1363       
1364 (defun gnus-article-edit-full-stops ()
1365   "Interactively repair spacing at end of sentences."
1366   (interactive)
1367   (save-excursion
1368     (goto-char (point-min))
1369     (search-forward-regexp "^$" nil t)
1370     (let ((case-fold-search nil))
1371       (query-replace-regexp "\\([.!?][])}]* \\)\\([[({A-Z]\\)" "\\1 \\2"))))
1372
1373 ;;; 
1374 ;;; Article highlights
1375 ;;;
1376
1377 ;; Written by Per Abrahamsen <abraham@iesd.auc.dk>.
1378
1379 ;;; Internal Variables:
1380
1381 (defvar gnus-button-url-regexp "\\b\\(s?https?\\|ftp\\|file\\|gopher\\|news\\|telnet\\|wais\\|mailto\\):\\(//[-a-zA-Z0-9_.]+:[0-9]*\\)?[-\\wa-zA-Z0-9_=!?#$@~`%&*+|\\/:;.,]*[-\\wa-zA-Z0-9_=#$@~`%&*+|\\/]"
1382   "*Regular expression that matches URLs.")
1383
1384 (defvar gnus-button-alist 
1385   `(("\\bin\\( +article\\)? +\\(<\\([^\n @<>]+@[^\n @<>]+\\)>\\)" 2 
1386      t gnus-button-message-id 3)
1387     ("\\(<?\\(url: ?\\)?news://\\([^>\n\t ]*\\)>?\\)" 1 t
1388      gnus-button-fetch-group 3)
1389     ("\\(<?\\(url: ?\\)?news:\\([^>\n\t ]*\\)>?\\)" 1 t
1390      gnus-button-message-id 3)
1391     ("\\(<URL: *\\)?mailto: *\\([^> \n\t]+\\)>?" 0 t gnus-button-reply 2)
1392     ;; This is how URLs _should_ be embedded in text...
1393     ("<URL: *\\([^\n\r>]*\\)>" 0 t gnus-button-url 1)
1394     ;; Next regexp stolen from highlight-headers.el.
1395     ;; Modified by Vladimir Alexiev.
1396     (,gnus-button-url-regexp 0 t gnus-button-url 0))
1397   "Alist of regexps matching buttons in article bodies.
1398
1399 Each entry has the form (REGEXP BUTTON FORM CALLBACK PAR...), where
1400 REGEXP: is the string matching text around the button,
1401 BUTTON: is the number of the regexp grouping actually matching the button,
1402 FORM: is a lisp expression which must eval to true for the button to
1403 be added, 
1404 CALLBACK: is the function to call when the user push this button, and each
1405 PAR: is a number of a regexp grouping whose text will be passed to CALLBACK.
1406
1407 CALLBACK can also be a variable, in that case the value of that
1408 variable it the real callback function.")
1409
1410 (defvar gnus-header-button-alist 
1411   `(("^\\(References\\|Message-I[Dd]\\):" "<[^>]+>"
1412      0 t gnus-button-message-id 0)
1413     ("^\\(From\\|Reply-To\\):" ": *\\(.+\\)$" 1 t gnus-button-reply 1)
1414     ("^\\(Cc\\|To\\):" "[^ \t\n<>,()\"]+@[^ \t\n<>,()\"]+" 
1415      0 t gnus-button-mailto 0)
1416     ("^X-[Uu][Rr][Ll]:" ,gnus-button-url-regexp 0 t gnus-button-url 0)
1417     ("^[^:]+:" ,gnus-button-url-regexp 0 t gnus-button-url 0)
1418     ("^[^:]+:" "\\(<\\(url: \\)?news:\\([^>\n ]*\\)>\\)" 1 t
1419      gnus-button-message-id 3))
1420   "Alist of headers and regexps to match buttons in article heads.
1421
1422 This alist is very similar to `gnus-button-alist', except that each
1423 alist has an additional HEADER element first in each entry:
1424
1425 \(HEADER REGEXP BUTTON FORM CALLBACK PAR)
1426
1427 HEADER is a regexp to match a header.  For a fuller explanation, see
1428 `gnus-button-alist'.")
1429
1430 (defvar gnus-button-regexp nil)
1431 (defvar gnus-button-marker-list nil)
1432 ;; Regexp matching any of the regexps from `gnus-button-alist'.
1433
1434 (defvar gnus-button-last nil)
1435 ;; The value of `gnus-button-alist' when `gnus-button-regexp' was build.
1436
1437 ;;; Commands:
1438
1439 (defun gnus-article-push-button (event)
1440   "Check text under the mouse pointer for a callback function.
1441 If the text under the mouse pointer has a `gnus-callback' property,
1442 call it with the value of the `gnus-data' text property."
1443   (interactive "e")
1444   (set-buffer (window-buffer (posn-window (event-start event))))
1445   (let* ((pos (posn-point (event-start event)))
1446          (data (get-text-property pos 'gnus-data))
1447          (fun (get-text-property pos 'gnus-callback)))
1448     (if fun (funcall fun data))))
1449
1450 (defun gnus-article-press-button ()
1451   "Check text at point for a callback function.
1452 If the text at point has a `gnus-callback' property,
1453 call it with the value of the `gnus-data' text property."
1454   (interactive)
1455   (let* ((data (get-text-property (point) 'gnus-data))
1456          (fun (get-text-property (point) 'gnus-callback)))
1457     (if fun (funcall fun data))))
1458
1459 (defun gnus-article-prev-button (n)
1460   "Move point to N buttons backward.
1461 If N is negative, move forward instead."
1462   (interactive "p")
1463   (gnus-article-next-button (- n)))
1464
1465 (defun gnus-article-next-button (n)
1466   "Move point to N buttons forward.
1467 If N is negative, move backward instead."
1468   (interactive "p")
1469   (let ((function (if (< n 0) 'previous-single-property-change
1470                     'next-single-property-change))
1471         (inhibit-point-motion-hooks t)
1472         (backward (< n 0))
1473         (limit (if (< n 0) (point-min) (point-max))))
1474     (setq n (abs n))
1475     (while (and (not (= limit (point)))
1476                 (> n 0))
1477       ;; Skip past the current button.
1478       (when (get-text-property (point) 'gnus-callback)
1479         (goto-char (funcall function (point) 'gnus-callback nil limit)))
1480       ;; Go to the next (or previous) button.
1481       (gnus-goto-char (funcall function (point) 'gnus-callback nil limit))
1482       ;; Put point at the start of the button.
1483       (when (and backward (not (get-text-property (point) 'gnus-callback)))
1484         (goto-char (funcall function (point) 'gnus-callback nil limit)))
1485       ;; Skip past intangible buttons.
1486       (when (get-text-property (point) 'intangible)
1487         (incf n))
1488       (decf n))
1489     (unless (zerop n)
1490       (gnus-message 5 "No more buttons"))
1491     n))
1492
1493 (defun gnus-article-highlight (&optional force)
1494   "Highlight current article.
1495 This function calls `gnus-article-highlight-headers',
1496 `gnus-article-highlight-citation', 
1497 `gnus-article-highlight-signature', and `gnus-article-add-buttons' to
1498 do the highlighting.  See the documentation for those functions."
1499   (interactive (list 'force))
1500   (gnus-article-highlight-headers)
1501   (gnus-article-highlight-citation force)
1502   (gnus-article-highlight-signature)
1503   (gnus-article-add-buttons force)
1504   (gnus-article-add-buttons-to-head))
1505
1506 (defun gnus-article-highlight-some (&optional force)
1507   "Highlight current article.
1508 This function calls `gnus-article-highlight-headers',
1509 `gnus-article-highlight-signature', and `gnus-article-add-buttons' to
1510 do the highlighting.  See the documentation for those functions."
1511   (interactive (list 'force))
1512   (gnus-article-highlight-headers)
1513   (gnus-article-highlight-signature)
1514   (gnus-article-add-buttons))
1515
1516 (defun gnus-article-highlight-headers ()
1517   "Highlight article headers as specified by `gnus-header-face-alist'."
1518   (interactive)
1519   (save-excursion
1520     (set-buffer gnus-article-buffer)
1521     (save-restriction
1522       (let ((alist gnus-header-face-alist)
1523             (buffer-read-only nil)
1524             (case-fold-search t)
1525             (inhibit-point-motion-hooks t)
1526             entry regexp header-face field-face from hpoints fpoints)
1527         (goto-char (point-min))
1528         (when (search-forward "\n\n" nil t)
1529           (narrow-to-region (1- (point)) (point-min))
1530           (while (setq entry (pop alist))
1531             (goto-char (point-min))
1532             (setq regexp (concat "^\\("
1533                                  (if (string-equal "" (nth 0 entry))
1534                                      "[^\t ]"
1535                                    (nth 0 entry))
1536                                  "\\)")
1537                   header-face (nth 1 entry)
1538                   field-face (nth 2 entry))
1539             (while (and (re-search-forward regexp nil t)
1540                         (not (eobp)))
1541               (beginning-of-line)
1542               (setq from (point))
1543               (or (search-forward ":" nil t)
1544                   (forward-char 1))
1545               (when (and header-face
1546                          (not (memq (point) hpoints)))
1547                 (push (point) hpoints)
1548                 (gnus-put-text-property from (point) 'face header-face))
1549               (when (and field-face
1550                          (not (memq (setq from (point)) fpoints)))
1551                 (push from fpoints)
1552                 (if (re-search-forward "^[^ \t]" nil t)
1553                     (forward-char -2)
1554                   (goto-char (point-max)))
1555                 (gnus-put-text-property from (point) 'face field-face)))))))))
1556
1557 (defun gnus-article-highlight-signature ()
1558   "Highlight the signature in an article.
1559 It does this by highlighting everything after
1560 `gnus-signature-separator' using `gnus-signature-face'." 
1561   (interactive)
1562   (save-excursion
1563     (set-buffer gnus-article-buffer)
1564     (let ((buffer-read-only nil)
1565           (inhibit-point-motion-hooks t))
1566       (save-restriction
1567         (when (and gnus-signature-face
1568                    (article-narrow-to-signature))
1569           (gnus-overlay-put (gnus-make-overlay (point-min) (point-max))
1570                             'face gnus-signature-face)
1571           (widen)
1572           (article-search-signature)
1573           (let ((start (match-beginning 0))
1574                 (end (set-marker (make-marker) (1+ (match-end 0)))))
1575             (gnus-article-add-button start (1- end) 'gnus-signature-toggle
1576                                      end)))))))
1577
1578 (defun gnus-article-add-buttons (&optional force)
1579   "Find external references in the article and make buttons of them.
1580 \"External references\" are things like Message-IDs and URLs, as
1581 specified by `gnus-button-alist'."
1582   (interactive (list 'force))
1583   (save-excursion
1584     (set-buffer gnus-article-buffer)
1585     ;; Remove all old markers.
1586     (while gnus-button-marker-list
1587       (set-marker (pop gnus-button-marker-list) nil))
1588     (let ((buffer-read-only nil)
1589           (inhibit-point-motion-hooks t)
1590           (case-fold-search t)
1591           (alist gnus-button-alist)
1592           beg entry regexp)
1593       (goto-char (point-min))
1594       ;; We skip the headers.
1595       (unless (search-forward "\n\n" nil t)
1596         (goto-char (point-max)))
1597       (setq beg (point))
1598       (while (setq entry (pop alist))
1599         (setq regexp (car entry))
1600         (goto-char beg)
1601         (while (re-search-forward regexp nil t)
1602           (let* ((start (and entry (match-beginning (nth 1 entry))))
1603                  (end (and entry (match-end (nth 1 entry))))
1604                  (from (match-beginning 0)))
1605             (when (or (eq t (nth 1 entry))
1606                       (eval (nth 1 entry)))
1607               ;; That optional form returned non-nil, so we add the
1608               ;; button. 
1609               (gnus-article-add-button 
1610                start end 'gnus-button-push 
1611                (car (push (set-marker (make-marker) from)
1612                           gnus-button-marker-list))))))))))
1613
1614 ;; Add buttons to the head of an article.
1615 (defun gnus-article-add-buttons-to-head ()
1616   "Add buttons to the head of the article."
1617   (interactive)
1618   (save-excursion
1619     (set-buffer gnus-article-buffer)
1620     (let ((buffer-read-only nil)
1621           (inhibit-point-motion-hooks t)
1622           (case-fold-search t)
1623           (alist gnus-header-button-alist)
1624           entry beg end)
1625       (nnheader-narrow-to-headers)
1626       (while alist
1627         ;; Each alist entry.
1628         (setq entry (car alist)
1629               alist (cdr alist))
1630         (goto-char (point-min))
1631         (while (re-search-forward (car entry) nil t)
1632           ;; Each header matching the entry.
1633           (setq beg (match-beginning 0))
1634           (setq end (or (and (re-search-forward "^[^ \t]" nil t)
1635                              (match-beginning 0))
1636                         (point-max)))
1637           (goto-char beg)
1638           (while (re-search-forward (nth 1 entry) end t)
1639             ;; Each match within a header.
1640             (let* ((entry (cdr entry))
1641                    (start (match-beginning (nth 1 entry)))
1642                    (end (match-end (nth 1 entry)))
1643                    (form (nth 2 entry)))
1644               (goto-char (match-end 0))
1645               (and (eval form)
1646                    (gnus-article-add-button 
1647                     start end (nth 3 entry)
1648                     (buffer-substring (match-beginning (nth 4 entry))
1649                                       (match-end (nth 4 entry)))))))
1650           (goto-char end))))
1651     (widen)))
1652
1653 ;;; External functions:
1654
1655 (defun gnus-article-add-button (from to fun &optional data)
1656   "Create a button between FROM and TO with callback FUN and data DATA."
1657   (and gnus-article-button-face
1658        (gnus-overlay-put (gnus-make-overlay from to)
1659                          'face gnus-article-button-face))
1660   (gnus-add-text-properties 
1661    from to
1662    (nconc (and gnus-article-mouse-face
1663                (list gnus-mouse-face-prop gnus-article-mouse-face))
1664           (list 'gnus-callback fun)
1665           (and data (list 'gnus-data data)))))
1666
1667 ;;; Internal functions:
1668
1669 (defun gnus-signature-toggle (end)
1670   (save-excursion
1671     (set-buffer gnus-article-buffer)
1672     (let ((buffer-read-only nil)
1673           (inhibit-point-motion-hooks t))
1674       (if (get-text-property end 'invisible)
1675           (article-unhide-text end (point-max))
1676         (article-hide-text end (point-max) gnus-hidden-properties)))))
1677
1678 (defun gnus-button-entry ()
1679   ;; Return the first entry in `gnus-button-alist' matching this place.
1680   (let ((alist gnus-button-alist)
1681         (entry nil))
1682     (while alist
1683       (setq entry (pop alist))
1684       (if (looking-at (car entry))
1685           (setq alist nil)
1686         (setq entry nil)))
1687     entry))
1688
1689 (defun gnus-button-push (marker)
1690   ;; Push button starting at MARKER.
1691   (save-excursion
1692     (set-buffer gnus-article-buffer)
1693     (goto-char marker)
1694     (let* ((entry (gnus-button-entry))
1695            (inhibit-point-motion-hooks t)
1696            (fun (nth 3 entry))
1697            (args (mapcar (lambda (group) 
1698                            (let ((string (buffer-substring
1699                                           (match-beginning group)
1700                                           (match-end group))))
1701                              (gnus-set-text-properties
1702                               0 (length string) nil string)
1703                              string))
1704                          (nthcdr 4 entry))))
1705       (cond
1706        ((fboundp fun)
1707         (apply fun args))
1708        ((and (boundp fun)
1709              (fboundp (symbol-value fun)))
1710         (apply (symbol-value fun) args))
1711        (t
1712         (gnus-message 1 "You must define `%S' to use this button"
1713                       (cons fun args)))))))
1714
1715 (defun gnus-button-message-id (message-id)
1716   "Fetch MESSAGE-ID."
1717   (save-excursion
1718     (set-buffer gnus-summary-buffer)
1719     (gnus-summary-refer-article message-id)))
1720
1721 (defun gnus-button-fetch-group (address)
1722   "Fetch GROUP specified by ADDRESS."
1723   (if (not (string-match "^\\([^:/]+\\)\\(:\\([^/]+\\)\\)?/\\(.*\\)$" address))
1724       (error "Can't parse %s" address)
1725     (gnus-group-read-ephemeral-group
1726      (match-string 4 address)
1727      `(nntp ,(match-string 1 address) (nntp-address ,(match-string 1 address))
1728             (nntp-port-number ,(if (match-end 3)
1729                                    (match-string 3 address)
1730                                  "nntp"))))))
1731
1732 (defun gnus-button-mailto (address)
1733   ;; Mail to ADDRESS.
1734   (set-buffer (gnus-copy-article-buffer))
1735   (message-reply address))
1736
1737 (defun gnus-button-reply (address)
1738   ;; Reply to ADDRESS.
1739   (message-reply address))
1740
1741 (defun gnus-button-url (address)
1742   "Browse ADDRESS."
1743   (funcall browse-url-browser-function address))
1744
1745 ;;; Next/prev buttons in the article buffer.
1746
1747 (defvar gnus-next-page-line-format "%{%(Next page...%)%}\n")
1748 (defvar gnus-prev-page-line-format "%{%(Previous page...%)%}\n")
1749
1750 (defvar gnus-prev-page-map nil)
1751 (unless gnus-prev-page-map
1752   (setq gnus-prev-page-map (make-sparse-keymap))
1753   (define-key gnus-prev-page-map gnus-mouse-2 'gnus-button-prev-page)
1754   (define-key gnus-prev-page-map "\r" 'gnus-button-prev-page))
1755
1756 (defun gnus-insert-prev-page-button ()
1757   (let ((buffer-read-only nil))
1758     (gnus-eval-format 
1759      gnus-prev-page-line-format nil
1760      `(gnus-prev t local-map ,gnus-prev-page-map
1761                  gnus-callback gnus-article-button-prev-page))))
1762
1763 (defvar gnus-next-page-map nil)
1764 (unless gnus-next-page-map
1765   (setq gnus-next-page-map (make-keymap))
1766   (suppress-keymap gnus-prev-page-map)
1767   (define-key gnus-next-page-map gnus-mouse-2 'gnus-button-next-page)
1768   (define-key gnus-next-page-map "\r" 'gnus-button-next-page))
1769
1770 (defun gnus-button-next-page ()
1771   "Go to the next page."
1772   (interactive)
1773   (let ((win (selected-window)))
1774     (select-window (get-buffer-window gnus-article-buffer t))
1775     (gnus-article-next-page)
1776     (select-window win)))
1777
1778 (defun gnus-button-prev-page ()
1779   "Go to the prev page."
1780   (interactive)
1781   (let ((win (selected-window)))
1782     (select-window (get-buffer-window gnus-article-buffer t))
1783     (gnus-article-prev-page)
1784     (select-window win)))
1785
1786 (defun gnus-insert-next-page-button ()
1787   (let ((buffer-read-only nil))
1788     (gnus-eval-format gnus-next-page-line-format nil
1789                       `(gnus-next t local-map ,gnus-next-page-map
1790                                   gnus-callback 
1791                                   gnus-article-button-next-page))))
1792
1793 (defun gnus-article-button-next-page (arg)
1794   "Go to the next page."
1795   (interactive "P")
1796   (let ((win (selected-window)))
1797     (select-window (get-buffer-window gnus-article-buffer t))
1798     (gnus-article-next-page)
1799     (select-window win)))
1800
1801 (defun gnus-article-button-prev-page (arg)
1802   "Go to the prev page."
1803   (interactive "P")
1804   (let ((win (selected-window)))
1805     (select-window (get-buffer-window gnus-article-buffer t))
1806     (gnus-article-prev-page)
1807     (select-window win)))
1808
1809 (provide 'gnus-art)
1810
1811 ;;; gnus-art.el ends here