*** 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
34 (defvar gnus-article-save-directory gnus-directory
35   "*Name of the directory articles will be saved in (default \"~/News\").")
36
37 (defvar gnus-save-all-headers t
38   "*If non-nil, don't remove any headers before saving.")
39
40 (defvar gnus-prompt-before-saving 'always
41   "*This variable says how much prompting is to be done when saving articles.
42 If it is nil, no prompting will be done, and the articles will be
43 saved to the default files.  If this variable is `always', each and
44 every article that is saved will be preceded by a prompt, even when
45 saving large batches of articles.  If this variable is neither nil not
46 `always', there the user will be prompted once for a file name for
47 each invocation of the saving commands.")
48
49 (defvar gnus-saved-headers gnus-visible-headers
50   "*Headers to keep if `gnus-save-all-headers' is nil.
51 If `gnus-save-all-headers' is non-nil, this variable will be ignored.
52 If that variable is nil, however, all headers that match this regexp
53 will be kept while the rest will be deleted before saving.")
54
55 (defvar gnus-default-article-saver 'gnus-summary-save-in-rmail
56   "*A function to save articles in your favorite format.
57 The function must be interactively callable (in other words, it must
58 be an Emacs command).
59
60 Gnus provides the following functions:
61
62 * gnus-summary-save-in-rmail (Rmail format)
63 * gnus-summary-save-in-mail (Unix mail format)
64 * gnus-summary-save-in-folder (MH folder)
65 * gnus-summary-save-in-file (article format).
66 * gnus-summary-save-in-vm (use VM's folder format).")
67
68 (defvar gnus-rmail-save-name (function gnus-plain-save-name)
69   "*A function generating a file name to save articles in Rmail format.
70 The function is called with NEWSGROUP, HEADERS, and optional LAST-FILE.")
71
72 (defvar gnus-mail-save-name (function gnus-plain-save-name)
73   "*A function generating a file name to save articles in Unix mail format.
74 The function is called with NEWSGROUP, HEADERS, and optional LAST-FILE.")
75
76 (defvar gnus-folder-save-name (function gnus-folder-save-name)
77   "*A function generating a file name to save articles in MH folder.
78 The function is called with NEWSGROUP, HEADERS, and optional LAST-FOLDER.")
79
80 (defvar gnus-file-save-name (function gnus-numeric-save-name)
81   "*A function generating a file name to save articles in article format.
82 The function is called with NEWSGROUP, HEADERS, and optional
83 LAST-FILE.")
84
85 (defvar gnus-split-methods
86   '((gnus-article-archive-name))
87   "*Variable used to suggest where articles are to be saved.
88 For instance, if you would like to save articles related to Gnus in
89 the file \"gnus-stuff\", and articles related to VM in \"vm-stuff\",
90 you could set this variable to something like:
91
92  '((\"^Subject:.*gnus\\|^Newsgroups:.*gnus\" \"gnus-stuff\")
93    (\"^Subject:.*vm\\|^Xref:.*vm\" \"vm-stuff\"))
94
95 This variable is an alist where the where the key is the match and the
96 value is a list of possible files to save in if the match is non-nil.
97
98 If the match is a string, it is used as a regexp match on the
99 article.  If the match is a symbol, that symbol will be funcalled
100 from the buffer of the article to be saved with the newsgroup as the
101 parameter.  If it is a list, it will be evaled in the same buffer.
102
103 If this form or function returns a string, this string will be used as
104 a possible file name; and if it returns a non-nil list, that list will
105 be used as possible file names.")
106
107 (defvar gnus-strict-mime t
108   "*If nil, MIME-decode even if there is no Mime-Version header in the article.")
109
110 (defvar gnus-show-mime-method 'metamail-buffer
111   "*Function to process a MIME message.
112 The function is called from the article buffer.")
113
114 (defvar gnus-decode-encoded-word-method (lambda ())
115   "*Function to decode a MIME encoded-words.
116 The function is called from the article buffer.")
117
118 (defvar gnus-page-delimiter "^\^L"
119   "*Regexp describing what to use as article page delimiters.
120 The default value is \"^\^L\", which is a form linefeed at the
121 beginning of a line.")
122
123 (defvar gnus-article-mode-line-format "Gnus: %%b %S"
124   "*The format specification for the article mode line.
125 See `gnus-summary-mode-line-format' for a closer description.")
126
127 (defvar gnus-article-mode-hook nil
128   "*A hook for Gnus article mode.")
129
130 (defvar gnus-article-prepare-hook nil
131   "*A hook called after an article has been prepared in the article buffer.
132 If you want to run a special decoding program like nkf, use this hook.")
133
134 ;(defvar gnus-article-display-hook nil
135 ;  "*A hook called after the article is displayed in the article buffer.
136 ;The hook is designed to change the contents of the article
137 ;buffer.  Typical functions that this hook may contain are
138 ;`gnus-article-hide-headers' (hide selected headers),
139 ;`gnus-article-maybe-highlight' (perform fancy article highlighting),
140 ;`gnus-article-hide-signature' (hide signature) and
141 ;`gnus-article-treat-overstrike' (turn \"^H_\" into bold characters).")
142 ;(add-hook 'gnus-article-display-hook 'gnus-article-hide-headers-if-wanted)
143 ;(add-hook 'gnus-article-display-hook 'gnus-article-treat-overstrike)
144 ;(add-hook 'gnus-article-display-hook 'gnus-article-maybe-highlight)
145
146 ;;; Internal variables
147
148 (defvar gnus-article-mode-line-format-alist
149     (nconc '((?w (gnus-article-wash-status) ?s))
150            gnus-summary-mode-line-format-alist))
151
152 ;;; Provide a mapping from `gnus-*' commands to Article commands.
153
154 (eval-and-compile
155   (mapcar
156    (lambda (func)
157      (let (afunc gfunc)
158        (if (consp func)
159            (setq afunc (car func)
160                  gfunc (cdr func))
161          (setq afunc func
162                gfunc (intern (format "gnus-%s" func))))
163        (fset gfunc 
164              `(lambda (&optional interactive &rest args)
165                 ,(documentation afunc t)
166                 (interactive (list t))
167                 (save-excursion
168                   (set-buffer gnus-article-buffer)
169                   (if interactive
170                       (call-interactively ',afunc)
171                     (apply ',afunc args)))))))
172    '(article-hide-headers
173      article-hide-boring-headers
174      article-treat-overstrike
175      (article-fill . gnus-article-word-wrap)
176      article-remove-cr
177      article-remove-trailing-blank-lines
178      article-display-x-face
179      article-de-quoted-unreadable
180      article-mime-decode-quoted-printable
181      article-hide-pgp
182      article-hide-pem
183      article-hide-signature
184      article-strip-leading-blank-lines
185      article-date-local
186      article-date-original
187      article-date-lapsed
188      article-emphasize
189      (article-show-all . gnus-article-show-all-headers))))
190
191 (defalias 'gnus-decode-rfc1522 'article-decode-rfc1522)
192
193 ;;; Saving functions.
194
195 (defun gnus-article-save (save-buffer file)
196   "Save the currently selected article."
197   (unless gnus-save-all-headers
198     ;; Remove headers accoring to `gnus-saved-headers'.
199     (let ((gnus-visible-headers
200            (or gnus-saved-headers gnus-visible-headers))
201           (gnus-article-buffer save-buffer))
202       (gnus-article-hide-headers 1 t)))
203   (save-window-excursion
204     (if (not gnus-default-article-saver)
205         (error "No default saver is defined.")
206       ;; !!! Magic!  The saving functions all save
207       ;; `gnus-original-article-buffer' (or so they think),
208       ;; but we bind that variable to our save-buffer.
209       (set-buffer gnus-article-buffer)
210       (let ((gnus-original-article-buffer save-buffer))
211         (set-buffer gnus-summary-buffer)
212         (funcall
213          gnus-default-article-saver
214          (cond
215           ((not gnus-prompt-before-saving)
216            'default)
217           ((eq gnus-prompt-before-saving 'always)
218            nil)
219           (t file)))))))
220
221 (defun gnus-read-save-file-name (prompt default-name)
222   (let* ((split-name (gnus-get-split-value gnus-split-methods))
223          (file
224           ;; Let the split methods have their say.
225           (cond
226            ;; No split name was found.
227            ((null split-name)
228             (read-file-name
229              (concat prompt " (default "
230                      (file-name-nondirectory default-name) ") ")
231              (file-name-directory default-name)
232              default-name))
233            ;; A single split name was found
234            ((= 1 (length split-name))
235             (let* ((name (car split-name))
236                    (dir (cond ((file-directory-p name)
237                                (file-name-as-directory name))
238                               ((file-exists-p name) name)
239                               (t gnus-article-save-directory))))
240               (read-file-name
241                (concat prompt " (default " name ") ")
242                dir name)))
243            ;; A list of splits was found.
244            (t
245             (setq split-name (nreverse split-name))
246             (let (result)
247               (let ((file-name-history (nconc split-name file-name-history)))
248                 (setq result
249                       (read-file-name
250                        (concat prompt " (`M-p' for defaults) ")
251                        gnus-article-save-directory
252                        (car split-name))))
253               (car (push result file-name-history)))))))
254     ;; Create the directory.
255     (unless (equal (directory-file-name file) file)
256       (make-directory (file-name-directory file) t))
257     ;; If we have read a directory, we append the default file name.
258     (when (file-directory-p file)
259       (setq file (concat (file-name-as-directory file)
260                          (file-name-nondirectory default-name))))
261     ;; Possibly translate some characters.
262     (nnheader-translate-file-chars file)))
263
264 (defun gnus-article-archive-name (group)
265   "Return the first instance of an \"Archive-name\" in the current buffer."
266   (let ((case-fold-search t))
267     (when (re-search-forward "archive-name: *\\([^ \n\t]+\\)[ \t]*$" nil t)
268       (nnheader-concat gnus-article-save-directory
269                        (match-string 1)))))
270
271 (defun gnus-summary-save-in-rmail (&optional filename)
272   "Append this article to Rmail file.
273 Optional argument FILENAME specifies file name.
274 Directory to save to is default to `gnus-article-save-directory'."
275   (interactive)
276   (gnus-set-global-variables)
277   (let ((default-name
278           (funcall gnus-rmail-save-name gnus-newsgroup-name
279                    gnus-current-headers gnus-newsgroup-last-rmail)))
280     (setq filename
281           (cond ((eq filename 'default)
282                  default-name)
283                 (filename filename)
284                 (t (gnus-read-save-file-name
285                     "Save in rmail file:" default-name))))
286     (make-directory (file-name-directory filename) t)
287     (gnus-eval-in-buffer-window gnus-original-article-buffer
288       (save-excursion
289         (save-restriction
290           (widen)
291           (gnus-output-to-rmail filename))))
292     ;; Remember the directory name to save articles
293     (setq gnus-newsgroup-last-rmail filename)))
294
295 (defun gnus-summary-save-in-mail (&optional filename)
296   "Append this article to Unix mail file.
297 Optional argument FILENAME specifies file name.
298 Directory to save to is default to `gnus-article-save-directory'."
299   (interactive)
300   (gnus-set-global-variables)
301   (let ((default-name
302           (funcall gnus-mail-save-name gnus-newsgroup-name
303                    gnus-current-headers gnus-newsgroup-last-mail)))
304     (setq filename
305           (cond ((eq filename 'default)
306                  default-name)
307                 (filename filename)
308                 (t (gnus-read-save-file-name
309                     "Save in Unix mail file:" default-name))))
310     (setq filename
311           (expand-file-name filename
312                             (and default-name
313                                  (file-name-directory default-name))))
314     (make-directory (file-name-directory filename) t)
315     (gnus-eval-in-buffer-window gnus-original-article-buffer
316       (save-excursion
317         (save-restriction
318           (widen)
319           (if (and (file-readable-p filename) (mail-file-babyl-p filename))
320               (gnus-output-to-rmail filename)
321             (let ((mail-use-rfc822 t))
322               (rmail-output filename 1 t t))))))
323     ;; Remember the directory name to save articles.
324     (setq gnus-newsgroup-last-mail filename)))
325
326 (defun gnus-summary-save-in-file (&optional filename)
327   "Append this article to file.
328 Optional argument FILENAME specifies file name.
329 Directory to save to is default to `gnus-article-save-directory'."
330   (interactive)
331   (gnus-set-global-variables)
332   (let ((default-name
333           (funcall gnus-file-save-name gnus-newsgroup-name
334                    gnus-current-headers gnus-newsgroup-last-file)))
335     (setq filename
336           (cond ((eq filename 'default)
337                  default-name)
338                 (filename filename)
339                 (t (gnus-read-save-file-name
340                     "Save in file:" default-name))))
341     (make-directory (file-name-directory filename) t)
342     (gnus-eval-in-buffer-window gnus-original-article-buffer
343       (save-excursion
344         (save-restriction
345           (widen)
346           (gnus-output-to-file filename))))
347     ;; Remember the directory name to save articles.
348     (setq gnus-newsgroup-last-file filename)))
349
350 (defun gnus-summary-save-body-in-file (&optional filename)
351   "Append this article body to a file.
352 Optional argument FILENAME specifies file name.
353 The directory to save in defaults to `gnus-article-save-directory'."
354   (interactive)
355   (gnus-set-global-variables)
356   (let ((default-name
357           (funcall gnus-file-save-name gnus-newsgroup-name
358                    gnus-current-headers gnus-newsgroup-last-file)))
359     (setq filename
360           (cond ((eq filename 'default)
361                  default-name)
362                 (filename filename)
363                 (t (gnus-read-save-file-name
364                     "Save body in file:" default-name))))
365     (make-directory (file-name-directory filename) t)
366     (gnus-eval-in-buffer-window gnus-original-article-buffer
367       (save-excursion
368         (save-restriction
369           (widen)
370           (goto-char (point-min))
371           (and (search-forward "\n\n" nil t)
372                (narrow-to-region (point) (point-max)))
373           (gnus-output-to-file filename))))
374     ;; Remember the directory name to save articles.
375     (setq gnus-newsgroup-last-file filename)))
376
377 (defun gnus-summary-save-in-pipe (&optional command)
378   "Pipe this article to subprocess."
379   (interactive)
380   (gnus-set-global-variables)
381   (setq command
382         (cond ((eq command 'default)
383                gnus-last-shell-command)
384               (command command)
385               (t (read-string "Shell command on article: "
386                               gnus-last-shell-command))))
387   (if (string-equal command "")
388       (setq command gnus-last-shell-command))
389   (gnus-eval-in-buffer-window gnus-article-buffer
390     (save-restriction
391       (widen)
392       (shell-command-on-region (point-min) (point-max) command nil)))
393   (setq gnus-last-shell-command command))
394
395 ;;; Article file names when saving.
396
397 (defun gnus-capitalize-newsgroup (newsgroup)
398   "Capitalize NEWSGROUP name."
399   (and (not (zerop (length newsgroup)))
400        (concat (char-to-string (upcase (aref newsgroup 0)))
401                (substring newsgroup 1))))
402
403 (defun gnus-Numeric-save-name (newsgroup headers &optional last-file)
404   "Generate file name from NEWSGROUP, HEADERS, and optional LAST-FILE.
405 If variable `gnus-use-long-file-name' is nil, it is ~/News/News.group/num.
406 Otherwise, it is like ~/News/news/group/num."
407   (let ((default
408           (expand-file-name
409            (concat (if (gnus-use-long-file-name 'not-save)
410                        (gnus-capitalize-newsgroup newsgroup)
411                      (gnus-newsgroup-directory-form newsgroup))
412                    "/" (int-to-string (mail-header-number headers)))
413            gnus-article-save-directory)))
414     (if (and last-file
415              (string-equal (file-name-directory default)
416                            (file-name-directory last-file))
417              (string-match "^[0-9]+$" (file-name-nondirectory last-file)))
418         default
419       (or last-file default))))
420
421 (defun gnus-numeric-save-name (newsgroup headers &optional last-file)
422   "Generate file name from NEWSGROUP, HEADERS, and optional LAST-FILE.
423 If variable `gnus-use-long-file-name' is non-nil, it is
424 ~/News/news.group/num.  Otherwise, it is like ~/News/news/group/num."
425   (let ((default
426           (expand-file-name
427            (concat (if (gnus-use-long-file-name 'not-save)
428                        newsgroup
429                      (gnus-newsgroup-directory-form newsgroup))
430                    "/" (int-to-string (mail-header-number headers)))
431            gnus-article-save-directory)))
432     (if (and last-file
433              (string-equal (file-name-directory default)
434                            (file-name-directory last-file))
435              (string-match "^[0-9]+$" (file-name-nondirectory last-file)))
436         default
437       (or last-file default))))
438
439 (defun gnus-Plain-save-name (newsgroup headers &optional last-file)
440   "Generate file name from NEWSGROUP, HEADERS, and optional LAST-FILE.
441 If variable `gnus-use-long-file-name' is non-nil, it is
442 ~/News/News.group.  Otherwise, it is like ~/News/news/group/news."
443   (or last-file
444       (expand-file-name
445        (if (gnus-use-long-file-name 'not-save)
446            (gnus-capitalize-newsgroup newsgroup)
447          (concat (gnus-newsgroup-directory-form newsgroup) "/news"))
448        gnus-article-save-directory)))
449
450 (defun gnus-plain-save-name (newsgroup headers &optional last-file)
451   "Generate file name from NEWSGROUP, HEADERS, and optional LAST-FILE.
452 If variable `gnus-use-long-file-name' is non-nil, it is
453 ~/News/news.group.  Otherwise, it is like ~/News/news/group/news."
454   (or last-file
455       (expand-file-name
456        (if (gnus-use-long-file-name 'not-save)
457            newsgroup
458          (concat (gnus-newsgroup-directory-form newsgroup) "/news"))
459        gnus-article-save-directory)))
460
461 \f
462 ;;;
463 ;;; Gnus article mode
464 ;;;
465
466 (put 'gnus-article-mode 'mode-class 'special)
467
468 (when t
469   (gnus-define-keys gnus-article-mode-map
470     " " gnus-article-goto-next-page
471     "\177" gnus-article-goto-prev-page
472     [delete] gnus-article-goto-prev-page
473     "\C-c^" gnus-article-refer-article
474     "h" gnus-article-show-summary
475     "s" gnus-article-show-summary
476     "\C-c\C-m" gnus-article-mail
477     "?" gnus-article-describe-briefly
478     gnus-mouse-2 gnus-article-push-button
479     "\r" gnus-article-press-button
480     "\t" gnus-article-next-button
481     "\M-\t" gnus-article-prev-button
482     "e" gnus-article-edit
483     "<" beginning-of-buffer
484     ">" end-of-buffer
485     "\C-c\C-i" gnus-info-find-node
486     "\C-c\C-b" gnus-bug)
487
488   (substitute-key-definition
489    'undefined 'gnus-article-read-summary-keys gnus-article-mode-map))
490
491 (defun gnus-article-mode ()
492   "Major mode for displaying an article.
493
494 All normal editing commands are switched off.
495
496 The following commands are available:
497
498 \\<gnus-article-mode-map>
499 \\[gnus-article-next-page]\t Scroll the article one page forwards
500 \\[gnus-article-prev-page]\t Scroll the article one page backwards
501 \\[gnus-article-refer-article]\t Go to the article referred to by an article id near point
502 \\[gnus-article-show-summary]\t Display the summary buffer
503 \\[gnus-article-mail]\t Send a reply to the address near point
504 \\[gnus-article-describe-briefly]\t Describe the current mode briefly
505 \\[gnus-info-find-node]\t Go to the Gnus info node"
506   (interactive)
507   (when (and menu-bar-mode
508              (gnus-visual-p 'article-menu 'menu))
509     (gnus-article-make-menu-bar))
510   (kill-all-local-variables)
511   (gnus-simplify-mode-line)
512   (setq mode-name "Article")
513   (setq major-mode 'gnus-article-mode)
514   (make-local-variable 'minor-mode-alist)
515   (or (assq 'gnus-show-mime minor-mode-alist)
516       (setq minor-mode-alist
517             (cons (list 'gnus-show-mime " MIME") minor-mode-alist)))
518   (use-local-map gnus-article-mode-map)
519   (gnus-update-format-specifications nil 'article-mode)
520   (make-local-variable 'page-delimiter)
521   (setq page-delimiter gnus-page-delimiter)
522   (buffer-disable-undo (current-buffer))
523   (setq buffer-read-only t)             ;Disable modification
524   (run-hooks 'gnus-article-mode-hook))
525
526 (defun gnus-article-setup-buffer ()
527   "Initialize the article buffer."
528   (let* ((name (if gnus-single-article-buffer "*Article*"
529                  (concat "*Article " gnus-newsgroup-name "*")))
530          (original
531           (progn (string-match "\\*Article" name)
532                  (concat " *Original Article"
533                          (substring name (match-end 0))))))
534     (setq gnus-article-buffer name)
535     (setq gnus-original-article-buffer original)
536     ;; This might be a variable local to the summary buffer.
537     (unless gnus-single-article-buffer
538       (save-excursion
539         (set-buffer gnus-summary-buffer)
540         (setq gnus-article-buffer name)
541         (setq gnus-original-article-buffer original)
542         (gnus-set-global-variables))
543       (make-local-variable 'gnus-summary-buffer))
544     ;; Init original article buffer.
545     (save-excursion
546       (set-buffer (get-buffer-create gnus-original-article-buffer))
547       (buffer-disable-undo (current-buffer))
548       (setq major-mode 'gnus-original-article-mode)
549       (gnus-add-current-to-buffer-list)
550       (make-local-variable 'gnus-original-article))
551     (if (get-buffer name)
552         (save-excursion
553           (set-buffer name)
554           (buffer-disable-undo (current-buffer))
555           (setq buffer-read-only t)
556           (gnus-add-current-to-buffer-list)
557           (or (eq major-mode 'gnus-article-mode)
558               (gnus-article-mode))
559           (current-buffer))
560       (save-excursion
561         (set-buffer (get-buffer-create name))
562         (gnus-add-current-to-buffer-list)
563         (gnus-article-mode)
564         (current-buffer)))))
565
566 ;; Set article window start at LINE, where LINE is the number of lines
567 ;; from the head of the article.
568 (defun gnus-article-set-window-start (&optional line)
569   (set-window-start
570    (get-buffer-window gnus-article-buffer t)
571    (save-excursion
572      (set-buffer gnus-article-buffer)
573      (goto-char (point-min))
574      (if (not line)
575          (point-min)
576        (gnus-message 6 "Moved to bookmark")
577        (search-forward "\n\n" nil t)
578        (forward-line line)
579        (point)))))
580
581 (defun gnus-article-prepare (article &optional all-headers header)
582   "Prepare ARTICLE in article mode buffer.
583 ARTICLE should either be an article number or a Message-ID.
584 If ARTICLE is an id, HEADER should be the article headers.
585 If ALL-HEADERS is non-nil, no headers are hidden."
586   (save-excursion
587     ;; Make sure we start in a summary buffer.
588     (unless (eq major-mode 'gnus-summary-mode)
589       (set-buffer gnus-summary-buffer))
590     (setq gnus-summary-buffer (current-buffer))
591     ;; Make sure the connection to the server is alive.
592     (unless (gnus-server-opened
593              (gnus-find-method-for-group gnus-newsgroup-name))
594       (gnus-check-server (gnus-find-method-for-group gnus-newsgroup-name))
595       (gnus-request-group gnus-newsgroup-name t))
596     (let* ((article (if header (mail-header-number header) article))
597            (summary-buffer (current-buffer))
598            (internal-hook gnus-article-internal-prepare-hook)
599            (group gnus-newsgroup-name)
600            result)
601       (save-excursion
602         (gnus-article-setup-buffer)
603         (set-buffer gnus-article-buffer)
604         ;; Deactivate active regions.
605         (when (and (boundp 'transient-mark-mode)
606                    transient-mark-mode)
607           (setq mark-active nil))
608         (if (not (setq result (let ((buffer-read-only nil))
609                                 (gnus-request-article-this-buffer
610                                  article group))))
611             ;; There is no such article.
612             (save-excursion
613               (when (and (numberp article)
614                          (not (memq article gnus-newsgroup-sparse)))
615                 (setq gnus-article-current
616                       (cons gnus-newsgroup-name article))
617                 (set-buffer gnus-summary-buffer)
618                 (setq gnus-current-article article)
619                 (gnus-summary-mark-article article gnus-canceled-mark))
620               (unless (memq article gnus-newsgroup-sparse)
621                 (gnus-error
622                  1 "No such article (may have expired or been canceled)")))
623           (if (or (eq result 'pseudo) (eq result 'nneething))
624               (progn
625                 (save-excursion
626                   (set-buffer summary-buffer)
627                   (setq gnus-last-article gnus-current-article
628                         gnus-newsgroup-history (cons gnus-current-article
629                                                      gnus-newsgroup-history)
630                         gnus-current-article 0
631                         gnus-current-headers nil
632                         gnus-article-current nil)
633                   (if (eq result 'nneething)
634                       (gnus-configure-windows 'summary)
635                     (gnus-configure-windows 'article))
636                   (gnus-set-global-variables))
637                 (gnus-set-mode-line 'article))
638             ;; The result from the `request' was an actual article -
639             ;; or at least some text that is now displayed in the
640             ;; article buffer.
641             (if (and (numberp article)
642                      (not (eq article gnus-current-article)))
643                 ;; Seems like a new article has been selected.
644                 ;; `gnus-current-article' must be an article number.
645                 (save-excursion
646                   (set-buffer summary-buffer)
647                   (setq gnus-last-article gnus-current-article
648                         gnus-newsgroup-history (cons gnus-current-article
649                                                      gnus-newsgroup-history)
650                         gnus-current-article article
651                         gnus-current-headers
652                         (gnus-summary-article-header gnus-current-article)
653                         gnus-article-current
654                         (cons gnus-newsgroup-name gnus-current-article))
655                   (unless (vectorp gnus-current-headers)
656                     (setq gnus-current-headers nil))
657                   (gnus-summary-show-thread)
658                   (run-hooks 'gnus-mark-article-hook)
659                   (gnus-set-mode-line 'summary)
660                   (and (gnus-visual-p 'article-highlight 'highlight)
661                        (run-hooks 'gnus-visual-mark-article-hook))
662                   ;; Set the global newsgroup variables here.
663                   ;; Suggested by Jim Sisolak
664                   ;; <sisolak@trans4.neep.wisc.edu>.
665                   (gnus-set-global-variables)
666                   (setq gnus-have-all-headers
667                         (or all-headers gnus-show-all-headers))
668                   (and gnus-use-cache
669                        (vectorp (gnus-summary-article-header article))
670                        (gnus-cache-possibly-enter-article
671                         group article
672                         (gnus-summary-article-header article)
673                         (memq article gnus-newsgroup-marked)
674                         (memq article gnus-newsgroup-dormant)
675                         (memq article gnus-newsgroup-unreads)))))
676             (when (or (numberp article)
677                       (stringp article))
678               ;; Hooks for getting information from the article.
679               ;; This hook must be called before being narrowed.
680               (let (buffer-read-only)
681                 (run-hooks 'internal-hook)
682                 (run-hooks 'gnus-article-prepare-hook)
683                 ;; Decode MIME message.
684                 (if gnus-show-mime
685                     (if (or (not gnus-strict-mime)
686                             (gnus-fetch-field "Mime-Version"))
687                         (funcall gnus-show-mime-method)
688                       (funcall gnus-decode-encoded-word-method)))
689                 ;; Perform the article display hooks.
690                 (run-hooks 'gnus-article-display-hook))
691               ;; Do page break.
692               (goto-char (point-min))
693               (and gnus-break-pages (gnus-narrow-to-page)))
694             (gnus-set-mode-line 'article)
695             (gnus-configure-windows 'article)
696             (goto-char (point-min))
697             t))))))
698
699 (defun gnus-article-wash-status ()
700   "Return a string which display status of article washing."
701   (save-excursion
702     (set-buffer gnus-article-buffer)
703     (let ((cite (article-hidden-text-p 'cite))
704           (headers (article-hidden-text-p 'headers))
705           (boring (article-hidden-text-p 'boring-headers))
706           (pgp (article-hidden-text-p 'pgp))
707           (pem (article-hidden-text-p 'pem))
708           (signature (article-hidden-text-p 'signature))
709           (overstrike (article-hidden-text-p 'overstrike))
710           (emphasis (article-hidden-text-p 'emphasis))
711           (mime gnus-show-mime))
712       (format "%c%c%c%c%c%c%c"
713               (if cite ?c ? )
714               (if (or headers boring) ?h ? )
715               (if (or pgp pem) ?p ? )
716               (if signature ?s ? )
717               (if overstrike ?o ? )
718               (if mime ?m ? )
719               (if emphasis ?e ? )))))
720
721 (defun gnus-article-hide-headers-if-wanted ()
722   "Hide unwanted headers if `gnus-have-all-headers' is nil.
723 Provided for backwards compatibility."
724   (or (save-excursion (set-buffer gnus-summary-buffer) gnus-have-all-headers)
725       gnus-inhibit-hiding
726       (gnus-article-hide-headers)))
727
728 ;;; Article savers.
729
730 (defun gnus-output-to-rmail (file-name)
731   "Append the current article to an Rmail file named FILE-NAME."
732   (require 'rmail)
733   ;; Most of these codes are borrowed from rmailout.el.
734   (setq file-name (expand-file-name file-name))
735   (setq rmail-default-rmail-file file-name)
736   (let ((artbuf (current-buffer))
737         (tmpbuf (get-buffer-create " *Gnus-output*")))
738     (save-excursion
739       (or (get-file-buffer file-name)
740           (file-exists-p file-name)
741           (if (gnus-yes-or-no-p
742                (concat "\"" file-name "\" does not exist, create it? "))
743               (let ((file-buffer (create-file-buffer file-name)))
744                 (save-excursion
745                   (set-buffer file-buffer)
746                   (rmail-insert-rmail-file-header)
747                   (let ((require-final-newline nil))
748                     (write-region (point-min) (point-max) file-name t 1)))
749                 (kill-buffer file-buffer))
750             (error "Output file does not exist")))
751       (set-buffer tmpbuf)
752       (buffer-disable-undo (current-buffer))
753       (erase-buffer)
754       (insert-buffer-substring artbuf)
755       (gnus-convert-article-to-rmail)
756       ;; Decide whether to append to a file or to an Emacs buffer.
757       (let ((outbuf (get-file-buffer file-name)))
758         (if (not outbuf)
759             (append-to-file (point-min) (point-max) file-name)
760           ;; File has been visited, in buffer OUTBUF.
761           (set-buffer outbuf)
762           (let ((buffer-read-only nil)
763                 (msg (and (boundp 'rmail-current-message)
764                           (symbol-value 'rmail-current-message))))
765             ;; If MSG is non-nil, buffer is in RMAIL mode.
766             (if msg
767                 (progn (widen)
768                        (narrow-to-region (point-max) (point-max))))
769             (insert-buffer-substring tmpbuf)
770             (if msg
771                 (progn
772                   (goto-char (point-min))
773                   (widen)
774                   (search-backward "\^_")
775                   (narrow-to-region (point) (point-max))
776                   (goto-char (1+ (point-min)))
777                   (rmail-count-new-messages t)
778                   (rmail-show-message msg)))))))
779     (kill-buffer tmpbuf)))
780
781 (defun gnus-output-to-file (file-name)
782   "Append the current article to a file named FILE-NAME."
783   (let ((artbuf (current-buffer)))
784     (nnheader-temp-write nil
785       (insert-buffer-substring artbuf)
786       ;; Append newline at end of the buffer as separator, and then
787       ;; save it to file.
788       (goto-char (point-max))
789       (insert "\n")
790       (append-to-file (point-min) (point-max) file-name))))
791
792 (defun gnus-convert-article-to-rmail ()
793   "Convert article in current buffer to Rmail message format."
794   (let ((buffer-read-only nil))
795     ;; Convert article directly into Babyl format.
796     ;; Suggested by Rob Austein <sra@lcs.mit.edu>
797     (goto-char (point-min))
798     (insert "\^L\n0, unseen,,\n*** EOOH ***\n")
799     (while (search-forward "\n\^_" nil t) ;single char
800       (replace-match "\n^_" t t))       ;2 chars: "^" and "_"
801     (goto-char (point-max))
802     (insert "\^_")))
803
804 (defun gnus-narrow-to-page (&optional arg)
805   "Narrow the article buffer to a page.
806 If given a numerical ARG, move forward ARG pages."
807   (interactive "P")
808   (setq arg (if arg (prefix-numeric-value arg) 0))
809   (save-excursion
810     (set-buffer gnus-article-buffer)
811     (goto-char (point-min))
812     (widen)
813     ;; Remove any old next/prev buttons.
814     (when (gnus-visual-p 'page-marker)
815       (let ((buffer-read-only nil))
816         (gnus-remove-text-with-property 'gnus-prev)
817         (gnus-remove-text-with-property 'gnus-next)))
818     (when
819         (cond ((< arg 0)
820                (re-search-backward page-delimiter nil 'move (1+ (abs arg))))
821               ((> arg 0)
822                (re-search-forward page-delimiter nil 'move arg)))
823       (goto-char (match-end 0)))
824     (narrow-to-region
825      (point)
826      (if (re-search-forward page-delimiter nil 'move)
827          (match-beginning 0)
828        (point)))
829     (when (and (gnus-visual-p 'page-marker)
830                (not (= (point-min) 1)))
831       (save-excursion
832         (goto-char (point-min))
833         (gnus-insert-prev-page-button)))
834     (when (and (gnus-visual-p 'page-marker)
835                (< (+ (point-max) 2) (buffer-size)))
836       (save-excursion
837         (goto-char (point-max))
838         (gnus-insert-next-page-button)))))
839
840 ;; Article mode commands
841
842 (defun gnus-article-goto-next-page ()
843   "Show the next page of the article."
844   (interactive)
845   (when (gnus-article-next-page)
846     (gnus-article-read-summary-keys nil (gnus-character-to-event ?n))))
847
848 (defun gnus-article-goto-prev-page ()
849   "Show the next page of the article."
850   (interactive)
851   (if (bobp) (gnus-article-read-summary-keys nil (gnus-character-to-event ?n))
852     (gnus-article-prev-page nil)))
853
854 (defun gnus-article-next-page (&optional lines)
855   "Show the next page of the current article.
856 If end of article, return non-nil.  Otherwise return nil.
857 Argument LINES specifies lines to be scrolled up."
858   (interactive "p")
859   (move-to-window-line -1)
860   ;; Fixed by enami@ptgd.sony.co.jp (enami tsugutomo)
861   (if (save-excursion
862         (end-of-line)
863         (and (pos-visible-in-window-p)  ;Not continuation line.
864              (eobp)))
865       ;; Nothing in this page.
866       (if (or (not gnus-break-pages)
867               (save-excursion
868                 (save-restriction
869                   (widen) (forward-line 1) (eobp)))) ;Real end-of-buffer?
870           t                             ;Nothing more.
871         (gnus-narrow-to-page 1)         ;Go to next page.
872         nil)
873     ;; More in this page.
874     (condition-case ()
875         (scroll-up lines)
876       (end-of-buffer
877        ;; Long lines may cause an end-of-buffer error.
878        (goto-char (point-max))))
879     (move-to-window-line 0)
880     nil))
881
882 (defun gnus-article-prev-page (&optional lines)
883   "Show previous page of current article.
884 Argument LINES specifies lines to be scrolled down."
885   (interactive "p")
886   (move-to-window-line 0)
887   (if (and gnus-break-pages
888            (bobp)
889            (not (save-restriction (widen) (bobp)))) ;Real beginning-of-buffer?
890       (progn
891         (gnus-narrow-to-page -1)        ;Go to previous page.
892         (goto-char (point-max))
893         (recenter -1))
894     (prog1
895         (condition-case ()
896             (scroll-down lines)
897           (error nil))
898       (move-to-window-line 0))))
899
900 (defun gnus-article-refer-article ()
901   "Read article specified by message-id around point."
902   (interactive)
903   (let ((point (point)))
904     (search-forward ">" nil t)          ;Move point to end of "<....>".
905     (if (re-search-backward "\\(<[^<> \t\n]+>\\)" nil t)
906         (let ((message-id (match-string 1)))
907           (goto-char point)
908           (set-buffer gnus-summary-buffer)
909           (gnus-summary-refer-article message-id))
910       (goto-char (point))
911       (error "No references around point"))))
912
913 (defun gnus-article-show-summary ()
914   "Reconfigure windows to show summary buffer."
915   (interactive)
916   (gnus-configure-windows 'article)
917   (gnus-summary-goto-subject gnus-current-article))
918
919 (defun gnus-article-describe-briefly ()
920   "Describe article mode commands briefly."
921   (interactive)
922   (gnus-message 6
923                 (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")))
924
925 (defun gnus-article-summary-command ()
926   "Execute the last keystroke in the summary buffer."
927   (interactive)
928   (let ((obuf (current-buffer))
929         (owin (current-window-configuration))
930         func)
931     (switch-to-buffer gnus-summary-buffer 'norecord)
932     (setq func (lookup-key (current-local-map) (this-command-keys)))
933     (call-interactively func)
934     (set-buffer obuf)
935     (set-window-configuration owin)
936     (set-window-point (get-buffer-window (current-buffer)) (point))))
937
938 (defun gnus-article-summary-command-nosave ()
939   "Execute the last keystroke in the summary buffer."
940   (interactive)
941   (let (func)
942     (pop-to-buffer gnus-summary-buffer 'norecord)
943     (setq func (lookup-key (current-local-map) (this-command-keys)))
944     (call-interactively func)))
945
946 (defun gnus-article-read-summary-keys (&optional arg key not-restore-window)
947   "Read a summary buffer key sequence and execute it from the article buffer."
948   (interactive "P")
949   (let ((nosaves
950          '("q" "Q"  "c" "r" "R" "\C-c\C-f" "m"  "a" "f" "F"
951            "Zc" "ZC" "ZE" "ZQ" "ZZ" "Zn" "ZR" "ZG" "ZN" "ZP"
952            "=" "^" "\M-^" "|"))
953         (nosave-but-article
954          '("A\r"))
955         keys)
956     (save-excursion
957       (set-buffer gnus-summary-buffer)
958       (push (or key last-command-event) unread-command-events)
959       (setq keys (read-key-sequence nil)))
960     (message "")
961
962     (if (or (member keys nosaves)
963             (member keys nosave-but-article))
964         (let (func)
965           (save-window-excursion
966             (pop-to-buffer gnus-summary-buffer 'norecord)
967             (setq func (lookup-key (current-local-map) keys)))
968           (if (not func)
969               (ding)
970             (set-buffer gnus-summary-buffer)
971             (call-interactively func))
972           (when (member keys nosave-but-article)
973             (pop-to-buffer gnus-article-buffer 'norecord)))
974       (let ((obuf (current-buffer))
975             (owin (current-window-configuration))
976             (opoint (point))
977             func in-buffer)
978         (if not-restore-window
979             (pop-to-buffer gnus-summary-buffer 'norecord)
980           (switch-to-buffer gnus-summary-buffer 'norecord))
981         (setq in-buffer (current-buffer))
982         (if (setq func (lookup-key (current-local-map) keys))
983             (call-interactively func)
984           (ding))
985         (when (eq in-buffer (current-buffer))
986           (set-buffer obuf)
987           (unless not-restore-window
988             (set-window-configuration owin))
989           (set-window-point (get-buffer-window (current-buffer)) opoint))))))
990
991 (defun gnus-article-hide (&optional arg force)
992   "Hide all the gruft in the current article.
993 This means that PGP stuff, signatures, cited text and (some)
994 headers will be hidden.
995 If given a prefix, show the hidden text instead."
996   (interactive (list current-prefix-arg 'force))
997   (gnus-article-hide-headers arg)
998   (gnus-article-hide-pgp arg)
999   (gnus-article-hide-citation-maybe arg force)
1000   (gnus-article-hide-signature arg))
1001
1002 (defun gnus-article-maybe-highlight ()
1003   "Do some article highlighting if `article-visual' is non-nil."
1004   (if (gnus-visual-p 'article-highlight 'highlight)
1005       (gnus-article-highlight-some)))
1006
1007 (defun gnus-request-article-this-buffer (article group)
1008   "Get an article and insert it into this buffer."
1009   (let (do-update-line)
1010     (prog1
1011         (save-excursion
1012           (erase-buffer)
1013           (gnus-kill-all-overlays)
1014           (setq group (or group gnus-newsgroup-name))
1015
1016           ;; Open server if it has closed.
1017           (gnus-check-server (gnus-find-method-for-group group))
1018
1019           ;; Using `gnus-request-article' directly will insert the article into
1020           ;; `nntp-server-buffer' - so we'll save some time by not having to
1021           ;; copy it from the server buffer into the article buffer.
1022
1023           ;; We only request an article by message-id when we do not have the
1024           ;; headers for it, so we'll have to get those.
1025           (when (stringp article)
1026             (let ((gnus-override-method gnus-refer-article-method))
1027               (gnus-read-header article)))
1028
1029           ;; If the article number is negative, that means that this article
1030           ;; doesn't belong in this newsgroup (possibly), so we find its
1031           ;; message-id and request it by id instead of number.
1032           (when (and (numberp article)
1033                      gnus-summary-buffer
1034                      (get-buffer gnus-summary-buffer)
1035                      (buffer-name (get-buffer gnus-summary-buffer)))
1036             (save-excursion
1037               (set-buffer gnus-summary-buffer)
1038               (let ((header (gnus-summary-article-header article)))
1039                 (if (< article 0)
1040                     (cond 
1041                      ((memq article gnus-newsgroup-sparse)
1042                       ;; This is a sparse gap article.
1043                       (setq do-update-line article)
1044                       (setq article (mail-header-id header))
1045                       (let ((gnus-override-method gnus-refer-article-method))
1046                         (gnus-read-header article))
1047                       (setq gnus-newsgroup-sparse
1048                             (delq article gnus-newsgroup-sparse)))
1049                      ((vectorp header)
1050                       ;; It's a real article.
1051                       (setq article (mail-header-id header)))
1052                      (t
1053                       ;; It is an extracted pseudo-article.
1054                       (setq article 'pseudo)
1055                       (gnus-request-pseudo-article header))))
1056                 
1057                 (let ((method (gnus-find-method-for-group 
1058                                gnus-newsgroup-name)))
1059                   (if (not (eq (car method) 'nneething))
1060                       ()
1061                     (let ((dir (concat (file-name-as-directory (nth 1 method))
1062                                        (mail-header-subject header))))
1063                       (if (file-directory-p dir)
1064                           (progn
1065                             (setq article 'nneething)
1066                             (gnus-group-enter-directory dir)))))))))
1067
1068           (cond
1069            ;; Refuse to select canceled articles.
1070            ((and (numberp article)
1071                  gnus-summary-buffer
1072                  (get-buffer gnus-summary-buffer)
1073                  (buffer-name (get-buffer gnus-summary-buffer))
1074                  (eq (cdr (save-excursion
1075                             (set-buffer gnus-summary-buffer)
1076                             (assq article gnus-newsgroup-reads)))
1077                      gnus-canceled-mark))
1078             nil)
1079            ;; We first check `gnus-original-article-buffer'.
1080            ((and (get-buffer gnus-original-article-buffer)
1081                  (numberp article)
1082                  (save-excursion
1083                    (set-buffer gnus-original-article-buffer)
1084                    (and (equal (car gnus-original-article) group)
1085                         (eq (cdr gnus-original-article) article))))
1086             (insert-buffer-substring gnus-original-article-buffer)
1087             'article)
1088            ;; Check the backlog.
1089            ((and gnus-keep-backlog
1090                  (gnus-backlog-request-article group article (current-buffer)))
1091             'article)
1092            ;; Check asynchronous pre-fetch.
1093            ((gnus-async-request-fetched-article group article (current-buffer))
1094             (gnus-async-prefetch-next group article gnus-summary-buffer)
1095             'article)
1096            ;; Check the cache.
1097            ((and gnus-use-cache
1098                  (numberp article)
1099                  (gnus-cache-request-article article group))
1100             'article)
1101            ;; Get the article and put into the article buffer.
1102            ((or (stringp article) (numberp article))
1103             (let ((gnus-override-method
1104                    (and (stringp article) gnus-refer-article-method))
1105                   (buffer-read-only nil))
1106               (erase-buffer)
1107               (gnus-kill-all-overlays)
1108               (when (gnus-request-article article group (current-buffer))
1109                 (when (numberp article)
1110                   (gnus-async-prefetch-next group article gnus-summary-buffer)
1111                   (when gnus-keep-backlog
1112                     (gnus-backlog-enter-article 
1113                      group article (current-buffer))))
1114                 'article)))
1115            ;; It was a pseudo.
1116            (t article)))
1117
1118       ;; Take the article from the original article buffer
1119       ;; and place it in the buffer it's supposed to be in.
1120       (when (and (get-buffer gnus-article-buffer)
1121                  ;;(numberp article)
1122                  (equal (buffer-name (current-buffer))
1123                         (buffer-name (get-buffer gnus-article-buffer))))
1124         (save-excursion
1125           (if (get-buffer gnus-original-article-buffer)
1126               (set-buffer (get-buffer gnus-original-article-buffer))
1127             (set-buffer (get-buffer-create gnus-original-article-buffer))
1128             (buffer-disable-undo (current-buffer))
1129             (setq major-mode 'gnus-original-article-mode)
1130             (setq buffer-read-only t)
1131             (gnus-add-current-to-buffer-list))
1132           (let (buffer-read-only)
1133             (erase-buffer)
1134             (insert-buffer-substring gnus-article-buffer))
1135           (setq gnus-original-article (cons group article))))
1136     
1137       ;; Update sparse articles.
1138       (when (and do-update-line
1139                  (or (numberp article)
1140                      (stringp article)))
1141         (let ((buf (current-buffer)))
1142           (set-buffer gnus-summary-buffer)
1143           (gnus-summary-update-article do-update-line)
1144           (gnus-summary-goto-subject do-update-line nil t)
1145           (set-window-point (get-buffer-window (current-buffer) t)
1146                             (point))
1147           (set-buffer buf))))))
1148
1149 (defun gnus-article-date-ut (&optional type highlight)
1150   "Convert DATE date to universal time in the current article.
1151 If TYPE is `local', convert to local time; if it is `lapsed', output
1152 how much time has lapsed since DATE."
1153   (interactive (list 'ut t))
1154   (let ((headers (or gnus-current-headers (gnus-summary-article-header))))
1155     (save-excursion
1156       (set-buffer gnus-article-buffer)
1157       (article-date-ut type highlight headers))))
1158
1159 ;;;
1160 ;;; Article editing
1161 ;;;
1162
1163 (defvar gnus-article-edit-mode-hook nil
1164   "*Hook run in article edit mode buffers.")
1165
1166 (defvar gnus-article-edit-done-function nil)
1167
1168 (defvar gnus-article-edit-mode-map nil)
1169
1170 (unless gnus-article-edit-mode-map 
1171   (setq gnus-article-edit-mode-map (copy-keymap text-mode-map))
1172
1173   (gnus-define-keys gnus-article-edit-mode-map
1174     "\C-c\C-c" gnus-article-edit-done
1175     "\C-c\C-k" gnus-article-edit-exit)
1176
1177   (gnus-define-keys (gnus-article-edit-wash-map
1178                      "\C-c\C-w" gnus-article-edit-mode-map)
1179     "f" gnus-article-edit-full-stops))
1180
1181 (defun gnus-article-edit-mode ()
1182   "Major mode for editing articles.
1183 This is an extended text-mode.
1184
1185 \\{gnus-article-edit-mode-map}"
1186   (interactive)
1187   (kill-all-local-variables)
1188   (setq major-mode 'gnus-article-edit-mode)
1189   (setq mode-name "Article Edit")
1190   (use-local-map gnus-article-edit-mode-map)
1191   (make-local-variable 'gnus-article-edit-done-function)
1192   (make-local-variable 'gnus-prev-winconf)
1193   (setq buffer-read-only nil)
1194   (buffer-enable-undo)
1195   (widen)
1196   (run-hooks 'text-mode 'gnus-article-edit-mode-hook))
1197
1198 (defun gnus-article-edit (&optional force)
1199   "Edit the current article.
1200 This will have permanent effect only in mail groups.
1201 If FORCE is non-nil, allow editing of articles even in read-only
1202 groups."
1203   (interactive "P")
1204   (when (and (not force)
1205              (gnus-group-read-only-p))
1206     (error "The current newsgroup does not support article editing."))
1207   (gnus-article-edit-article
1208    `(lambda ()
1209       (gnus-summary-edit-article-done
1210        ,(or (mail-header-references gnus-current-headers) "")
1211        ,(gnus-group-read-only-p) ,gnus-summary-buffer))))
1212
1213 (defun gnus-article-edit-article (exit-func)
1214   "Start editing the contents of the current article buffer."
1215   (let ((winconf (current-window-configuration)))
1216     (set-buffer gnus-article-buffer)
1217     (gnus-article-edit-mode)
1218     (gnus-configure-windows 'edit-article)
1219     (setq gnus-article-edit-done-function exit-func)
1220     (setq gnus-prev-winconf winconf)
1221     (gnus-message 6 "C-c C-c to end edits")))
1222
1223 (defun gnus-article-edit-done ()
1224   "Update the article edits and exit."
1225   (interactive)
1226   (let ((func gnus-article-edit-done-function)
1227         (buf (current-buffer))
1228         (start (window-start)))
1229     (gnus-article-edit-exit)
1230     (let ((cur (current-buffer)))
1231       (save-excursion
1232         (set-buffer buf)
1233         (let ((buffer-read-only nil))
1234           (funcall func)))
1235       (set-buffer buf)
1236       (set-window-start (get-buffer-window buf) start)
1237       (set-window-point (get-buffer-window buf) (point)))))
1238
1239 (defun gnus-article-edit-exit ()
1240   "Exit the article editing without updating."
1241   (interactive)
1242   ;; We remove all text props from the article buffer.
1243   (let ((buf (format "%s" (buffer-string)))
1244         (curbuf (current-buffer))
1245         (p (point))
1246         (window-start (window-start)))
1247     (erase-buffer)
1248     (insert buf)
1249   (let ((winconf gnus-prev-winconf))
1250     (gnus-article-mode)
1251     ;; The cache and backlog have to be flushed somewhat.
1252     (when gnus-use-cache
1253       (gnus-cache-update-article        
1254        (car gnus-article-current) (cdr gnus-article-current)))
1255     (when gnus-keep-backlog
1256       (gnus-backlog-remove-article 
1257        (car gnus-article-current) (cdr gnus-article-current)))
1258     ;; Flush original article as well.
1259     (save-excursion
1260       (when (get-buffer gnus-original-article-buffer)
1261         (set-buffer gnus-original-article-buffer)
1262         (setq gnus-original-article nil)))
1263     (set-window-configuration winconf)
1264     ;; Tippy-toe some to make sure that point remains where it was.
1265     (let ((buf (current-buffer)))
1266       (set-buffer curbuf)
1267       (set-window-start (get-buffer-window (current-buffer)) window-start)
1268       (goto-char p)
1269       (set-buffer buf)))))
1270       
1271 (defun gnus-article-edit-full-stops ()
1272   "Interactively repair spacing at end of sentences."
1273   (interactive)
1274   (save-excursion
1275     (goto-char (point-min))
1276     (search-forward-regexp "^$" nil t)
1277     (let ((case-fold-search nil))
1278       (query-replace-regexp "\\([.!?][])}]* \\)\\([[({A-Z]\\)" "\\1 \\2"))))
1279
1280 (provide 'gnus-art)
1281
1282 ;;; gnus-art.el ends here