Merge from gnus--rel--5.10
[bpt/emacs.git] / lisp / gnus / gnus-art.el
index eed793d..90af074 100644 (file)
@@ -1,7 +1,7 @@
 ;;; gnus-art.el --- article mode commands for Gnus
 
 ;; Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004,
-;;   2005, 2006 Free Software Foundation, Inc.
+;;   2005, 2006, 2007 Free Software Foundation, Inc.
 
 ;; Author: Lars Magne Ingebrigtsen <larsi@gnus.org>
 ;; Keywords: news
@@ -15,7 +15,7 @@
 
 ;; GNU Emacs is distributed in the hope that it will be useful,
 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
-;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.         See the
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 ;; GNU General Public License for more details.
 
 ;; You should have received a copy of the GNU General Public License
@@ -242,7 +242,7 @@ regexp.  If it matches, the text in question is not a signature."
   :type '(choice (const nil)
                 (integer :value 200)
                 (number :value 4.0)
-                (function :value fun)
+                function
                 (regexp :value ".*"))
   :group 'gnus-article-signature)
 
@@ -265,7 +265,7 @@ regexp.  If it matches, the text in question is not a signature."
 display -"))
   "*String or function to be executed to display an X-Face header.
 If it is a string, the command will be executed in a sub-shell
-asynchronously.         The compressed face will be piped to this command."
+asynchronously.  The compressed face will be piped to this command."
   :type `(choice string
                 (function-item gnus-display-x-face-in-from)
                 function)
@@ -492,7 +492,10 @@ be fed to `format-time-string'."
   :group 'gnus-article-washing)
 
 (defcustom gnus-save-all-headers t
-  "*If non-nil, don't remove any headers before saving."
+  "*If non-nil, don't remove any headers before saving.
+This will be overridden by the `:headers' property that the symbol of
+the saver function, which is specified by `gnus-default-article-saver',
+might have."
   :group 'gnus-article-saving
   :type 'boolean)
 
@@ -513,14 +516,17 @@ each invocation of the saving commands."
   "Headers to keep if `gnus-save-all-headers' is nil.
 If `gnus-save-all-headers' is non-nil, this variable will be ignored.
 If that variable is nil, however, all headers that match this regexp
-will be kept while the rest will be deleted before saving."
+will be kept while the rest will be deleted before saving.  This and
+`gnus-save-all-headers' will be overridden by the `:headers' property
+that the symbol of the saver function, which is specified by
+`gnus-default-article-saver', might have."
   :group 'gnus-article-saving
   :type 'regexp)
 
 (defcustom gnus-default-article-saver 'gnus-summary-save-in-rmail
   "A function to save articles in your favourite format.
-The function must be interactively callable (in other words, it must
-be an Emacs command).
+The function will be called by way of the `gnus-summary-save-article'
+command, and friends such as `gnus-summary-save-article-rmail'.
 
 Gnus provides the following functions:
 
@@ -530,7 +536,28 @@ Gnus provides the following functions:
 * gnus-summary-save-in-file (article format)
 * gnus-summary-save-body-in-file (article body)
 * gnus-summary-save-in-vm (use VM's folder format)
-* gnus-summary-write-to-file (article format -- overwrite)."
+* gnus-summary-write-to-file (article format -- overwrite)
+* gnus-summary-write-body-to-file (article body -- overwrite)
+
+The symbol of each function may have the following properties:
+
+* :decode
+The value non-nil means save decoded articles.  This is meaningful
+only with `gnus-summary-save-in-file', `gnus-summary-save-body-in-file',
+`gnus-summary-write-to-file', and `gnus-summary-write-body-to-file'.
+
+* :function
+The value specifies an alternative function which appends, not
+overwrites, articles to a file.  This implies that when saving many
+articles at a time, `gnus-prompt-before-saving' is bound to t and all
+articles are saved in a single file.  This is meaningful only with
+`gnus-summary-write-to-file' and `gnus-summary-write-body-to-file'.
+
+* :headers
+The value specifies the symbol of a variable of which the value
+specifies headers to be saved.  If it is omitted,
+`gnus-save-all-headers' and `gnus-saved-headers' control what
+headers should be saved."
   :group 'gnus-article-saving
   :type '(radio (function-item gnus-summary-save-in-rmail)
                (function-item gnus-summary-save-in-mail)
@@ -539,8 +566,49 @@ Gnus provides the following functions:
                (function-item gnus-summary-save-body-in-file)
                (function-item gnus-summary-save-in-vm)
                (function-item gnus-summary-write-to-file)
+               (function-item gnus-summary-write-body-to-file)
                (function)))
 
+(defcustom gnus-article-save-coding-system
+  (or (and (mm-coding-system-p 'utf-8) 'utf-8)
+      (and (mm-coding-system-p 'iso-2022-7bit) 'iso-2022-7bit)
+      (and (mm-coding-system-p 'emacs-mule) 'emacs-mule)
+      (and (mm-coding-system-p 'escape-quoted) 'escape-quoted))
+  "Coding system used to save decoded articles to a file.
+
+The recommended coding systems are `utf-8', `iso-2022-7bit' and so on,
+which can safely encode any characters in text.  This is used by the
+commands including:
+
+* gnus-summary-save-article-file
+* gnus-summary-save-article-body-file
+* gnus-summary-write-article-file
+* gnus-summary-write-article-body-file
+
+and the functions to which you may set `gnus-default-article-saver':
+
+* gnus-summary-save-in-file
+* gnus-summary-save-body-in-file
+* gnus-summary-write-to-file
+* gnus-summary-write-body-to-file
+
+Those commands and functions save just text displayed in the article
+buffer to a file if the value of this variable is non-nil.  Note that
+buttonized MIME parts will be lost in a saved file in that case.
+Otherwise, raw articles will be saved."
+  :group 'gnus-article-saving
+  :type `(choice
+         :format "%{%t%}:\n %[Value Menu%] %v"
+         (const :tag "Save raw articles" nil)
+         ,@(delq nil
+                 (mapcar
+                  (lambda (arg) (if (mm-coding-system-p (nth 3 arg)) arg))
+                  '((const :tag "UTF-8" utf-8)
+                    (const :tag "iso-2022-7bit" iso-2022-7bit)
+                    (const :tag "Emacs internal" emacs-mule)
+                    (const :tag "escape-quoted" escape-quoted))))
+         (symbol :tag "Coding system")))
+
 (defcustom gnus-rmail-save-name 'gnus-plain-save-name
   "A function generating a file name to save articles in Rmail format.
 The function is called with NEWSGROUP, HEADERS, and optional LAST-FILE."
@@ -583,7 +651,7 @@ value is a list of possible files to save in if the match is non-nil.
 If the match is a string, it is used as a regexp match on the
 article.  If the match is a symbol, that symbol will be funcalled
 from the buffer of the article to be saved with the newsgroup as the
-parameter.  If it is a list, it will be evaled in the same buffer.
+parameter.  If it is a list, it will be evalled in the same buffer.
 
 If this form or function returns a string, this string will be used as
 a possible file name; and if it returns a non-nil list, that list will
@@ -785,6 +853,9 @@ be displayed by the first non-nil matching CONTENT face."
 (defvar gnus-decode-header-function 'mail-decode-encoded-word-region
   "Function used to decode headers.")
 
+(defvar gnus-decode-address-function 'mail-decode-encoded-address-region
+  "Function used to decode addresses.")
+
 (defvar gnus-article-dumbquotes-map
   '(("\200" "EUR")
     ("\202" ",")
@@ -1254,15 +1325,14 @@ See Info node `(gnus)Customizing Articles' for details."
 
 (defcustom gnus-treat-display-x-face
   (and (not noninteractive)
-       (or (and (fboundp 'image-type-available-p)
-               (image-type-available-p 'xbm)
-               (string-match "^0x" (shell-command-to-string "uncompface"))
-               (executable-find "icontopbm"))
-          (and (featurep 'xemacs)
-               (featurep 'xface)))
+       (gnus-image-type-available-p 'xbm)
+       (if (featurep 'xemacs)
+          (featurep 'xface)
+        (and (string-match "^0x" (shell-command-to-string "uncompface"))
+             (executable-find "icontopbm")))
        'head)
   "Display X-Face headers.
-Valid values are nil, t, `head', `last', an integer or a predicate.
+Valid values are nil and `head'.
 See Info node `(gnus)Customizing Articles' and Info node
 `(gnus)X-Face' for details."
   :group 'gnus-article-treat
@@ -1291,10 +1361,7 @@ See Info node `(gnus)Customizing Articles' and Info node
 
 (defcustom gnus-treat-display-face
   (and (not noninteractive)
-       (or (and (fboundp 'image-type-available-p)
-               (image-type-available-p 'png))
-          (and (featurep 'xemacs)
-               (featurep 'png)))
+       (gnus-image-type-available-p 'png)
        'head)
   "Display Face headers.
 Valid values are nil, t, `head', `last', an integer or a predicate.
@@ -1307,12 +1374,7 @@ See Info node `(gnus)Customizing Articles' and Info node
   :type gnus-article-treat-head-custom)
 (put 'gnus-treat-display-face 'highlight t)
 
-(defcustom gnus-treat-display-smileys
-  (if (or (and (featurep 'xemacs)
-              (featurep 'xpm))
-         (and (fboundp 'image-type-available-p)
-              (image-type-available-p 'pbm)))
-      t nil)
+(defcustom gnus-treat-display-smileys (gnus-image-type-available-p 'xpm)
   "Display smileys.
 Valid values are nil, t, `head', `last', an integer or a predicate.
 See Info node `(gnus)Customizing Articles' and Info node
@@ -1846,7 +1908,11 @@ always hide."
                                'string<))))
                    (gnus-article-hide-header "reply-to")))))
             ((eq elem 'date)
-             (let ((date (message-fetch-field "date")))
+             (let ((date (with-current-buffer gnus-original-article-buffer
+                           ;; If date in `gnus-article-buffer' is localized
+                           ;; (`gnus-treat-date-user-defined'),
+                           ;; `days-between' might fail.
+                           (message-fetch-field "date"))))
                (when (and date
                           (< (days-between (current-time-string) date)
                              4))
@@ -2309,10 +2375,24 @@ If PROMPT (the prefix), prompt for a coding system to use."
                             (set-buffer gnus-summary-buffer)
                           (error))
                         gnus-newsgroup-ignored-charsets))
-       (inhibit-read-only t))
-    (save-restriction
-      (article-narrow-to-head)
-      (funcall gnus-decode-header-function (point-min) (point-max)))))
+       (inhibit-read-only t)
+       end start)
+    (goto-char (point-min))
+    (when (search-forward "\n\n" nil 'move)
+      (forward-line -1))
+    (setq end (point))
+    (while (not (bobp))
+      (while (progn
+              (forward-line -1)
+              (and (not (bobp))
+                   (memq (char-after) '(?\t ? )))))
+      (setq start (point))
+      (if (looking-at "\
+\\(?:Resent-\\)?\\(?:From\\|Cc\\|To\\|Bcc\\|\\(?:In-\\)?Reply-To\\|Sender\
+\\|Mail-Followup-To\\|Mail-Copies-To\\|Approved\\):")
+         (funcall gnus-decode-address-function start end)
+       (funcall gnus-decode-header-function start end))
+      (goto-char (setq end start)))))
 
 (defun article-decode-group-name ()
   "Decode group names in `Newsgroups:'."
@@ -3249,10 +3329,13 @@ This format is defined by the `gnus-article-time-format' variable."
 
 (defun gnus-article-save (save-buffer file &optional num)
   "Save the currently selected article."
-  (unless gnus-save-all-headers
-    ;; Remove headers according to `gnus-saved-headers'.
+  (when (or (get gnus-default-article-saver :headers)
+           (not gnus-save-all-headers))
+    ;; Remove headers according to `gnus-saved-headers' or the value
+    ;; of the `:headers' property that the saver function might have.
     (let ((gnus-visible-headers
-          (or gnus-saved-headers gnus-visible-headers))
+          (or (symbol-value (get gnus-default-article-saver :headers))
+              gnus-saved-headers gnus-visible-headers))
          (gnus-article-buffer save-buffer))
       (save-excursion
        (set-buffer save-buffer)
@@ -3277,7 +3360,8 @@ This format is defined by the `gnus-article-time-format' variable."
        (funcall gnus-default-article-saver filename)))))
 
 (defun gnus-read-save-file-name (prompt &optional filename
-                                       function group headers variable)
+                                       function group headers variable
+                                       dir-var)
   (let ((default-name
          (funcall function group headers (symbol-value variable)))
        result)
@@ -3290,6 +3374,10 @@ This format is defined by the `gnus-article-time-format' variable."
             default-name)
            (filename filename)
            (t
+            (when (symbol-value dir-var)
+              (setq default-name (expand-file-name
+                                  (file-name-nondirectory default-name)
+                                  (symbol-value dir-var))))
             (let* ((split-name (gnus-get-split-value gnus-split-methods))
                    (prompt
                     (format prompt
@@ -3354,7 +3442,11 @@ This format is defined by the `gnus-article-time-format' variable."
               ;; Possibly translate some characters.
               (nnheader-translate-file-chars file))))))
     (gnus-make-directory (file-name-directory result))
-    (set variable result)))
+    (when variable
+      (set variable result))
+    (when dir-var
+      (set dir-var (file-name-directory result)))
+    result))
 
 (defun gnus-article-archive-name (group)
   "Return the first instance of an \"Archive-name\" in the current buffer."
@@ -3402,6 +3494,8 @@ Directory to save to is default to `gnus-article-save-directory'."
          (gnus-output-to-mail filename)))))
   filename)
 
+(put 'gnus-summary-save-in-file :decode t)
+(put 'gnus-summary-save-in-file :headers 'gnus-saved-headers)
 (defun gnus-summary-save-in-file (&optional filename overwrite)
   "Append this article to file.
 Optional argument FILENAME specifies file name.
@@ -3420,13 +3514,21 @@ Directory to save to is default to `gnus-article-save-directory'."
        (gnus-output-to-file filename))))
   filename)
 
+(put 'gnus-summary-write-to-file :decode t)
+(put 'gnus-summary-write-to-file :function 'gnus-summary-save-in-file)
+(put 'gnus-summary-write-to-file :headers 'gnus-saved-headers)
 (defun gnus-summary-write-to-file (&optional filename)
   "Write this article to a file, overwriting it if the file exists.
 Optional argument FILENAME specifies file name.
 The directory to save in defaults to `gnus-article-save-directory'."
-  (gnus-summary-save-in-file nil t))
+  (setq filename (gnus-read-save-file-name
+                 "Save %s in file" filename
+                 gnus-file-save-name gnus-newsgroup-name
+                 gnus-current-headers nil 'gnus-newsgroup-last-directory))
+  (gnus-summary-save-in-file filename t))
 
-(defun gnus-summary-save-body-in-file (&optional filename)
+(put 'gnus-summary-save-body-in-file :decode t)
+(defun gnus-summary-save-body-in-file (&optional filename overwrite)
   "Append this article body to a file.
 Optional argument FILENAME specifies file name.
 The directory to save in defaults to `gnus-article-save-directory'."
@@ -3440,9 +3542,25 @@ The directory to save in defaults to `gnus-article-save-directory'."
        (widen)
        (when (article-goto-body)
          (narrow-to-region (point) (point-max)))
+       (when (and overwrite
+                  (file-exists-p filename))
+         (delete-file filename))
        (gnus-output-to-file filename))))
   filename)
 
+(put 'gnus-summary-write-body-to-file :decode t)
+(put 'gnus-summary-write-body-to-file
+     :function 'gnus-summary-save-body-in-file)
+(defun gnus-summary-write-body-to-file (&optional filename)
+  "Write this article body to a file, overwriting it if the file exists.
+Optional argument FILENAME specifies file name.
+The directory to save in defaults to `gnus-article-save-directory'."
+  (setq filename (gnus-read-save-file-name
+                 "Save %s body in file" filename
+                 gnus-file-save-name gnus-newsgroup-name
+                 gnus-current-headers nil 'gnus-newsgroup-last-directory))
+  (gnus-summary-save-body-in-file filename t))
+
 (defun gnus-summary-save-in-pipe (&optional command)
   "Pipe this article to subprocess."
   (setq command
@@ -3514,7 +3632,7 @@ Otherwise, it is like ~/News/news/group/num."
 (defun gnus-numeric-save-name (newsgroup headers &optional last-file)
   "Generate file name from NEWSGROUP, HEADERS, and optional LAST-FILE.
 If variable `gnus-use-long-file-name' is non-nil, it is
-~/News/news.group/num. Otherwise, it is like ~/News/news/group/num."
+~/News/news.group/num.  Otherwise, it is like ~/News/news/group/num."
   (let ((default
          (expand-file-name
           (concat (if (gnus-use-long-file-name 'not-save)
@@ -3807,8 +3925,10 @@ commands:
   (make-local-variable 'gnus-article-image-alist)
   (make-local-variable 'gnus-article-charset)
   (make-local-variable 'gnus-article-ignored-charsets)
-  ;; Prevent recent Emacsen from displaying non-break space as "\ ".
+  ;; Prevent Emacs 22 from displaying non-break space with `nobreak-space'
+  ;; face.
   (set (make-local-variable 'nobreak-char-display) nil)
+  (setq cursor-in-non-selected-windows nil)
   (gnus-set-default-directory)
   (buffer-disable-undo)
   (setq buffer-read-only t)
@@ -3816,6 +3936,14 @@ commands:
   (mm-enable-multibyte)
   (gnus-run-mode-hooks 'gnus-article-mode-hook))
 
+;; Internal variables.  Are `gnus-button-regexp' and `gnus-button-last' used
+;; at all?
+(defvar gnus-button-regexp nil)
+(defvar gnus-button-marker-list nil
+  "Regexp matching any of the regexps from `gnus-button-alist'.")
+(defvar gnus-button-last nil
+  "The value of `gnus-button-alist' when `gnus-button-regexp' was build.")
+
 (defun gnus-article-setup-buffer ()
   "Initialize the article buffer."
   (let* ((name (if gnus-single-article-buffer "*Article*"
@@ -4217,9 +4345,8 @@ Deleting parts may malfunction or destroy the article; continue? ")
           (handles gnus-article-mime-handles)
           (none "(none)")
           (description
-           (or
-            (mail-decode-encoded-word-string (or (mm-handle-description data)
-                                                 none))))
+           (mail-decode-encoded-word-string (or (mm-handle-description data)
+                                                none)))
           (filename
            (or (mail-content-type-get (mm-handle-disposition data) 'filename)
                none))
@@ -4316,21 +4443,29 @@ Deleting parts may malfunction or destroy the article; continue? ")
 
 (defun gnus-mime-view-part-as-type-internal ()
   (gnus-article-check-buffer)
-  (let* ((name (mail-content-type-get
-               (mm-handle-type (get-text-property (point) 'gnus-data))
-               'name))
+  (let* ((handle (get-text-property (point) 'gnus-data))
+        (name (or
+               ;; Content-Type: foo/bar; name=...
+               (mail-content-type-get (mm-handle-type handle) 'name)
+               ;; Content-Disposition: attachment; filename=...
+               (cdr (assq 'filename (cdr (mm-handle-disposition handle))))))
         (def-type (and name (mm-default-file-encoding name))))
     (and def-type (cons def-type 0))))
 
-(defun gnus-mime-view-part-as-type (&optional mime-type)
-  "Choose a MIME media type, and view the part as such."
+(defun gnus-mime-view-part-as-type (&optional mime-type pred)
+  "Choose a MIME media type, and view the part as such.
+If non-nil, PRED is a predicate to use during completion to limit the
+available media-types."
   (interactive)
   (unless mime-type
-    (setq mime-type (completing-read
-                    "View as MIME type: "
-                    (mapcar #'list (mailcap-mime-types))
-                    nil nil
-                    (gnus-mime-view-part-as-type-internal))))
+    (setq mime-type
+         (let ((default (gnus-mime-view-part-as-type-internal)))
+           (completing-read
+            (format "View as MIME type (default %s): "
+                    (car default))
+            (mapcar #'list (mailcap-mime-types))
+            pred nil nil nil
+            (car default)))))
   (gnus-article-check-buffer)
   (let ((handle (get-text-property (point) 'gnus-data)))
     (when handle
@@ -4472,24 +4607,56 @@ are decompressed."
           (mm-string-to-multibyte contents)))
        (goto-char b)))))
 
+(defun gnus-mime-strip-charset-parameters (handle)
+  "Strip charset parameters from HANDLE."
+  (if (stringp (car handle))
+      (mapc #'gnus-mime-strip-charset-parameters (cdr handle))
+    (let* ((type (mm-handle-type (if (equal (mm-handle-media-type handle)
+                                           "message/external-body")
+                                    (progn
+                                      (unless (mm-handle-cache handle)
+                                        (mm-extern-cache-contents handle))
+                                      (mm-handle-cache handle))
+                                  handle)))
+          (charset (assq 'charset (cdr type))))
+      (when charset
+       (delq charset type)))))
+
 (defun gnus-mime-view-part-as-charset (&optional handle arg)
   "Insert the MIME part under point into the current buffer using the
 specified charset."
   (interactive (list nil current-prefix-arg))
   (gnus-article-check-buffer)
-  (let* ((handle (or handle (get-text-property (point) 'gnus-data)))
-        contents charset
-        (b (point))
-        (inhibit-read-only t))
+  (let ((handle (or handle (get-text-property (point) 'gnus-data)))
+       (fun (get-text-property (point) 'gnus-callback))
+       (gnus-newsgroup-ignored-charsets 'gnus-all)
+       gnus-newsgroup-charset form preferred parts)
     (when handle
       (if (mm-handle-undisplayer handle)
          (mm-remove-part handle))
-      (let ((gnus-newsgroup-charset
-            (or (cdr (assq arg
-                           gnus-summary-show-article-charset-alist))
-                (mm-read-coding-system "Charset: ")))
-         (gnus-newsgroup-ignored-charsets 'gnus-all))
-       (gnus-article-press-button)))))
+      (when fun
+       (setq gnus-newsgroup-charset
+             (or (cdr (assq arg gnus-summary-show-article-charset-alist))
+                 (mm-read-coding-system "Charset: ")))
+       (gnus-mime-strip-charset-parameters handle)
+       (when (and (consp (setq form (cdr-safe fun)))
+                  (setq form (ignore-errors
+                               (assq 'gnus-mime-display-alternative form)))
+                  (setq preferred (caddr form))
+                  (progn
+                    (when (eq (car preferred) 'quote)
+                      (setq preferred (cadr preferred)))
+                    (not (equal preferred
+                                (get-text-property (point) 'gnus-data))))
+                  (setq parts (get-text-property (point) 'gnus-part))
+                  (setq parts (cdr (assq parts
+                                         gnus-article-mime-handle-alist)))
+                  (equal (mm-handle-media-type parts) "multipart/alternative")
+                  (setq parts (reverse (cdr parts))))
+         (setcar (cddr form)
+                 (list 'quote (or (cadr (member preferred parts))
+                                  (car parts)))))
+       (funcall fun handle)))))
 
 (defun gnus-mime-view-part-externally (&optional handle)
   "View the MIME part under point with an external viewer."
@@ -4500,12 +4667,18 @@ specified charset."
         (mm-inlined-types nil)
         (mail-parse-charset gnus-newsgroup-charset)
         (mail-parse-ignored-charsets
-         (save-excursion (set-buffer gnus-summary-buffer)
-                         gnus-newsgroup-ignored-charsets)))
-    (when handle
-      (if (mm-handle-undisplayer handle)
-         (mm-remove-part handle)
-       (mm-display-part handle)))))
+          (with-current-buffer gnus-summary-buffer
+            gnus-newsgroup-ignored-charsets))
+         (type (mm-handle-media-type handle))
+         (method (mailcap-mime-info type))
+         (mm-enable-external t))
+    (if (not (stringp method))
+       (gnus-mime-view-part-as-type
+        nil (lambda (types) (stringp (mailcap-mime-info (car types)))))
+      (when handle
+       (if (mm-handle-undisplayer handle)
+           (mm-remove-part handle)
+         (mm-display-part handle))))))
 
 (defun gnus-mime-view-part-internally (&optional handle)
   "View the MIME part under point with an internal viewer.
@@ -4517,13 +4690,16 @@ If no internal viewer is available, use an external viewer."
         (mm-inline-large-images t)
         (mail-parse-charset gnus-newsgroup-charset)
         (mail-parse-ignored-charsets
-         (save-excursion (set-buffer gnus-summary-buffer)
-                         gnus-newsgroup-ignored-charsets))
+         (with-current-buffer gnus-summary-buffer
+           gnus-newsgroup-ignored-charsets))
         (inhibit-read-only t))
-    (when handle
-      (if (mm-handle-undisplayer handle)
-         (mm-remove-part handle)
-       (mm-display-part handle)))))
+    (if (not (mm-inlinable-p handle))
+        (gnus-mime-view-part-as-type
+         nil (lambda (types) (mm-inlinable-p handle (car types))))
+      (when handle
+       (if (mm-handle-undisplayer handle)
+           (mm-remove-part handle)
+         (mm-display-part handle))))))
 
 (defun gnus-mime-action-on-part (&optional action)
   "Do something with the MIME attachment at \(point\)."
@@ -4535,13 +4711,22 @@ If no internal viewer is available, use an external viewer."
        (funcall (cdr action-pair)))))
 
 (defun gnus-article-part-wrapper (n function)
-  (save-current-buffer
-    (set-buffer gnus-article-buffer)
-    (when (> n (length gnus-article-mime-handle-alist))
-      (error "No such part"))
-    (gnus-article-goto-part n)
-    (let ((handle (cdr (assq n gnus-article-mime-handle-alist))))
-      (funcall function handle))))
+  (let ((window (get-buffer-window gnus-article-buffer 'visible))
+       frame)
+    (when window
+      ;; It is necessary to select the article window so that
+      ;; `gnus-article-goto-part' may really move the point.
+      (setq frame (selected-frame))
+      (gnus-select-frame-set-input-focus (window-frame window))
+      (unwind-protect
+         (save-window-excursion
+           (select-window window)
+           (when (> n (length gnus-article-mime-handle-alist))
+             (error "No such part"))
+           (gnus-article-goto-part n)
+           (let ((handle (cdr (assq n gnus-article-mime-handle-alist))))
+             (funcall function handle)))
+       (gnus-select-frame-set-input-focus frame)))))
 
 (defun gnus-article-pipe-part (n)
   "Pipe MIME part N, which is the numerical prefix."
@@ -4793,7 +4978,11 @@ N is the numerical prefix."
              (article-goto-body)
              (narrow-to-region (point-min) (point))
              (gnus-article-save-original-date
-              (gnus-treat-article 'head)))))))))
+              (gnus-treat-article 'head)))))))
+    ;; Cope with broken MIME messages.
+    (goto-char (point-max))
+    (unless (bolp)
+      (insert "\n"))))
 
 (defcustom gnus-mime-display-multipart-as-mixed nil
   "Display \"multipart\" parts as  \"multipart/mixed\".
@@ -5154,17 +5343,55 @@ Provided for backwards compatibility."
 ;;; Article savers.
 
 (defun gnus-output-to-file (file-name)
-  "Append the current article to a file named FILE-NAME."
-  (let ((artbuf (current-buffer)))
+  "Append the current article to a file named FILE-NAME.
+If `gnus-article-save-coding-system' is non-nil, it is used to encode
+text and used as the value of the coding cookie which is added to the
+top of a file.  Otherwise, this function saves a raw article without
+the coding cookie."
+  (let* ((artbuf (current-buffer))
+        (file-name-coding-system nnmail-pathname-coding-system)
+        (coding gnus-article-save-coding-system)
+        (coding-system-for-read (if coding
+                                    nil ;; Rely on the coding cookie.
+                                  mm-text-coding-system))
+        (coding-system-for-write (or coding
+                                     mm-text-coding-system-for-write
+                                     mm-text-coding-system))
+        (exists (file-exists-p file-name)))
     (with-temp-buffer
+      (when exists
+       (insert-file-contents file-name)
+       (goto-char (point-min))
+       ;; Remove the existing coding cookie.
+       (when (looking-at "X-Gnus-Coding-System: .+\n\n")
+         (delete-region (match-beginning 0) (match-end 0))))
+      (goto-char (point-max))
       (insert-buffer-substring artbuf)
       ;; Append newline at end of the buffer as separator, and then
       ;; save it to file.
       (goto-char (point-max))
       (insert "\n")
-      (let ((file-name-coding-system nnmail-pathname-coding-system))
-       (mm-append-to-file (point-min) (point-max) file-name))
-      t)))
+      (when coding
+       ;; If the coding system is not suitable to encode the text,
+       ;; ask a user for a proper one.
+       (when (fboundp 'select-safe-coding-system)
+         (setq coding (coding-system-base
+                       (save-window-excursion
+                         (select-safe-coding-system (point-min) (point-max)
+                                                    coding))))
+         (setq coding-system-for-write
+               (or (cdr (assq coding '((mule-utf-8 . utf-8))))
+                   coding)))
+       (goto-char (point-min))
+       ;; Add the coding cookie.
+       (insert (format "X-Gnus-Coding-System: -*- coding: %s; -*-\n\n"
+                       coding-system-for-write)))
+      (if exists
+         (progn
+           (write-region (point-min) (point-max) file-name nil 'no-message)
+           (message "Appended to %s" file-name))
+       (write-region (point-min) (point-max) file-name))))
+  t)
 
 (defun gnus-narrow-to-page (&optional arg)
   "Narrow the article buffer to a page.
@@ -5380,7 +5607,7 @@ not have a face in `gnus-article-boring-faces'."
   "Execute the last keystroke in the summary buffer."
   (interactive)
   (let (func)
-    (pop-to-buffer gnus-article-current-summary 'norecord)
+    (pop-to-buffer gnus-article-current-summary nil 'norecord)
     (setq func (lookup-key (current-local-map) (this-command-keys)))
     (call-interactively func)))
 
@@ -5419,7 +5646,7 @@ not have a face in `gnus-article-boring-faces'."
            (member keys nosave-in-article))
        (let (func)
          (save-window-excursion
-           (pop-to-buffer gnus-article-current-summary 'norecord)
+           (pop-to-buffer gnus-article-current-summary nil 'norecord)
            ;; We disable the pick minor mode commands.
            (let (gnus-pick-mode)
              (setq func (lookup-key (current-local-map) keys))))
@@ -5431,14 +5658,14 @@ not have a face in `gnus-article-boring-faces'."
            (call-interactively func)
            (setq new-sum-point (point)))
          (when (member keys nosave-but-article)
-           (pop-to-buffer gnus-article-buffer 'norecord)))
+           (pop-to-buffer gnus-article-buffer nil 'norecord)))
       ;; These commands should restore window configuration.
       (let ((obuf (current-buffer))
            (owin (current-window-configuration))
            (opoint (point))
            win func in-buffer selected new-sum-start new-sum-hscroll)
        (cond (not-restore-window
-              (pop-to-buffer gnus-article-current-summary 'norecord))
+              (pop-to-buffer gnus-article-current-summary nil 'norecord))
              ((setq win (get-buffer-window gnus-article-current-summary))
               (select-window win))
              (t
@@ -6043,7 +6270,7 @@ Strings like this can be either a message ID or a mail address.  If it is one
 of the symbols `mid' or `mail', Gnus will always assume that the string is a
 message ID or a mail address, respectively.  If this variable is set to the
 symbol `ask', always query the user what do do.  If it is a function, this
-function will be called with the string as it's only argument.  The function
+function will be called with the string as its only argument.  The function
 must return `mid', `mail', `invalid' or `ask'."
   :version "22.1"
   :group 'gnus-article-buttons
@@ -6425,7 +6652,7 @@ positives are possible."
      0 (>= gnus-button-emacs-level 1) gnus-button-handle-describe-function 2)
     ("\\b\\(C-h\\|<?[Ff]1>?\\)[ \t\n]+v[ \t\n]+\\([^ \t\n]+\\)[ \t\n]+RET"
      0 (>= gnus-button-emacs-level 1) gnus-button-handle-describe-variable 2)
-    ("`\\(\\b\\(C-h\\|<?[Ff]1>?\\)[ \t\n]+k[ \t\n]+\\([^']+\\)\\)'"
+    ("`\\(\\(C-h\\|<?[Ff]1>?\\)[ \t\n]+k[ \t\n]+\\([^']+\\)\\)'"
      ;; Unlike the other regexps we really have to require quoting
      ;; here to determine where it ends.
      1 (>= gnus-button-emacs-level 1) gnus-button-handle-describe-key 3)
@@ -6519,13 +6746,6 @@ HEADER is a regexp to match a header.  For a fuller explanation, see
                               :inline t
                               (integer :tag "Regexp group")))))
 
-(defvar gnus-button-regexp nil)
-(defvar gnus-button-marker-list nil)
-;; Regexp matching any of the regexps from `gnus-button-alist'.
-
-(defvar gnus-button-last nil)
-;; The value of `gnus-button-alist' when `gnus-button-regexp' was build.
-
 ;;; Commands:
 
 (defun gnus-article-push-button (event)