]> code.delx.au - gnu-emacs/blobdiff - lisp/gnus/gnus-art.el
Merge from gnus--rel--5.10
[gnu-emacs] / lisp / gnus / gnus-art.el
index 92a4f09ded3bcd2e2b74c4f36ef2000d18c1410c..bc7f27c97c8682b952fba6a897069af16251c2ae 100644 (file)
@@ -49,6 +49,7 @@
 (autoload 'gnus-button-mailto "gnus-msg")
 (autoload 'gnus-button-reply "gnus-msg" nil t)
 (autoload 'parse-time-string "parse-time" nil nil)
+(autoload 'mm-extern-cache-contents "mm-extern")
 
 (defgroup gnus-article nil
   "Article display."
@@ -491,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)
 
@@ -512,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:
 
@@ -529,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)
@@ -538,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."
@@ -784,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" ",")
@@ -1845,7 +1917,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))
@@ -2267,38 +2343,37 @@ If PROMPT (the prefix), prompt for a coding system to use."
                           (error))
                         gnus-newsgroup-ignored-charsets))
        ct cte ctl charset format)
-  (save-excursion
-    (save-restriction
-      (article-narrow-to-head)
-      (setq ct (message-fetch-field "Content-Type" t)
-           cte (message-fetch-field "Content-Transfer-Encoding" t)
-           ctl (and ct (ignore-errors
-                         (mail-header-parse-content-type ct)))
-           charset (cond
-                    (prompt
-                     (mm-read-coding-system "Charset to decode: "))
-                    (ctl
-                     (mail-content-type-get ctl 'charset)))
-           format (and ctl (mail-content-type-get ctl 'format)))
-      (when cte
-       (setq cte (mail-header-strip cte)))
-      (if (and ctl (not (string-match "/" (car ctl))))
-         (setq ctl nil))
-      (goto-char (point-max)))
-    (forward-line 1)
-    (save-restriction
-      (narrow-to-region (point) (point-max))
-      (when (and (eq mail-parse-charset 'gnus-decoded)
-                (eq (mm-body-7-or-8) '8bit))
-       ;; The text code could have been decoded.
-       (setq charset mail-parse-charset))
-      (when (and (or (not ctl)
-                    (equal (car ctl) "text/plain"))
-                (not format)) ;; article with format will decode later.
-       (mm-decode-body
-        charset (and cte (intern (downcase
-                                  (gnus-strip-whitespace cte))))
-        (car ctl)))))))
+    (save-excursion
+      (save-restriction
+       (article-narrow-to-head)
+       (setq ct (message-fetch-field "Content-Type" t)
+             cte (message-fetch-field "Content-Transfer-Encoding" t)
+             ctl (and ct (mail-header-parse-content-type ct))
+             charset (cond
+                      (prompt
+                       (mm-read-coding-system "Charset to decode: "))
+                      (ctl
+                       (mail-content-type-get ctl 'charset)))
+             format (and ctl (mail-content-type-get ctl 'format)))
+       (when cte
+         (setq cte (mail-header-strip cte)))
+       (if (and ctl (not (string-match "/" (car ctl))))
+           (setq ctl nil))
+       (goto-char (point-max)))
+      (forward-line 1)
+      (save-restriction
+       (narrow-to-region (point) (point-max))
+       (when (and (eq mail-parse-charset 'gnus-decoded)
+                  (eq (mm-body-7-or-8) '8bit))
+         ;; The text code could have been decoded.
+         (setq charset mail-parse-charset))
+       (when (and (or (not ctl)
+                      (equal (car ctl) "text/plain"))
+                  (not format)) ;; article with format will decode later.
+         (mm-decode-body
+          charset (and cte (intern (downcase
+                                    (gnus-strip-whitespace cte))))
+          (car ctl)))))))
 
 (defun article-decode-encoded-words ()
   "Remove encoded-word encoding from headers."
@@ -2309,10 +2384,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:'."
@@ -2390,9 +2479,7 @@ If READ-CHARSET, ask for a coding system."
            (setq type
                  (gnus-fetch-field "content-transfer-encoding"))
            (let* ((ct (gnus-fetch-field "content-type"))
-                  (ctl (and ct
-                            (ignore-errors
-                              (mail-header-parse-content-type ct)))))
+                  (ctl (and ct (mail-header-parse-content-type ct))))
              (setq charset (and ctl
                                 (mail-content-type-get ctl 'charset)))
              (if (stringp charset)
@@ -2420,9 +2507,7 @@ If READ-CHARSET, ask for a coding system."
            (setq type
                  (gnus-fetch-field "content-transfer-encoding"))
            (let* ((ct (gnus-fetch-field "content-type"))
-                  (ctl (and ct
-                            (ignore-errors
-                              (mail-header-parse-content-type ct)))))
+                  (ctl (and ct (mail-header-parse-content-type ct))))
              (setq charset (and ctl
                                 (mail-content-type-get ctl 'charset)))
              (if (stringp charset)
@@ -2488,9 +2573,7 @@ charset defined in `gnus-summary-show-article-charset-alist' is used."
        (when (gnus-buffer-live-p gnus-original-article-buffer)
          (with-current-buffer gnus-original-article-buffer
            (let* ((ct (gnus-fetch-field "content-type"))
-                  (ctl (and ct
-                            (ignore-errors
-                              (mail-header-parse-content-type ct)))))
+                  (ctl (and ct (mail-header-parse-content-type ct))))
              (setq charset (and ctl
                                 (mail-content-type-get ctl 'charset)))
              (when (stringp charset)
@@ -2541,15 +2624,18 @@ charset defined in `gnus-summary-show-article-charset-alist' is used."
 
 (defun gnus-article-wash-html-with-w3m-standalone ()
   "Wash the current buffer with w3m."
-  (unless (mm-coding-system-p charset)
-    ;; The default.
-    (setq charset 'iso-8859-1))
-  (let ((coding-system-for-write charset)
-       (coding-system-for-read charset))
-    (call-process-region
-     (point-min) (point-max)
-     "w3m" t t nil "-dump" "-T" "text/html"
-     "-I" (symbol-name charset) "-O" (symbol-name charset))))
+  (if (mm-w3m-standalone-supports-m17n-p)
+      (progn
+       (unless (mm-coding-system-p charset) ;; Bound by `article-wash-html'.
+         ;; The default.
+         (setq charset 'iso-8859-1))
+       (let ((coding-system-for-write charset)
+             (coding-system-for-read charset))
+         (call-process-region
+          (point-min) (point-max)
+          "w3m" t t nil "-dump" "-T" "text/html"
+          "-I" (symbol-name charset) "-O" (symbol-name charset))))
+    (mm-inline-wash-with-stdin nil "w3m" "-dump" "-T" "text/html")))
 
 (defun article-hide-list-identifiers ()
   "Remove list identifies from the Subject header.
@@ -2612,18 +2698,22 @@ always hide."
          (article-really-strip-banner
           (gnus-parameter-banner gnus-newsgroup-name)))
        (when gnus-article-address-banner-alist
-         (article-really-strip-banner
-          (let ((from (save-restriction
-                        (widen)
-                        (article-narrow-to-head)
-                        (mail-fetch-field "from"))))
-            (when (and from
-                       (setq from
-                             (caar (mail-header-parse-addresses from))))
-              (catch 'found
-                (dolist (pair gnus-article-address-banner-alist)
-                  (when (string-match (car pair) from)
-                    (throw 'found (cdr pair)))))))))))))
+         ;; Note that the From header is decoded here, so it is
+         ;; required that the *-extract-address-components function
+         ;; supports non-ASCII text.
+         (let ((from (save-restriction
+                       (widen)
+                       (article-narrow-to-head)
+                       (mail-fetch-field "from"))))
+           (when (and from
+                      (setq from
+                            (cadr (funcall gnus-extract-address-components
+                                           from))))
+             (catch 'found
+               (dolist (pair gnus-article-address-banner-alist)
+                 (when (string-match (car pair) from)
+                   (throw 'found
+                          (article-really-strip-banner (cdr pair)))))))))))))
 
 (defun article-really-strip-banner (banner)
   "Strip the banner specified by the argument."
@@ -3248,10 +3338,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)
@@ -3276,7 +3369,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)
@@ -3289,6 +3383,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
@@ -3353,7 +3451,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."
@@ -3401,6 +3503,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.
@@ -3419,13 +3523,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'."
@@ -3439,9 +3551,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
@@ -3808,6 +3936,7 @@ commands:
   (make-local-variable 'gnus-article-ignored-charsets)
   ;; Prevent recent Emacsen from displaying non-break space as "\ ".
   (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)
@@ -3815,6 +3944,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*"
@@ -4151,6 +4288,9 @@ Deleting parts may malfunction or destroy the article; continue? ")
          (insert "Content-Type: " (mm-handle-media-type data))
          (mml-insert-parameter-string (cdr (mm-handle-type data))
                                       '(charset))
+         ;; Add a filename for the sake of saving the part again.
+         (mml-insert-parameter
+          (mail-header-encode-parameter "name" (file-name-nondirectory file)))
          (insert "\n")
          (insert "Content-ID: " (message-make-message-id) "\n")
          (insert "Content-Transfer-Encoding: binary\n")
@@ -4213,9 +4353,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))
@@ -4312,24 +4451,36 @@ 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
+      (when (equal (mm-handle-media-type handle) "message/external-body")
+       (unless (mm-handle-cache handle)
+         (mm-extern-cache-contents handle))
+       (setq handle (mm-handle-cache handle)))
       (setq handle
            (mm-make-handle (mm-handle-buffer handle)
                            (cons mime-type (cdr (mm-handle-type handle)))
@@ -4469,19 +4620,29 @@ are decompressed."
 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 type charset)
     (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: ")))
+       ;; Strip the charset parameter from `handle'.
+       (setq 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)))
+       (delq charset type)
+       (funcall fun handle)))))
 
 (defun gnus-mime-view-part-externally (&optional handle)
   "View the MIME part under point with an external viewer."
@@ -4492,12 +4653,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 (type) (stringp (mailcap-mime-info type))))
+      (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.
@@ -4509,13 +4676,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 (type) (mm-inlinable-p handle type)))
+      (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\)."
@@ -4785,7 +4955,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\".
@@ -4889,13 +5063,18 @@ If displaying \"text/html\" is discouraged \(see
        (let ((id (1+ (length gnus-article-mime-handle-alist)))
              beg)
          (push (cons id handle) gnus-article-mime-handle-alist)
+         (when (and display
+                    (equal (mm-handle-media-supertype handle) "message"))
+           (insert-char
+            ?\n
+            (cond ((not (bolp)) 2)
+                  ((or (bobp) (eq (char-before (1- (point))) ?\n)) 0)
+                  (t 1))))
          (when (or (not display)
                    (not (gnus-unbuttonized-mime-type-p type)))
-           ;(gnus-article-insert-newline)
            (gnus-insert-mime-button
             handle id (list (or display (and not-attachment text))))
            (gnus-article-insert-newline)
-           ;(gnus-article-insert-newline)
            ;; Remember modify the number of forward lines.
            (setq move t))
          (setq beg (point))
@@ -5141,17 +5320,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.
@@ -5313,14 +5530,15 @@ not have a face in `gnus-article-boring-faces'."
             (boundp 'gnus-article-boring-faces)
             (symbol-value 'gnus-article-boring-faces))
     (save-excursion
-      (catch 'only-boring
-       (while (re-search-forward "\\b\\w\\w" nil t)
-         (forward-char -1)
-         (when (not (gnus-intersection
-                     (gnus-faces-at (point))
-                     (symbol-value 'gnus-article-boring-faces)))
-           (throw 'only-boring nil)))
-       (throw 'only-boring t)))))
+      (let ((inhibit-point-motion-hooks t))
+       (catch 'only-boring
+         (while (re-search-forward "\\b\\w\\w" nil t)
+           (forward-char -1)
+           (when (not (gnus-intersection
+                       (gnus-faces-at (point))
+                       (symbol-value 'gnus-article-boring-faces)))
+             (throw 'only-boring nil)))
+         (throw 'only-boring t))))))
 
 (defun gnus-article-refer-article ()
   "Read article specified by message-id around point."
@@ -5958,7 +6176,7 @@ groups."
 
 ;; Regexp suggested by Felix Wiemann in <87oeuomcz9.fsf@news2.ososo.de>
 (defcustom gnus-button-valid-localpart-regexp
-  "[a-z0-9$%(*-=?[_][^<>\")!;:,{}\n\t ]*"
+  "[a-z0-9$%(*-=?[_][^<>\")!;:,{}\n\t @]*"
   "Regular expression that matches a localpart of mail addresses or MIDs."
   :version "22.1"
   :group 'gnus-article-buttons
@@ -6505,13 +6723,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)