Merge from gnus--rel--5.10
[gnus] / lisp / gnus-art.el
index b450d98..41d845d 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
@@ -252,7 +252,7 @@ This can also be a list of the above values."
   :type '(choice (const nil)
                 (integer :value 200)
                 (number :value 4.0)
-                (function :value fun)
+                function
                 (regexp :value ".*"))
   :group 'gnus-article-signature)
 
@@ -275,7 +275,7 @@ This can also be a list of the above values."
 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)
@@ -502,7 +502,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)
 
@@ -523,14 +526,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:
 
@@ -540,7 +546,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)
@@ -549,8 +576,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."
@@ -834,6 +902,9 @@ image type in XEmacs if it is built with the libcompface library."
 (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" ",")
@@ -1286,6 +1357,18 @@ predicate.  See Info node `(gnus)Customizing Articles'."
   :link '(custom-manual "(gnus)Customizing Articles")
   :type gnus-article-treat-custom)
 
+(defcustom gnus-article-unfold-long-headers nil
+  "If non-nil, allow unfolding headers even if the header is long.
+If it is a regexp, only long headers matching this regexp are unfolded.
+If it is t, all long headers are unfolded.
+
+This variable has no effect if `gnus-treat-unfold-headers' is nil."
+  :version "23.0" ;; No Gnus
+  :group 'gnus-article-treat
+  :type '(choice (const nil)
+                (const :tag "all" t)
+                (regexp)))
+
 (defcustom gnus-treat-fold-headers nil
   "Fold headers.
 Valid values are nil, t, `head', `first', `last', an integer or a
@@ -1326,12 +1409,11 @@ predicate.  See Info node `(gnus)Customizing Articles'."
 
 (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', `first', `last', an integer or a
@@ -1363,10 +1445,7 @@ node `(gnus)X-Face' for details."
 
 (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', `first', `last', an integer or a
@@ -1379,12 +1458,7 @@ node `(gnus)X-Face' for details."
   :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', `first', `last', an integer or a
 predicate.  See Info node `(gnus)Customizing Articles' and Info
@@ -1549,13 +1623,6 @@ This requires GNU Libidn, and by default only enabled if it is found."
   '("January" "February" "March" "April" "May" "June" "July" "August"
     "September" "October" "November" "December"))
 
-(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.
-
 (defvar article-goto-body-goes-to-point-min-p nil)
 (defvar gnus-article-wash-types nil)
 (defvar gnus-article-emphasis-alist nil)
@@ -1762,7 +1829,7 @@ Initialized from `text-mode-syntax-table.")
   (interactive)
   ;; This function might be inhibited.
   (unless gnus-inhibit-hiding
-    (let ((inhibit-read-only nil)
+    (let ((inhibit-read-only t)
          (case-fold-search t)
          (max (1+ (length gnus-sorted-header-list)))
          (inhibit-point-motion-hooks t)
@@ -1924,7 +1991,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))
@@ -2093,16 +2164,21 @@ unfolded."
       (while (not (eobp))
        (save-restriction
          (mail-header-narrow-to-field)
-         (let ((header (buffer-string)))
+         (let* ((header (buffer-string))
+                (unfoldable
+                 (or (equal gnus-article-unfold-long-headers t)
+                     (and (stringp gnus-article-unfold-long-headers)
+                          (string-match gnus-article-unfold-long-headers header)))))
            (with-temp-buffer
              (insert header)
              (goto-char (point-min))
              (while (re-search-forward "\n[\t ]" nil t)
                (replace-match " " t t)))
-           (setq length (- (point-max) (point-min) 1)))
-         (when (< length (window-width))
-           (while (re-search-forward "\n[\t ]" nil t)
-             (replace-match " " t t)))
+           (setq length (- (point-max) (point-min) 1))
+           (when (or unfoldable
+                     (< length (window-width)))
+             (while (re-search-forward "\n[\t ]" nil t)
+               (replace-match " " t t))))
          (goto-char (point-max)))))))
 
 (defun gnus-article-treat-fold-headers ()
@@ -2149,6 +2225,39 @@ unfolded."
        (mail-header-fold-field)
        (goto-char (point-max))))))
 
+(defcustom gnus-article-truncate-lines default-truncate-lines
+  "Value of `truncate-lines' in Gnus Article buffer.
+Valid values are nil, t, `head', `first', `last', an integer or a
+predicate.  See Info node `(gnus)Customizing Articles'."
+  :version "23.0" ;; No Gnus
+  :group 'gnus-article
+  ;; :link '(custom-manual "(gnus)Customizing Articles")
+  :type 'boolean)
+
+(defun gnus-article-toggle-truncate-lines (&optional arg)
+  "Toggle whether to fold or truncate long lines in article the buffer.
+If ARG is non-nil and not a number, toggle
+`gnus-article-truncate-lines' too.  If ARG is a number, truncate
+long lines iff arg is positive."
+  (interactive "P")
+  (cond
+   ((and (numberp arg) (> arg 0))
+    (setq gnus-article-truncate-lines t))
+   ((numberp arg)
+    (setq gnus-article-truncate-lines nil))
+   (arg
+    (setq gnus-article-truncate-lines
+         (not gnus-article-truncate-lines))))
+  (gnus-with-article-buffer
+    (cond
+     ((and (numberp arg) (> arg 0))
+      (setq truncate-lines nil))
+     ((numberp arg)
+      (setq truncate-lines t)))
+    ;; In versions of Emacs 22 (CVS) before 2006-05-26,
+    ;; `toggle-truncate-lines' needs an argument.
+    (toggle-truncate-lines)))
+
 (defun gnus-article-treat-body-boundary ()
   "Place a boundary line at the end of the headers."
   (interactive)
@@ -2397,10 +2506,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:'."
@@ -2698,7 +2821,8 @@ Recurse into multiparts."
                 (add-hook 'gnus-exit-gnus-hook
                           (lambda  ()
                             (gnus-article-browse-delete-temp-files t)))
-                (browse-url tmp-file)
+                ;; FIXME: Warn if there's an <img> tag?
+                (browse-url-of-file tmp-file)
                 (setq showed t)))
              ;; If multipart, recurse
              ((and (stringp (car handle))
@@ -2708,9 +2832,16 @@ Recurse into multiparts."
                              (gnus-article-browse-html-parts handle))))))))
     showed))
 
-;; TODO: Key binding
+;; FIXME: Documentation in texi/gnus.texi missing.
 (defun gnus-article-browse-html-article ()
-  "View \"text/html\" parts of the current article with a WWW browser."
+  "View \"text/html\" parts of the current article with a WWW browser.
+
+Warning: Spammers use links to images in HTML articles to verify
+whether you have read the message.  As
+`gnus-article-browse-html-article' passes the unmodified HTML
+content to the browser without eliminatin these \"web bugs\" you
+should only use it for mails from trusted senders."
+  ;; Cf. `mm-w3m-safe-url-regexp'
   (interactive)
   (save-window-excursion
     ;; Open raw article and select the buffer
@@ -3422,10 +3553,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)
@@ -3450,7 +3584,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)
@@ -3463,6 +3598,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
@@ -3527,7 +3666,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."
@@ -3575,6 +3718,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.
@@ -3593,13 +3738,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'."
@@ -3613,9 +3766,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
@@ -3679,7 +3848,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)
@@ -3977,6 +4146,7 @@ commands:
   ;; Prevent recent Emacsen from displaying non-break space as "\ ".
   (set (make-local-variable 'nobreak-char-display) nil)
   (setq cursor-in-non-selected-windows nil)
+  (setq truncate-lines gnus-article-truncate-lines)
   (gnus-set-default-directory)
   (buffer-disable-undo)
   (setq buffer-read-only t
@@ -3985,6 +4155,10 @@ commands:
   (mm-enable-multibyte)
   (gnus-run-mode-hooks 'gnus-article-mode-hook))
 
+(defvar gnus-button-marker-list nil
+  "Regexp matching any of the regexps from `gnus-button-alist'.
+Internal variable.")
+
 (defun gnus-article-setup-buffer ()
   "Initialize the article buffer."
   (let* ((name (if gnus-single-article-buffer "*Article*"
@@ -3996,10 +4170,9 @@ commands:
     (setq gnus-article-buffer name)
     (setq gnus-original-article-buffer original)
     (setq gnus-article-mime-handle-alist nil)
-    ;; This might be a variable local to the summary buffer.
-    (unless gnus-single-article-buffer
-      (save-excursion
-       (set-buffer gnus-summary-buffer)
+    (with-current-buffer gnus-summary-buffer
+      ;; This might be a variable local to the summary buffer.
+      (unless gnus-single-article-buffer
        (setq gnus-article-buffer name)
        (setq gnus-original-article-buffer original)
        (gnus-set-global-variables)))
@@ -4040,6 +4213,8 @@ commands:
        (set-buffer (gnus-get-buffer-create name))
        (gnus-article-mode)
        (make-local-variable 'gnus-summary-buffer)
+       (setq gnus-summary-buffer
+             (gnus-summary-buffer-name gnus-newsgroup-name))
        (gnus-summary-set-local-parameters gnus-newsgroup-name)
        (current-buffer)))))
 
@@ -4251,7 +4426,7 @@ General format specifiers can also be used.  See Info node
   gnus-mime-button-menu gnus-mime-button-map "MIME button menu."
   `("MIME Part"
     ,@(mapcar (lambda (c)
-               (vector (caddr c) (car c) :enable t))
+               (vector (caddr c) (car c) :active t))
              gnus-mime-button-commands)))
 
 (defun gnus-mime-button-menu (event prefix)
@@ -4429,9 +4604,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))
@@ -4497,8 +4671,10 @@ Deleting parts may malfunction or destroy the article; continue? "))
         (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
@@ -4507,7 +4683,7 @@ Deleting parts may malfunction or destroy the article; continue? "))
             (format "View as MIME type (default %s): "
                     (car default))
             (mapcar #'list (mailcap-mime-types))
-            nil nil nil nil
+            pred nil nil nil
             (car default)))))
   (gnus-article-check-buffer)
   (let ((handle (get-text-property (point) 'gnus-data)))
@@ -4707,12 +4883,18 @@ specified charset."
         (mm-inlined-types nil)
         (mail-parse-charset gnus-newsgroup-charset)
         (mail-parse-ignored-charsets
-         (with-current-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.
@@ -4727,10 +4909,13 @@ If no internal viewer is available, use an external viewer."
          (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\)."
@@ -4858,6 +5043,12 @@ N is the numerical prefix."
   (interactive "p")
   (gnus-article-part-wrapper n 'gnus-mime-delete-part t))
 
+(defun gnus-article-view-part-as-type (n)
+  "Choose a MIME media type, and view part N as such.
+N is the numerical prefix."
+  (interactive "p")
+  (gnus-article-part-wrapper n 'gnus-mime-view-part-as-type t))
+
 (defun gnus-article-mime-match-handle-first (condition)
   (if condition
       (let (n)
@@ -4986,6 +5177,9 @@ N is the numerical prefix."
                ;; Exclude a newline.
                (1- (point))
              (point)))
+    (when gnus-article-button-face
+      (gnus-overlay-put (gnus-make-overlay b e)
+                        'face gnus-article-button-face))
     (widget-convert-button
      'link b e
      :mime-handle handle
@@ -5070,7 +5264,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\".
@@ -5430,17 +5628,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.
@@ -6228,9 +6464,24 @@ groups."
 ;;; Internal Variables:
 
 (defcustom gnus-button-url-regexp
-  (if (string-match "[[:digit:]]" "1") ;; support POSIX?
-      "\\b\\(\\(www\\.\\|\\(s?https?\\|ftp\\|file\\|gopher\\|nntp\\|news\\|telnet\\|wais\\|mailto\\|info\\):\\)\\(//[-a-z0-9_.]+:[0-9]*\\)?[-a-z0-9_=!?#$@~%&*+\\/:;.,[:word:]]+[-a-z0-9_=#$@~%&*+\\/[:word:]]\\)"
-    "\\b\\(\\(www\\.\\|\\(s?https?\\|ftp\\|file\\|gopher\\|nntp\\|news\\|telnet\\|wais\\|mailto\\|info\\):\\)\\(//[-a-z0-9_.]+:[0-9]*\\)?\\([-a-z0-9_=!?#$@~%&*+\\/:;.,]\\|\\w\\)+\\([-a-z0-9_=#$@~%&*+\\/]\\|\\w\\)\\)")
+  (concat
+   "\\b\\(\\(www\\.\\|\\(s?https?\\|ftp\\|file\\|gopher\\|"
+   "nntp\\|news\\|telnet\\|wais\\|mailto\\|info\\):\\)"
+   "\\(//[-a-z0-9_.]+:[0-9]*\\)?"
+   (if (string-match "[[:digit:]]" "1") ;; Support POSIX?
+       (let ((chars "-a-z0-9_=#$@~%&*+\\/[:word:]")
+            (punct "!?:;.,"))
+        (concat
+         "\\(?:"
+         ;; Match paired parentheses, e.g. in WikiPedia URLs:
+         "[" chars punct "]+" "(" "[" chars punct "]+" "[" chars "]*)" "[" chars "]"
+         "\\|"
+         "[" chars punct     "]+" "[" chars "]"
+         "\\)"))
+     (concat ;; XEmacs 21.4 doesn't support POSIX.
+      "\\([-a-z0-9_=!?#$@~%&*+\\/:;.,]\\|\\w\\)+"
+      "\\([-a-z0-9_=#$@~%&*+\\/]\\|\\w\\)"))
+   "\\)")
   "Regular expression that matches URLs."
   :group 'gnus-article-buttons
   :type 'regexp)
@@ -6486,7 +6737,10 @@ address, `ask' if unsure and `invalid' if the string is invalid."
   "Call function FUN on argument ARG.
 Both FUN and ARG are supposed to be strings.  ARG will be passed
 as a symbol to FUN."
-  (funcall (intern fun) (intern arg)))
+  (funcall (intern fun)
+          (if (string-match "^customize-apropos" fun)
+              arg
+            (intern arg))))
 
 (defvar gnus-button-handle-describe-prefix "^\\(C-h\\|<?[Ff]1>?\\)")
 
@@ -6630,6 +6884,8 @@ positives are possible."
      0 (>= gnus-button-message-level 0) gnus-button-message-id 2)
     ("\\bin\\( +article\\| +message\\)? +\\(<\\([^\n @<>]+@[^\n @<>]+\\)>\\)"
      2 (>= gnus-button-message-level 0) gnus-button-message-id 3)
+    ("\\b\\(mid\\|message-id\\):? +\\(<\\([^\n @<>]+@[^\n @<>]+\\)>\\)"
+     2 (>= gnus-button-message-level 0) gnus-button-message-id 3)
     ("\\(<URL: *\\)mailto: *\\([^> \n\t]+\\)>"
      0 (>= gnus-button-message-level 0) gnus-url-mailto 2)
     ;; RFC 2368 (The mailto URL scheme)
@@ -6685,7 +6941,7 @@ positives are possible."
      1 (>= gnus-button-emacs-level 8) gnus-button-handle-library 1)
     ("`\\([a-z][-a-z0-9]+\\.el\\)'"
      1 (>= gnus-button-emacs-level 8) gnus-button-handle-library 1)
-    ("`\\([a-z][a-z0-9]+-[a-z]+-[-a-z]+\\|\\(gnus\\|message\\)-[-a-z]+\\)'"
+    ("`\\([a-z][a-z0-9]+-[a-z0-9]+-[-a-z0-9]*[a-z]\\|\\(gnus\\|message\\)-[-a-z]+\\)'"
      0 (>= gnus-button-emacs-level 8) gnus-button-handle-symbol 1)
     ("`\\([a-z][a-z0-9]+-[a-z]+\\)'"
      0 (>= gnus-button-emacs-level 9) gnus-button-handle-symbol 1)
@@ -7167,8 +7423,13 @@ specified by `gnus-button-alist'."
   (with-current-buffer gnus-summary-buffer
     (gnus-summary-refer-article message-id)))
 
-(defun gnus-button-fetch-group (address)
+(defun gnus-button-fetch-group (address &rest ignore)
   "Fetch GROUP specified by ADDRESS."
+  (when (string-match "\\`\\(nntp\\|news\\):\\(//\\)?\\(.*\\)\\'"
+                     address)
+    ;; Allow to use `gnus-button-fetch-group' in `browse-url-browser-function'
+    ;; for nntp:// and news://
+    (setq address (match-string 3 address)))
   (if (not (string-match "[:/]" address))
       ;; This is just a simple group url.
       (gnus-group-read-ephemeral-group address gnus-select-method)
@@ -7258,19 +7519,23 @@ specified by `gnus-button-alist'."
     map))
 
 (defun gnus-insert-prev-page-button ()
-  (let ((b (point))
+  (let ((b (point)) e
        (inhibit-read-only t))
     (gnus-eval-format
      gnus-prev-page-line-format nil
      `(keymap ,gnus-prev-page-map
-        gnus-prev t
-        gnus-callback gnus-article-button-prev-page
-        article-type annotation))
+             gnus-prev t
+             gnus-callback gnus-article-button-prev-page
+             article-type annotation))
+    (setq e (if (bolp)
+               ;; Exclude a newline.
+               (1- (point))
+             (point)))
+    (when gnus-article-button-face
+      (gnus-overlay-put (gnus-make-overlay b e)
+                        'face gnus-article-button-face))
     (widget-convert-button
-     'link b (if (bolp)
-                ;; Exclude a newline.
-                (1- (point))
-              (point))
+     'link b e
      :action 'gnus-button-prev-page
      :button-keymap gnus-prev-page-map)))
 
@@ -7291,18 +7556,22 @@ specified by `gnus-button-alist'."
     (select-window win)))
 
 (defun gnus-insert-next-page-button ()
-  (let ((b (point))
+  (let ((b (point)) e
        (inhibit-read-only t))
     (gnus-eval-format gnus-next-page-line-format nil
                      `(keymap ,gnus-next-page-map
-                         gnus-next t
-                         gnus-callback gnus-article-button-next-page
-                         article-type annotation))
+                               gnus-next t
+                               gnus-callback gnus-article-button-next-page
+                               article-type annotation))
+    (setq e (if (bolp)
+               ;; Exclude a newline.
+               (1- (point))
+             (point)))
+    (when gnus-article-button-face
+      (gnus-overlay-put (gnus-make-overlay b e)
+                        'face gnus-article-button-face))
     (widget-convert-button
-     'link b (if (bolp)
-                ;; Exclude a newline.
-                (1- (point))
-              (point))
+     'link b e
      :action 'gnus-button-next-page
      :button-keymap gnus-next-page-map)))
 
@@ -7562,7 +7831,7 @@ For example:
     ,@(delq nil
            (mapcar (lambda (c)
                      (unless (eq (car c) 'undefined)
-                       (vector (caddr c) (car c) :enable t)))
+                       (vector (caddr c) (car c) :active t)))
                    gnus-mime-security-button-commands))))
 
 (defun gnus-mime-security-button-menu (event prefix)
@@ -7687,6 +7956,9 @@ For example:
                ;; Exclude a newline.
                (1- (point))
              (point)))
+    (when gnus-article-button-face
+      (gnus-overlay-put (gnus-make-overlay b e)
+                        'face gnus-article-button-face))
     (widget-convert-button
      'link b e
      :mime-handle handle