Revision: miles@gnu.org--gnu-2005/gnus--devo--0--patch-151
[gnus] / lisp / gnus-art.el
index c0481bd..5c3c0bb 100644 (file)
@@ -1,5 +1,5 @@
 ;;; gnus-art.el --- article mode commands for Gnus
-;; Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004
+;; Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005
 ;;        Free Software Foundation, Inc.
 
 ;; Author: Lars Magne Ingebrigtsen <larsi@gnus.org>
@@ -19,8 +19,8 @@
 
 ;; You should have received a copy of the GNU General Public License
 ;; along with GNU Emacs; see the file COPYING.  If not, write to the
-;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
-;; Boston, MA 02111-1307, USA.
+;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+;; Boston, MA 02110-1301, USA.
 
 ;;; Commentary:
 
   (defvar tool-bar-map))
 
 (require 'gnus)
-(require 'gnus-sum)
+;; Avoid the "Recursive load suspected" error in Emacs 21.1.
+(eval-and-compile
+  (let ((recursive-load-depth-limit 100))
+    (require 'gnus-sum)))
 (require 'gnus-spec)
 (require 'gnus-int)
 (require 'gnus-win)
@@ -46,6 +49,7 @@
 (autoload 'gnus-msg-mail "gnus-msg" nil t)
 (autoload 'gnus-button-mailto "gnus-msg")
 (autoload 'gnus-button-reply "gnus-msg" nil t)
+(autoload 'parse-time-string "parse-time" nil nil)
 (autoload 'ansi-color-apply-on-region "ansi-color")
 
 (defgroup gnus-article nil
@@ -214,6 +218,7 @@ By default, if you set this t, then Gnus will display citations and
 signatures, but will never scroll down to show you a page consisting
 only of boring text.  Boring text is controlled by
 `gnus-article-boring-faces'."
+  :version "22.1"
   :type 'boolean
   :group 'gnus-article-hiding)
 
@@ -320,27 +325,62 @@ advertisements.  For example:
                   (symbol :tag "Item in `gnus-article-banner-alist'" none)
                   regexp
                   (const :tag "None" nil))))
+  :version "22.1"
   :group 'gnus-article-washing)
 
+(defmacro gnus-emphasis-custom-with-format (&rest body)
+  `(let ((format "\
+\\(\\s-\\|^\\|\\=\\|[-\"]\\|\\s(\\)\\(%s\\(\\w+\\(\\s-+\\w+\\)*[.,]?\\)%s\\)\
+\\(\\([-,.;:!?\"]\\|\\s)\\)+\\s-\\|[?!.]\\s-\\|\\s)\\|\\s-\\)"))
+     ,@body))
+
+(defun gnus-emphasis-custom-value-to-external (value)
+  (gnus-emphasis-custom-with-format
+   (if (consp (car value))
+       (list (format format (car (car value)) (cdr (car value)))
+            2
+            (if (nth 1 value) 2 3)
+            (nth 2 value))
+     value)))
+
+(defun gnus-emphasis-custom-value-to-internal (value)
+  (gnus-emphasis-custom-with-format
+   (let ((regexp (concat "\\`"
+                        (format (regexp-quote format)
+                                "\\([^()]+\\)" "\\([^()]+\\)")
+                        "\\'"))
+        pattern)
+     (if (string-match regexp (setq pattern (car value)))
+        (list (cons (match-string 1 pattern) (match-string 2 pattern))
+              (= (nth 2 value) 2)
+              (nth 3 value))
+       value))))
+
 (defcustom gnus-emphasis-alist
-  (let ((format
-        "\\(\\s-\\|^\\|\\=\\|[-\"]\\|\\s(\\)\\(%s\\(\\w+\\(\\s-+\\w+\\)*[.,]?\\)%s\\)\\(\\([-,.;:!?\"]\\|\\s)\\)+\\s-\\|[?!.]\\s-\\|\\s)\\|\\s-\\)")
-       (types
-        '(("\\*" "\\*" bold)
+  (let ((types
+        '(("\\*" "\\*" bold nil 2)
           ("_" "_" underline)
           ("/" "/" italic)
           ("_/" "/_" underline-italic)
           ("_\\*" "\\*_" underline-bold)
           ("\\*/" "/\\*" bold-italic)
           ("_\\*/" "/\\*_" underline-bold-italic))))
-    `(,@(mapcar
-        (lambda (spec)
-          (list
-           (format format (car spec) (cadr spec))
-           2 3 (intern (format "gnus-emphasis-%s" (nth 2 spec)))))
-        types)
-       ("\\(\\s-\\|^\\)\\(_\\(\\(\\w\\|_[^_]\\)+\\)_\\)\\(\\s-\\|[?!.,;]\\)"
-        2 3 gnus-emphasis-underline)))
+    (nconc
+     (gnus-emphasis-custom-with-format
+      (mapcar (lambda (spec)
+               (list (format format (car spec) (cadr spec))
+                     (or (nth 3 spec) 2)
+                     (or (nth 4 spec) 3)
+                     (intern (format "gnus-emphasis-%s" (nth 2 spec)))))
+             types))
+     '(;; I've never seen anyone use this strikethru convention whereas I've
+       ;; several times seen it triggered by normal text.  --Stef
+       ;; Miles suggests that this form is sometimes used but for italics,
+       ;; so maybe we should map it to `italic'.
+       ;; ("\\(\\s-\\|^\\)\\(-\\(\\(\\w\\|-[^-]\\)+\\)-\\)\\(\\s-\\|[?!.,;]\\)"
+       ;; 2 3 gnus-emphasis-strikethru)
+       ("\\(\\s-\\|^\\)\\(_\\(\\(\\w\\|_[^_]\\)+\\)_\\)\\(\\s-\\|[?!.,;]\\)"
+       2 3 gnus-emphasis-underline))))
   "*Alist that says how to fontify certain phrases.
 Each item looks like this:
 
@@ -351,11 +391,43 @@ is a number that says what regular expression grouping used to find
 the entire emphasized word.  The third is a number that says what
 regexp grouping should be displayed and highlighted.  The fourth
 is the face used for highlighting."
-  :type '(repeat (list :value ("" 0 0 default)
-                      regexp
-                      (integer :tag "Match group")
-                      (integer :tag "Emphasize group")
-                      face))
+  :type
+  '(repeat
+    (menu-choice
+     :format "%[Customizing Style%]\n%v"
+     :indent 2
+     (group :tag "Default"
+           :value ("" 0 0 default)
+           :value-create
+           (lambda (widget)
+             (let ((value (widget-get
+                           (cadr (widget-get (widget-get widget :parent)
+                                             :args))
+                           :value)))
+               (if (not (eq (nth 2 value) 'default))
+                   (widget-put
+                    widget
+                    :value
+                    (gnus-emphasis-custom-value-to-external value))))
+             (widget-group-value-create widget))
+           regexp
+           (integer :format "Match group: %v")
+           (integer :format "Emphasize group: %v")
+           face)
+     (group :tag "Simple"
+           :value (("_" . "_") nil default)
+           (cons :format "%v"
+                 (regexp :format "Start regexp: %v")
+                 (regexp :format "End regexp: %v"))
+           (boolean :format "Show start and end patterns: %[%v%]\n"
+                    :on " On " :off " Off ")
+           face)))
+  :get (lambda (symbol)
+        (mapcar 'gnus-emphasis-custom-value-to-internal
+                (default-value symbol)))
+  :set (lambda (symbol value)
+        (set-default symbol (mapcar 'gnus-emphasis-custom-value-to-external
+                                    value)))
   :group 'gnus-article-emphasis)
 
 (defcustom gnus-emphasize-whitespace-regexp "^[ \t]+\\|[ \t]*\n"
@@ -419,9 +491,6 @@ be fed to `format-time-string'."
   :link '(custom-manual "(gnus)Article Date")
   :group 'gnus-article-washing)
 
-(eval-and-compile
-  (autoload 'mail-extract-address-components "mail-extr"))
-
 (defcustom gnus-save-all-headers t
   "*If non-nil, don't remove any headers before saving."
   :group 'gnus-article-saving
@@ -507,17 +576,19 @@ you could set this variable to something like:
  '((\"^Subject:.*gnus\\|^Newsgroups:.*gnus\" \"gnus-stuff\")
    (\"^Subject:.*vm\\|^Xref:.*vm\" \"vm-stuff\"))
 
-This variable is an alist where the where the key is the match and the
-value is a list of possible files to save in if the match is non-nil.
+This variable is an alist where the key is the match and the
+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.
+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.
 
-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
-be used as possible file names."
+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 be used as possible file names."
   :group 'gnus-article-saving
   :type '(repeat (choice (list :value (fun) function)
                         (cons :value ("" "") regexp (repeat string))
@@ -563,6 +634,13 @@ The following additional specs are available:
   :type 'hook
   :group 'gnus-article-various)
 
+(defcustom gnus-copy-article-ignored-headers nil
+  "List of headers to be removed when copying an article.
+Each element is a regular expression."
+  :version "23.0" ;; No Gnus
+  :type '(repeat regexp)
+  :group 'gnus-article-various)
+
 (make-obsolete-variable 'gnus-article-hide-pgp-hook
                        "This variable is obsolete in Gnus 5.10.")
 
@@ -582,21 +660,23 @@ above them."
   :type 'face
   :group 'gnus-article-buttons)
 
-(defcustom gnus-signature-face 'gnus-signature-face
+(defcustom gnus-signature-face 'gnus-signature
   "Face used for highlighting a signature in the article buffer.
-Obsolete; use the face `gnus-signature-face' for customizations instead."
+Obsolete; use the face `gnus-signature' for customizations instead."
   :type 'face
   :group 'gnus-article-highlight
   :group 'gnus-article-signature)
 
-(defface gnus-signature-face
+(defface gnus-signature
   '((t
      (:italic t)))
   "Face used for highlighting a signature in the article buffer."
   :group 'gnus-article-highlight
   :group 'gnus-article-signature)
+;; backward-compatibility alias
+(put 'gnus-signature-face 'face-alias 'gnus-signature)
 
-(defface gnus-header-from-face
+(defface gnus-header-from
   '((((class color)
       (background dark))
      (:foreground "spring green"))
@@ -608,8 +688,10 @@ Obsolete; use the face `gnus-signature-face' for customizations instead."
   "Face used for displaying from headers."
   :group 'gnus-article-headers
   :group 'gnus-article-highlight)
+;; backward-compatibility alias
+(put 'gnus-header-from-face 'face-alias 'gnus-header-from)
 
-(defface gnus-header-subject-face
+(defface gnus-header-subject
   '((((class color)
       (background dark))
      (:foreground "SeaGreen3"))
@@ -621,8 +703,10 @@ Obsolete; use the face `gnus-signature-face' for customizations instead."
   "Face used for displaying subject headers."
   :group 'gnus-article-headers
   :group 'gnus-article-highlight)
+;; backward-compatibility alias
+(put 'gnus-header-subject-face 'face-alias 'gnus-header-subject)
 
-(defface gnus-header-newsgroups-face
+(defface gnus-header-newsgroups
   '((((class color)
       (background dark))
      (:foreground "yellow" :italic t))
@@ -636,8 +720,10 @@ In the default setup this face is only used for crossposted
 articles."
   :group 'gnus-article-headers
   :group 'gnus-article-highlight)
+;; backward-compatibility alias
+(put 'gnus-header-newsgroups-face 'face-alias 'gnus-header-newsgroups)
 
-(defface gnus-header-name-face
+(defface gnus-header-name
   '((((class color)
       (background dark))
      (:foreground "SeaGreen"))
@@ -649,8 +735,10 @@ articles."
   "Face used for displaying header names."
   :group 'gnus-article-headers
   :group 'gnus-article-highlight)
+;; backward-compatibility alias
+(put 'gnus-header-name-face 'face-alias 'gnus-header-name)
 
-(defface gnus-header-content-face
+(defface gnus-header-content
   '((((class color)
       (background dark))
      (:foreground "forest green" :italic t))
@@ -661,12 +749,14 @@ articles."
      (:italic t)))  "Face used for displaying header content."
   :group 'gnus-article-headers
   :group 'gnus-article-highlight)
+;; backward-compatibility alias
+(put 'gnus-header-content-face 'face-alias 'gnus-header-content)
 
 (defcustom gnus-header-face-alist
-  '(("From" nil gnus-header-from-face)
-    ("Subject" nil gnus-header-subject-face)
-    ("Newsgroups:.*," nil gnus-header-newsgroups-face)
-    ("" gnus-header-name-face gnus-header-content-face))
+  '(("From" nil gnus-header-from)
+    ("Subject" nil gnus-header-subject)
+    ("Newsgroups:.*," nil gnus-header-newsgroups)
+    ("" gnus-header-name gnus-header-content))
   "*Controls highlighting of article headers.
 
 An alist of the form (HEADER NAME CONTENT).
@@ -745,7 +835,7 @@ If set, this variable overrides `gnus-unbuttonized-mime-types'.
 To see e.g. security buttons you could set this to
 `(\"multipart/signed\")'.
 This variable is only used when `gnus-inhibit-mime-unbuttonizing' is nil."
-  :version "21.1"
+  :version "22.1"
   :group 'gnus-article-mime
   :type '(repeat regexp))
 
@@ -754,7 +844,7 @@ This variable is only used when `gnus-inhibit-mime-unbuttonizing' is nil."
 When nil (the default value), then some MIME parts do not get buttons,
 as described by the variables `gnus-buttonized-mime-types' and
 `gnus-unbuttonized-mime-types'."
-  :version "21.3"
+  :version "22.1"
   :group 'gnus-article-mime
   :type 'boolean)
 
@@ -762,6 +852,7 @@ as described by the variables `gnus-buttonized-mime-types' and
   "String used to delimit header and body.
 This variable is used by `gnus-article-treat-body-boundary' which can
 be controlled by `gnus-treat-body-boundary'."
+  :version "22.1"
   :group 'gnus-article-various
   :type '(choice (item :tag "None" :value nil)
                 string))
@@ -771,6 +862,7 @@ be controlled by `gnus-treat-body-boundary'."
   "Defines the location of the faces database.
 For information on obtaining this database of pretty pictures, please
 see http://www.cs.indiana.edu/picons/ftp/index.html"
+  :version "22.1"
   :type '(repeat directory)
   :link '(url-link :tag "download"
                   "http://www.cs.indiana.edu/picons/ftp/index.html")
@@ -910,6 +1002,7 @@ See Info node `(gnus)Customizing Articles' for details."
   "Remove carriage returns.
 Valid values are nil, t, `head', `last', an integer or a predicate.
 See Info node `(gnus)Customizing Articles' for details."
+  :version "22.1"
   :group 'gnus-article-treat
   :link '(custom-manual "(gnus)Customizing Articles")
   :type gnus-article-treat-custom)
@@ -918,6 +1011,7 @@ See Info node `(gnus)Customizing Articles' for details."
   "Remove newlines from within URLs.
 Valid values are nil, t, `head', `last', an integer or a predicate.
 See Info node `(gnus)Customizing Articles' for details."
+  :version "22.1"
   :group 'gnus-article-treat
   :link '(custom-manual "(gnus)Customizing Articles")
   :type gnus-article-treat-custom)
@@ -926,6 +1020,7 @@ See Info node `(gnus)Customizing Articles' for details."
   "Remove leading whitespace in headers.
 Valid values are nil, t, `head', `last', an integer or a predicate.
 See Info node `(gnus)Customizing Articles' for details."
+  :version "22.1"
   :group 'gnus-article-treat
   :link '(custom-manual "(gnus)Customizing Articles")
   :type gnus-article-treat-custom)
@@ -1045,6 +1140,7 @@ See Info node `(gnus)Customizing Articles' for details."
   "Display the Date in a format that can be read aloud in English.
 Valid values are nil, t, `head', `last', an integer or a predicate.
 See Info node `(gnus)Customizing Articles' for details."
+  :version "22.1"
   :group 'gnus-article-treat
   :link '(custom-manual "(gnus)Customizing Articles")
   :type gnus-article-treat-head-custom)
@@ -1120,6 +1216,7 @@ See Info node `(gnus)Customizing Articles' for details."
   "Unfold folded header lines.
 Valid values are nil, t, `head', `last', an integer or a predicate.
 See Info node `(gnus)Customizing Articles' for details."
+  :version "22.1"
   :group 'gnus-article-treat
   :link '(custom-manual "(gnus)Customizing Articles")
   :type gnus-article-treat-custom)
@@ -1128,6 +1225,7 @@ See Info node `(gnus)Customizing Articles' for details."
   "Fold headers.
 Valid values are nil, t, `head', `last', an integer or a predicate.
 See Info node `(gnus)Customizing Articles' for details."
+  :version "22.1"
   :group 'gnus-article-treat
   :link '(custom-manual "(gnus)Customizing Articles")
   :type gnus-article-treat-custom)
@@ -1136,6 +1234,7 @@ See Info node `(gnus)Customizing Articles' for details."
   "Fold the Newsgroups and Followup-To headers.
 Valid values are nil, t, `head', `last', an integer or a predicate.
 See Info node `(gnus)Customizing Articles' for details."
+  :version "22.1"
   :group 'gnus-article-treat
   :link '(custom-manual "(gnus)Customizing Articles")
   :type gnus-article-treat-custom)
@@ -1209,7 +1308,7 @@ Valid values are nil, t, `head', `last', an integer or a predicate.
 See Info node `(gnus)Customizing Articles' and Info node
 `(gnus)X-Face' for details."
   :group 'gnus-article-treat
-  :version "21.1"
+  :version "22.1"
   :link '(custom-manual "(gnus)Customizing Articles")
   :link '(custom-manual "(gnus)X-Face")
   :type gnus-article-treat-head-custom)
@@ -1240,6 +1339,7 @@ See Info node `(gnus)Customizing Articles' and Info node
 Valid values are nil, t, `head', `last', an integer or a predicate.
 See Info node `(gnus)Customizing Articles' and Info node
 `(gnus)Picons' for details."
+  :version "22.1"
   :group 'gnus-article-treat
   :group 'gnus-picon
   :link '(custom-manual "(gnus)Customizing Articles")
@@ -1255,6 +1355,7 @@ See Info node `(gnus)Customizing Articles' and Info node
 Valid values are nil, t, `head', `last', an integer or a predicate.
 See Info node `(gnus)Customizing Articles' and Info node
 `(gnus)Picons' for details."
+  :version "22.1"
   :group 'gnus-article-treat
   :group 'gnus-picon
   :link '(custom-manual "(gnus)Customizing Articles")
@@ -1270,6 +1371,7 @@ See Info node `(gnus)Customizing Articles' and Info node
 Valid values are nil, t, `head', `last', an integer or a predicate.
 See Info node `(gnus)Customizing Articles' and Info node
 `(gnus)Picons' for details."
+  :version "22.1"
   :group 'gnus-article-treat
   :group 'gnus-picon
   :link '(custom-manual "(gnus)Customizing Articles")
@@ -1278,14 +1380,15 @@ See Info node `(gnus)Customizing Articles' and Info node
 (put 'gnus-treat-newsgroups-picon 'highlight t)
 
 (defcustom gnus-treat-body-boundary
-  (if (or gnus-treat-newsgroups-picon
-         gnus-treat-mail-picon
-         gnus-treat-from-picon)
+  (if (and (eq window-system 'x)
+          (or gnus-treat-newsgroups-picon
+              gnus-treat-mail-picon
+              gnus-treat-from-picon))
       'head nil)
   "Draw a boundary at the end of the headers.
 Valid values are nil and `head'.
 See Info node `(gnus)Customizing Articles' for details."
-  :version "21.1"
+  :version "22.1"
   :group 'gnus-article-treat
   :link '(custom-manual "(gnus)Customizing Articles")
   :type gnus-article-treat-head-custom)
@@ -1303,6 +1406,7 @@ See Info node `(gnus)Customizing Articles' for details."
   "Format as HTML.
 Valid values are nil, t, `head', `last', an integer or a predicate.
 See Info node `(gnus)Customizing Articles' for details."
+  :version "22.1"
   :group 'gnus-article-treat
   :link '(custom-manual "(gnus)Customizing Articles")
   :type gnus-article-treat-custom)
@@ -1338,6 +1442,7 @@ See Info node `(gnus)Customizing Articles' for details."
 To automatically treat X-PGP-Sig, set it to head.
 Valid values are nil, t, `head', `last', an integer or a predicate.
 See Info node `(gnus)Customizing Articles' for details."
+  :version "22.1"
   :group 'gnus-article-treat
   :group 'mime-security
   :link '(custom-manual "(gnus)Customizing Articles")
@@ -1351,6 +1456,7 @@ See Info node `(gnus)Customizing Articles' for details."
 (defcustom gnus-article-encrypt-protocol "PGP"
   "The protocol used for encrypt articles.
 It is a string, such as \"PGP\". If nil, ask user."
+  :version "22.1"
   :type 'string
   :group 'mime-security)
 
@@ -1362,11 +1468,13 @@ It is a string, such as \"PGP\". If nil, ask user."
                              (executable-find idna-program))
   "Whether IDNA decoding of headers is used when viewing messages.
 This requires GNU Libidn, and by default only enabled if it is found."
+  :version "22.1"
   :group 'gnus-article-headers
   :type 'boolean)
 
 (defcustom gnus-article-over-scroll nil
   "If non-nil, allow scrolling the article buffer even when there no more text."
+  :version "22.1"
   :group 'gnus-article
   :type 'boolean)
 
@@ -1477,7 +1585,7 @@ Initialized from `text-mode-syntax-table.")
   `(save-excursion
      (set-buffer gnus-article-buffer)
      (save-restriction
-       (let ((buffer-read-only nil)
+       (let ((inhibit-read-only t)
             (inhibit-point-motion-hooks t)
             (case-fold-search t))
         (article-narrow-to-head)
@@ -1489,7 +1597,7 @@ Initialized from `text-mode-syntax-table.")
 (defmacro gnus-with-article-buffer (&rest forms)
   `(save-excursion
      (set-buffer gnus-article-buffer)
-     (let ((buffer-read-only nil))
+     (let ((inhibit-read-only t))
        ,@forms)))
 
 (put 'gnus-with-article-buffer 'lisp-indent-function 0)
@@ -1575,7 +1683,7 @@ Initialized from `text-mode-syntax-table.")
   (interactive)
   ;; This function might be inhibited.
   (unless gnus-inhibit-hiding
-    (let ((buffer-read-only nil)
+    (let ((inhibit-read-only nil)
          (case-fold-search t)
          (max (1+ (length gnus-sorted-header-list)))
          (inhibit-point-motion-hooks t)
@@ -1645,13 +1753,10 @@ always hide."
             (not gnus-show-all-headers))
     (save-excursion
       (save-restriction
-       (let ((buffer-read-only nil)
-             (list gnus-boring-article-headers)
-             (inhibit-point-motion-hooks t)
-             elem)
+       (let ((inhibit-read-only t)
+             (inhibit-point-motion-hooks t))
          (article-narrow-to-head)
-         (while list
-           (setq elem (pop list))
+         (dolist (elem gnus-boring-article-headers)
            (goto-char (point-min))
            (cond
             ;; Hide empty headers.
@@ -1799,7 +1904,7 @@ always hide."
 (defun article-normalize-headers ()
   "Make all header lines 40 characters long."
   (interactive)
-  (let ((buffer-read-only nil)
+  (let ((inhibit-read-only t)
        column)
     (save-excursion
       (save-restriction
@@ -1843,7 +1948,7 @@ FROM is a string of characters to translate from; to is a string of
 characters to translate to."
   (save-excursion
     (when (article-goto-body)
-      (let ((buffer-read-only nil)
+      (let ((inhibit-read-only t)
            (x (make-string 225 ?x))
            (i -1))
        (while (< (incf i) (length x))
@@ -1859,9 +1964,8 @@ characters to translate to."
 MAP is an alist where the elements are on the form (\"from\" \"to\")."
   (save-excursion
     (when (article-goto-body)
-      (let ((buffer-read-only nil)
-           elem)
-       (while (setq elem (pop map))
+      (let ((inhibit-read-only t))
+       (dolist (elem map)
          (save-excursion
            (while (search-forward (car elem) nil t)
              (replace-match (cadr elem)))))))))
@@ -1871,7 +1975,7 @@ MAP is an alist where the elements are on the form (\"from\" \"to\")."
   (interactive)
   (save-excursion
     (when (article-goto-body)
-      (let ((buffer-read-only nil))
+      (let ((inhibit-read-only t))
        (while (search-forward "\b" nil t)
          (let ((next (char-after))
                (previous (char-after (- (point) 2))))
@@ -1897,7 +2001,7 @@ MAP is an alist where the elements are on the form (\"from\" \"to\")."
   (interactive)
   (save-excursion
     (when (article-goto-body)
-      (let ((buffer-read-only nil))
+      (let ((inhibit-read-only t))
        (ansi-color-apply-on-region (point) (point-max))))))
 
 (defun gnus-article-treat-unfold-headers ()
@@ -1987,7 +2091,7 @@ unfolded."
   "Fill lines that are wider than the window width."
   (interactive)
   (save-excursion
-    (let ((buffer-read-only nil)
+    (let ((inhibit-read-only t)
          (width (window-width (get-buffer-window (current-buffer)))))
       (save-restriction
        (article-goto-body)
@@ -2007,7 +2111,7 @@ unfolded."
   "Capitalize the first word in each sentence."
   (interactive)
   (save-excursion
-    (let ((buffer-read-only nil)
+    (let ((inhibit-read-only t)
          (paragraph-start "^[\n\^L]"))
       (article-goto-body)
       (while (not (eobp))
@@ -2018,7 +2122,7 @@ unfolded."
   "Remove trailing CRs and then translate remaining CRs into LFs."
   (interactive)
   (save-excursion
-    (let ((buffer-read-only nil))
+    (let ((inhibit-read-only t))
       (goto-char (point-min))
       (while (re-search-forward "\r+$" nil t)
        (replace-match "" t t))
@@ -2030,7 +2134,7 @@ unfolded."
   "Remove all trailing blank lines from the article."
   (interactive)
   (save-excursion
-    (let ((buffer-read-only nil))
+    (let ((inhibit-read-only t))
       (goto-char (point-max))
       (delete-region
        (point)
@@ -2058,33 +2162,33 @@ unfolded."
       ;; read-only.
       (if (and wash-face-p (memq 'face gnus-article-wash-types))
          (gnus-delete-images 'face)
-       (let (face faces)
-         (save-excursion
+       (let (face faces from)
+         (save-current-buffer
            (when (and wash-face-p
-                      (progn
-                        (goto-char (point-min))
-                        (not (re-search-forward "^Face:[\t ]*" nil t)))
-                      (gnus-buffer-live-p gnus-original-article-buffer))
+                      (gnus-buffer-live-p gnus-original-article-buffer)
+                      (not (re-search-forward "^Face:[\t ]*" nil t)))
              (set-buffer gnus-original-article-buffer))
            (save-restriction
              (mail-narrow-to-head)
              (while (gnus-article-goto-header "Face")
-               (setq faces (nconc faces (list (mail-header-field-value)))))))
-         (while (setq face (pop faces))
-           (let ((png (gnus-convert-face-to-png face))
-                 image)
-             (when png
-               (setq image
-                     (apply 'gnus-create-image png 'png t
-                            (cdr (assq 'png gnus-face-properties-alist))))
-               (gnus-article-goto-header "from")
-               (when (bobp)
-                 (insert "From: [no `from' set]\n")
-                 (forward-char -17))
-               (gnus-add-wash-type 'face)
-               (gnus-add-image 'face image)
-               (gnus-put-image image nil 'face))))))
-      )))
+               (push (mail-header-field-value) faces))))
+         (when faces
+           (goto-char (point-min))
+           (let ((from (gnus-article-goto-header "from"))
+                 png image)
+             (unless from
+               (insert "From:")
+               (setq from (point))
+               (insert "[no `from' set]\n"))
+             (while faces
+               (when (setq png (gnus-convert-face-to-png (pop faces)))
+                 (setq image
+                       (apply 'gnus-create-image png 'png t
+                              (cdr (assq 'png gnus-face-properties-alist))))
+                 (goto-char from)
+                 (gnus-add-wash-type 'face)
+                 (gnus-add-image 'face image)
+                 (gnus-put-image image nil 'face))))))))))
 
 (defun article-display-x-face (&optional force)
   "Look for an X-Face header and display it if present."
@@ -2101,13 +2205,10 @@ unfolded."
          (gnus-delete-images 'xface)
        ;; Display X-Faces.
        (let (x-faces from face)
-         (save-excursion
+         (save-current-buffer
            (when (and wash-face-p
-                      (progn
-                        (goto-char (point-min))
-                        (not (re-search-forward
-                              "^X-Face\\(-[0-9]+\\)?:[\t ]*" nil t)))
-                      (gnus-buffer-live-p gnus-original-article-buffer))
+                      (gnus-buffer-live-p gnus-original-article-buffer)
+                      (not (re-search-forward "^X-Face:[\t ]*" nil t)))
              ;; If type `W f', use gnus-original-article-buffer,
              ;; otherwise use the current buffer because displaying
              ;; RFC822 parts calls this function too.
@@ -2121,34 +2222,36 @@ unfolded."
          ;; single external face.
          (when (stringp gnus-article-x-face-command)
            (setq x-faces (list (car x-faces))))
-         (while (and (setq face (pop x-faces))
-                     gnus-article-x-face-command
-                     (or force
-                         ;; Check whether this face is censored.
-                         (not gnus-article-x-face-too-ugly)
-                         (and gnus-article-x-face-too-ugly from
-                              (not (string-match gnus-article-x-face-too-ugly
-                                                 from)))))
-           ;; We display the face.
-           (cond ((stringp gnus-article-x-face-command)
-                  ;; The command is a string, so we interpret the command
-                  ;; as a, well, command, and fork it off.
-                  (let ((process-connection-type nil))
-                    (process-kill-without-query
-                     (start-process
-                      "article-x-face" nil shell-file-name
-                      shell-command-switch gnus-article-x-face-command))
-                    (with-temp-buffer
-                      (insert face)
-                      (process-send-region "article-x-face"
-                                           (point-min) (point-max)))
-                    (process-send-eof "article-x-face")))
-                 ((functionp gnus-article-x-face-command)
-                  ;; The command is a lisp function, so we call it.
-                  (funcall gnus-article-x-face-command face))
-                 (t
-                  (error "%s is not a function"
-                         gnus-article-x-face-command)))))))))
+         (when (and x-faces
+                    gnus-article-x-face-command
+                    (or force
+                        ;; Check whether this face is censored.
+                        (not gnus-article-x-face-too-ugly)
+                        (and from
+                             (not (string-match gnus-article-x-face-too-ugly
+                                                from)))))
+           (while (setq face (pop x-faces))
+             ;; We display the face.
+             (cond ((stringp gnus-article-x-face-command)
+                    ;; The command is a string, so we interpret the command
+                    ;; as a, well, command, and fork it off.
+                    (let ((process-connection-type nil))
+                      (gnus-set-process-query-on-exit-flag
+                       (start-process
+                        "article-x-face" nil shell-file-name
+                        shell-command-switch gnus-article-x-face-command)
+                       nil)
+                      (with-temp-buffer
+                        (insert face)
+                        (process-send-region "article-x-face"
+                                             (point-min) (point-max)))
+                      (process-send-eof "article-x-face")))
+                   ((functionp gnus-article-x-face-command)
+                    ;; The command is a lisp function, so we call it.
+                    (funcall gnus-article-x-face-command face))
+                   (t
+                    (error "%s is not a function"
+                           gnus-article-x-face-command))))))))))
 
 (defun article-decode-mime-words ()
   "Decode all MIME-encoded words in the article."
@@ -2166,7 +2269,7 @@ unfolded."
 If PROMPT (the prefix), prompt for a coding system to use."
   (interactive "P")
   (let ((inhibit-point-motion-hooks t) (case-fold-search t)
-       buffer-read-only
+       (inhibit-read-only t)
        (mail-parse-charset gnus-newsgroup-charset)
        (mail-parse-ignored-charsets
         (save-excursion (condition-case nil
@@ -2216,7 +2319,7 @@ If PROMPT (the prefix), prompt for a coding system to use."
                             (set-buffer gnus-summary-buffer)
                           (error))
                         gnus-newsgroup-ignored-charsets))
-       buffer-read-only)
+       (inhibit-read-only t))
     (save-restriction
       (article-narrow-to-head)
       (funcall gnus-decode-header-function (point-min) (point-max)))))
@@ -2224,7 +2327,7 @@ If PROMPT (the prefix), prompt for a coding system to use."
 (defun article-decode-group-name ()
   "Decode group names in `Newsgroups:'."
   (let ((inhibit-point-motion-hooks t)
-       buffer-read-only
+       (inhibit-read-only t)
        (method (gnus-find-method-for-group gnus-newsgroup-name)))
     (when (and (or gnus-group-name-charset-method-alist
                   gnus-group-name-charset-group-alist)
@@ -2268,7 +2371,7 @@ If PROMPT (the prefix), prompt for a coding system to use."
   (when gnus-use-idna
     (save-restriction
       (let ((inhibit-point-motion-hooks t)
-           buffer-read-only)
+           (inhibit-read-only t))
        (article-narrow-to-head)
        (goto-char (point-min))
        (while (re-search-forward "@.*\\(xn--[-A-Za-z0-9.]*\\)[ \t\n\r,>]" nil t)
@@ -2289,7 +2392,7 @@ or not.
 If READ-CHARSET, ask for a coding system."
   (interactive (list 'force current-prefix-arg))
   (save-excursion
-    (let ((buffer-read-only nil) type charset)
+    (let ((inhibit-read-only t) type charset)
       (if (gnus-buffer-live-p gnus-original-article-buffer)
          (with-current-buffer gnus-original-article-buffer
            (setq type
@@ -2319,7 +2422,7 @@ If FORCE, decode the article whether it is marked as base64 not.
 If READ-CHARSET, ask for a coding system."
   (interactive (list 'force current-prefix-arg))
   (save-excursion
-    (let ((buffer-read-only nil) type charset)
+    (let ((inhibit-read-only t) type charset)
       (if (gnus-buffer-live-p gnus-original-article-buffer)
          (with-current-buffer gnus-original-article-buffer
            (setq type
@@ -2354,17 +2457,17 @@ If READ-CHARSET, ask for a coding system."
   (interactive)
   (require 'rfc1843)
   (save-excursion
-    (let ((buffer-read-only nil))
+    (let ((inhibit-read-only t))
       (rfc1843-decode-region (point-min) (point-max)))))
 
 (defun article-unsplit-urls ()
   "Remove the newlines that some other mailers insert into URLs."
   (interactive)
   (save-excursion
-    (let ((buffer-read-only nil))
+    (let ((inhibit-read-only t))
       (goto-char (point-min))
       (while (re-search-forward
-             "^\\(\\(https?\\|ftp\\)://\\S-+\\) *\n\\(\\S-+\\)" nil t)
+             "\\(\\(https?\\|ftp\\)://\\S-+\\) *\n\\(\\S-+\\)" nil t)
        (replace-match "\\1\\3" t)))
     (when (interactive-p)
       (gnus-treat-article nil))))
@@ -2375,7 +2478,7 @@ If READ-CHARSET, ask for a coding system."
 If READ-CHARSET, ask for a coding system."
   (interactive "P")
   (save-excursion
-    (let ((buffer-read-only nil)
+    (let ((inhibit-read-only t)
          charset)
       (when (gnus-buffer-live-p gnus-original-article-buffer)
        (with-current-buffer gnus-original-article-buffer
@@ -2441,7 +2544,7 @@ The `gnus-list-identifiers' variable specifies what to do."
        (regexp (if (consp gnus-list-identifiers)
                    (mapconcat 'identity gnus-list-identifiers " *\\|")
                  gnus-list-identifiers))
-       buffer-read-only)
+       (inhibit-read-only t))
     (when regexp
       (save-excursion
        (save-restriction
@@ -2463,7 +2566,7 @@ always hide."
   (interactive (gnus-article-hidden-arg))
   (unless (gnus-article-check-hidden-text 'pem arg)
     (save-excursion
-      (let (buffer-read-only end)
+      (let ((inhibit-read-only t) end)
        (goto-char (point-min))
        ;; Hide the horrendously ugly "header".
        (when (and (search-forward
@@ -2520,7 +2623,7 @@ always hide."
     (save-restriction
       (let ((inhibit-point-motion-hooks t)
            (gnus-signature-limit nil)
-           buffer-read-only)
+           (inhibit-read-only t))
        (article-goto-body)
        (cond
         ((eq banner 'signature)
@@ -2559,7 +2662,7 @@ always hide."
   (unless (gnus-article-check-hidden-text 'signature arg)
     (save-excursion
       (save-restriction
-       (let ((buffer-read-only nil))
+       (let ((inhibit-read-only t))
          (when (gnus-article-narrow-to-signature)
            (gnus-article-hide-text-type
             (point-min) (point-max) 'signature))))))
@@ -2579,7 +2682,7 @@ always hide."
   (interactive)
   (save-excursion
     (let ((inhibit-point-motion-hooks t)
-         buffer-read-only)
+         (inhibit-read-only t))
       (when (article-goto-body)
        (while (and (not (eobp))
                    (looking-at "[ \t]*$"))
@@ -2614,7 +2717,7 @@ Point is left at the beginning of the narrowed-to region."
   (interactive)
   (save-excursion
     (let ((inhibit-point-motion-hooks t)
-         buffer-read-only)
+         (inhibit-read-only t))
       ;; First make all blank lines empty.
       (article-goto-body)
       (while (re-search-forward "^[ \t]+$" nil t)
@@ -2633,7 +2736,7 @@ Point is left at the beginning of the narrowed-to region."
   (interactive)
   (save-excursion
     (let ((inhibit-point-motion-hooks t)
-         buffer-read-only)
+         (inhibit-read-only t))
       (article-goto-body)
       (while (re-search-forward "^[ \t]+" nil t)
        (replace-match "" t t)))))
@@ -2643,7 +2746,7 @@ Point is left at the beginning of the narrowed-to region."
   (interactive)
   (save-excursion
     (let ((inhibit-point-motion-hooks t)
-         buffer-read-only)
+         (inhibit-read-only t))
       (article-goto-body)
       (while (re-search-forward "[ \t]+$" nil t)
        (replace-match "" t t)))))
@@ -2660,7 +2763,7 @@ Point is left at the beginning of the narrowed-to region."
   (interactive)
   (save-excursion
     (let ((inhibit-point-motion-hooks t)
-         buffer-read-only)
+         (inhibit-read-only t))
       (article-goto-body)
       (while (re-search-forward "^[ \t]*\n" nil t)
        (replace-match "" t t)))))
@@ -2749,7 +2852,7 @@ means show, 0 means toggle."
 (defun gnus-article-show-hidden-text (type &optional dummy)
   "Show all hidden text of type TYPE.
 Originally it is hide instead of DUMMY."
-  (let ((buffer-read-only nil)
+  (let ((inhibit-read-only t)
        (inhibit-point-motion-hooks t))
     (gnus-remove-text-properties-when
      'article-type type
@@ -2778,69 +2881,74 @@ lines forward."
          (forward-line 1)
        (setq ended t)))))
 
-(defun article-date-ut (&optional type highlight header)
+(defun article-date-ut (&optional type highlight)
   "Convert DATE date to universal time in the current article.
 If TYPE is `local', convert to local time; if it is `lapsed', output
 how much time has lapsed since DATE.  For `lapsed', the value of
 `gnus-article-date-lapsed-new-header' says whether the \"X-Sent:\" header
 should replace the \"Date:\" one, or should be added below it."
   (interactive (list 'ut t))
-  (let* ((header (or header
-                    (message-fetch-field "date")
-                    ""))
-        (tdate-regexp "^Date:[ \t]\\|^X-Sent:[ \t]")
-        (date-regexp
-         (cond
-          ((not gnus-article-date-lapsed-new-header)
-           tdate-regexp)
-          ((eq type 'lapsed)
-           "^X-Sent:[ \t]")
-          (t
-           "^Date:[ \t]")))
-        (date (if (vectorp header) (mail-header-date header)
-                header))
+  (let* ((tdate-regexp "^Date:[ \t]\\|^X-Sent:[ \t]")
+        (date-regexp (cond ((not gnus-article-date-lapsed-new-header)
+                            tdate-regexp)
+                           ((eq type 'lapsed)
+                            "^X-Sent:[ \t]")
+                           (article-lapsed-timer
+                            "^Date:[ \t]")
+                           (t
+                            tdate-regexp)))
+        (case-fold-search t)
+        (inhibit-read-only t)
         (inhibit-point-motion-hooks t)
-        pos
-        bface eface)
+        pos date bface eface)
     (save-excursion
       (save-restriction
-       (article-narrow-to-head)
-       (when (re-search-forward tdate-regexp nil t)
-         (setq bface (get-text-property (point-at-bol) 'face)
-               date (or (get-text-property (point-at-bol)
-                                           'original-date)
-                        date)
-               eface (get-text-property (1- (point-at-eol)) 'face))
-         (forward-line 1))
-       (when (and date (not (string= date "")))
+       (widen)
+       (goto-char (point-min))
+       (while (or (setq date (get-text-property (setq pos (point))
+                                                'original-date))
+                  (when (setq pos (next-single-property-change
+                                   (point) 'original-date))
+                    (setq date (get-text-property pos 'original-date))
+                    t))
+         (narrow-to-region pos (or (text-property-any pos (point-max)
+                                                      'original-date nil)
+                                   (point-max)))
          (goto-char (point-min))
-         (let ((buffer-read-only nil))
-           ;; Delete any old Date headers.
-           (while (re-search-forward date-regexp nil t)
-             (if pos
-                 (delete-region (point-at-bol)
-                                (progn (gnus-article-forward-header)
-                                       (point)))
-               (delete-region (point-at-bol)
-                              (progn (gnus-article-forward-header)
-                                     (forward-char -1)
-                                     (point)))
-               (setq pos (point))))
-           (when (and (not pos)
-                      (re-search-forward tdate-regexp nil t))
-             (forward-line 1))
-           (gnus-goto-char pos)
-           (insert (article-make-date-line date (or type 'ut)))
-           (unless pos
-             (insert "\n")
-             (forward-line -1))
-           ;; Do highlighting.
-           (beginning-of-line)
-           (when (looking-at "\\([^:]+\\): *\\(.*\\)$")
-             (add-text-properties (match-beginning 1) (1+ (match-end 1))
-                                  (list 'original-date date 'face bface))
-             (put-text-property (match-beginning 2) (match-end 2)
-                                'face eface))))))))
+         (when (re-search-forward tdate-regexp nil t)
+           (setq bface (get-text-property (point-at-bol) 'face)
+                 eface (get-text-property (1- (point-at-eol)) 'face)))
+         (goto-char (point-min))
+         (setq pos nil)
+         ;; Delete any old Date headers.
+         (while (re-search-forward date-regexp nil t)
+           (if pos
+               (delete-region (point-at-bol) (progn
+                                               (gnus-article-forward-header)
+                                               (point)))
+             (delete-region (point-at-bol) (progn
+                                             (gnus-article-forward-header)
+                                             (forward-char -1)
+                                             (point)))
+             (setq pos (point))))
+         (when (and (not pos)
+                    (re-search-forward tdate-regexp nil t))
+           (forward-line 1))
+         (gnus-goto-char pos)
+         (insert (article-make-date-line date (or type 'ut)))
+         (unless pos
+           (insert "\n")
+           (forward-line -1))
+         ;; Do highlighting.
+         (beginning-of-line)
+         (when (looking-at "\\([^:]+\\): *\\(.*\\)$")
+           (put-text-property (match-beginning 1) (1+ (match-end 1))
+                              'face bface)
+           (put-text-property (match-beginning 2) (match-end 2)
+                              'face eface))
+         (put-text-property (point-min) (1- (point-max)) 'original-date date)
+         (goto-char (point-max))
+         (widen))))))
 
 (defun article-make-date-line (date type)
   "Return a DATE line of TYPE."
@@ -2981,20 +3089,21 @@ function and want to see what the date was before converting."
 
 (defun article-update-date-lapsed ()
   "Function to be run from a timer to update the lapsed time line."
-  (let (deactivate-mark)
-    (save-excursion
-      (ignore-errors
-       (walk-windows
-        (lambda (w)
-          (set-buffer (window-buffer w))
-          (when (eq major-mode 'gnus-article-mode)
-            (let ((mark (point-marker)))
-              (goto-char (point-min))
-              (when (re-search-forward "^X-Sent:" nil t)
-                (article-date-lapsed t))
-              (goto-char (marker-position mark))
-              (move-marker mark nil))))
-        nil 'visible)))))
+  (save-match-data
+    (let (deactivate-mark)
+      (save-excursion
+       (ignore-errors
+        (walk-windows
+         (lambda (w)
+           (set-buffer (window-buffer w))
+           (when (eq major-mode 'gnus-article-mode)
+             (let ((mark (point-marker)))
+               (goto-char (point-min))
+               (when (re-search-forward "^X-Sent:" nil t)
+                 (article-date-lapsed t))
+               (goto-char (marker-position mark))
+               (move-marker mark nil))))
+         nil 'visible))))))
 
 (defun gnus-start-date-timer (&optional n)
   "Start a timer to update the X-Sent header in the article buffers.
@@ -3025,11 +3134,32 @@ This format is defined by the `gnus-article-time-format' variable."
   (interactive (list t))
   (article-date-ut 'iso8601 highlight))
 
+(defmacro gnus-article-save-original-date (&rest forms)
+  "Save the original date as a text property and evaluate FORMS."
+  `(let* ((case-fold-search t)
+         (start (progn
+                  (goto-char (point-min))
+                  (when (and (re-search-forward "^date:[\t\n ]+" nil t)
+                             (not (bolp)))
+                    (match-end 0))))
+         (date (when (and start
+                          (re-search-forward "[\t ]*\n\\(?:[^\t ]\\|\\'\\)"
+                                             nil t))
+                 (buffer-substring-no-properties start
+                                                 (match-beginning 0)))))
+     (goto-char (point-max))
+     (skip-chars-backward "\n")
+     (put-text-property (point-min) (point) 'original-date date)
+     ,@forms
+     (goto-char (point-max))
+     (skip-chars-backward "\n")
+     (put-text-property (point-min) (point) 'original-date date)))
+
 ;; (defun article-show-all ()
 ;;   "Show all hidden text in the article buffer."
 ;;   (interactive)
 ;;   (save-excursion
-;;     (let ((buffer-read-only nil))
+;;     (let ((inhibit-read-only t))
 ;;       (gnus-article-unhide-text (point-min) (point-max)))))
 
 (defun article-remove-leading-whitespace ()
@@ -3037,7 +3167,7 @@ This format is defined by the `gnus-article-time-format' variable."
   (interactive)
   (save-excursion
     (save-restriction
-      (let ((buffer-read-only nil))
+      (let ((inhibit-read-only t))
        (article-narrow-to-head)
        (goto-char (point-min))
        (while (re-search-forward "^[^ :]+: \\([ \t]+\\)" nil t)
@@ -3054,7 +3184,7 @@ This format is defined by the `gnus-article-time-format' variable."
                          gnus-article-emphasis-alist)
                      (error))
                    gnus-emphasis-alist))
-           (buffer-read-only nil)
+           (inhibit-read-only t)
            (props (append '(article-type emphasis)
                           gnus-hidden-properties))
            regexp elem beg invisible visible face)
@@ -3446,7 +3576,7 @@ If variable `gnus-use-long-file-name' is non-nil, it is
                        (mm-handle-multipart-ctl-parameter
                         mm-security-handle 'gnus-info)))))
          (when info
-           (let (buffer-read-only bface eface)
+           (let ((inhibit-read-only t) bface eface)
              (save-restriction
                (message-narrow-to-head)
                (goto-char (point-max))
@@ -3635,6 +3765,7 @@ commands:
 \\[gnus-article-describe-briefly]\t Describe the current mode briefly
 \\[gnus-info-find-node]\t Go to the Gnus info node"
   (interactive)
+  (kill-all-local-variables)
   (gnus-simplify-mode-line)
   (setq mode-name "Article")
   (setq major-mode 'gnus-article-mode)
@@ -3656,13 +3787,15 @@ 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 "\ ".
+  (set (make-local-variable 'nobreak-char-display) nil)
   (gnus-set-default-directory)
   (buffer-disable-undo)
   (setq buffer-read-only t
        show-trailing-whitespace nil)
   (set-syntax-table gnus-article-mode-syntax-table)
   (mm-enable-multibyte)
-  (gnus-run-hooks 'gnus-article-mode-hook))
+  (gnus-run-mode-hooks 'gnus-article-mode-hook))
 
 (defun gnus-article-setup-buffer ()
   "Initialize the article buffer."
@@ -3689,14 +3822,19 @@ commands:
       (mm-enable-multibyte)
       (setq major-mode 'gnus-original-article-mode)
       (make-local-variable 'gnus-original-article))
-    (if (get-buffer name)
+    (if (and (get-buffer name)
+            (with-current-buffer name
+              (if gnus-article-edit-mode
+                  (if (y-or-n-p "Article mode edit in progress; discard? ")
+                      (progn
+                        (set-buffer-modified-p nil)
+                        (gnus-kill-buffer name)
+                        (message "")
+                        nil)
+                    (error "Action aborted"))
+                t)))
        (save-excursion
          (set-buffer name)
-         (when (and gnus-article-edit-mode
-                    (buffer-modified-p)
-                    (not
-                     (y-or-n-p "Article mode edit in progress; discard? ")))
-           (error "Action aborted"))
          (set (make-local-variable 'gnus-article-edit-mode) nil)
          (when gnus-article-mime-handles
            (mm-destroy-parts gnus-article-mime-handles)
@@ -3754,7 +3892,7 @@ If ALL-HEADERS is non-nil, no headers are hidden."
        (when (and (boundp 'transient-mark-mode)
                   transient-mark-mode)
          (setq mark-active nil))
-       (if (not (setq result (let ((buffer-read-only nil))
+       (if (not (setq result (let ((inhibit-read-only t))
                                (gnus-request-article-this-buffer
                                 article group))))
            ;; There is no such article.
@@ -3849,7 +3987,8 @@ If ALL-HEADERS is non-nil, no headers are hidden."
   ;; Hooks for getting information from the article.
   ;; This hook must be called before being narrowed.
   (let ((gnus-article-buffer (current-buffer))
-       buffer-read-only)
+       buffer-read-only
+       (inhibit-read-only t))
     (unless (eq major-mode 'gnus-article-mode)
       (gnus-article-mode))
     (setq buffer-read-only nil
@@ -3949,7 +4088,7 @@ General format specifiers can also be used.  See Info node
        (mm-remove-parts handles)
        (goto-char (point-min))
        (or (search-forward "\n\n") (goto-char (point-max)))
-       (let (buffer-read-only)
+       (let ((inhibit-read-only t))
          (delete-region (point) (point-max))
          (mm-display-parts handles))))))
 
@@ -3960,6 +4099,7 @@ This function is exclusively used by `gnus-mime-save-part-and-strip'
 and `gnus-mime-delete-part', and not provided at run-time normally."
     (gnus-article-edit-article
      `(lambda ()
+       (buffer-disable-undo)
        (erase-buffer)
        (let ((mail-parse-charset (or gnus-article-charset
                                      ',gnus-newsgroup-charset))
@@ -4136,60 +4276,63 @@ Deleting parts may malfunction or destroy the article; continue? ")
            (mm-merge-handles gnus-article-mime-handles handle))
       (gnus-mm-display-part handle))))
 
-(eval-when-compile
-  (require 'jka-compr))
-
-;; jka-compr.el uses a "sh -c" to direct stderr to err-file, but these days
-;; emacs can do that itself.
-;;
-(defun gnus-mime-jka-compr-maybe-uncompress ()
-  "Uncompress the current buffer if `auto-compression-mode' is enabled.
-The uncompress method used is derived from `buffer-file-name'."
-  (when (and (fboundp 'jka-compr-installed-p)
-             (jka-compr-installed-p))
-    (let ((info (jka-compr-get-compression-info buffer-file-name)))
-      (when info
-        (let ((basename (file-name-nondirectory buffer-file-name))
-              (args     (jka-compr-info-uncompress-args    info))
-              (prog     (jka-compr-info-uncompress-program info))
-              (message  (jka-compr-info-uncompress-message info))
-              (err-file (jka-compr-make-temp-name)))
-          (if message
-              (message "%s %s..." message basename))
-          (unwind-protect
-              (unless (memq (apply 'call-process-region
-                                   (point-min) (point-max)
-                                   prog
-                                   t (list t err-file) nil
-                                   args)
-                            jka-compr-acceptable-retval-list)
-                (jka-compr-error prog args basename message err-file))
-            (jka-compr-delete-temp-file err-file)))))))
-
-(defun gnus-mime-copy-part (&optional handle)
+(defun gnus-mime-copy-part (&optional handle arg)
   "Put the MIME part under point into a new buffer.
 If `auto-compression-mode' is enabled, compressed files like .gz and .bz2
 are decompressed."
-  (interactive)
+  (interactive (list nil current-prefix-arg))
   (gnus-article-check-buffer)
-  (let* ((handle (or handle (get-text-property (point) 'gnus-data)))
-        (contents (and handle (mm-get-part handle)))
-        (base (and handle
-                   (file-name-nondirectory
-                    (or
-                     (mail-content-type-get (mm-handle-type handle) 'name)
-                     (mail-content-type-get (mm-handle-disposition handle)
-                                            'filename)
-                     "*decoded*"))))
-        (buffer (and base (generate-new-buffer base))))
-    (when contents
-      (switch-to-buffer buffer)
-      (insert contents)
+  (unless handle
+    (setq handle (get-text-property (point) 'gnus-data)))
+  (when handle
+    (let ((filename (or (mail-content-type-get (mm-handle-disposition handle)
+                                              'name)
+                       (mail-content-type-get (mm-handle-disposition handle)
+                                              'filename)))
+         contents dont-decode charset coding-system)
+      (mm-with-unibyte-buffer
+       (mm-insert-part handle)
+       (setq contents (or (condition-case nil
+                              (mm-decompress-buffer filename nil 'sig)
+                            (error
+                             (setq dont-decode t)
+                             nil))
+                          (buffer-string))))
+      (setq filename (cond (filename (file-name-nondirectory filename))
+                          (dont-decode "*raw data*")
+                          (t "*decoded*")))
+      (cond
+       (dont-decode)
+       ((not arg)
+       (unless (setq charset (mail-content-type-get
+                              (mm-handle-type handle) 'charset))
+         (unless (setq coding-system (mm-with-unibyte-buffer
+                                       (insert contents)
+                                       (mm-find-buffer-file-coding-system)))
+           (setq charset gnus-newsgroup-charset))))
+       ((numberp arg)
+       (setq charset (or (cdr (assq arg
+                                    gnus-summary-show-article-charset-alist))
+                         (mm-read-coding-system "Charset: ")))))
+      (switch-to-buffer (generate-new-buffer filename))
+      (if (or coding-system
+             (and charset
+                  (setq coding-system (mm-charset-to-coding-system charset))
+                  (not (eq charset 'ascii))))
+         (progn
+           (mm-enable-multibyte)
+           (insert (mm-decode-coding-string contents coding-system))
+           (setq buffer-file-coding-system
+                 (if (boundp 'last-coding-system-used)
+                     (symbol-value 'last-coding-system-used)
+                   coding-system)))
+       (mm-disable-multibyte)
+       (insert contents)
+       (setq buffer-file-coding-system mm-binary-coding-system))
       ;; We do it this way to make `normal-mode' set the appropriate mode.
       (unwind-protect
          (progn
-           (setq buffer-file-name (expand-file-name base))
-           (gnus-mime-jka-compr-maybe-uncompress)
+           (setq buffer-file-name (expand-file-name filename))
            (normal-mode))
        (setq buffer-file-name nil))
       (goto-char (point-min)))))
@@ -4220,37 +4363,57 @@ are decompressed."
          (ps-despool filename)))))
 
 (defun gnus-mime-inline-part (&optional handle arg)
-  "Insert the MIME part under point into the current buffer."
+  "Insert the MIME part under point into the current buffer.
+Compressed files like .gz and .bz2 are decompressed."
   (interactive (list nil current-prefix-arg))
   (gnus-article-check-buffer)
-  (let* ((handle (or handle (get-text-property (point) 'gnus-data)))
-        contents charset
-        (b (point))
-        buffer-read-only)
-    (when handle
+  (unless handle
+    (setq handle (get-text-property (point) 'gnus-data)))
+  (when handle
+    (let ((b (point))
+         (inhibit-read-only t)
+         contents charset coding-system)
       (if (and (not arg) (mm-handle-undisplayer handle))
          (mm-remove-part handle)
-       (setq contents (mm-get-part handle))
+       (mm-with-unibyte-buffer
+         (mm-insert-part handle)
+         (setq contents
+               (or (mm-decompress-buffer
+                    (or (mail-content-type-get (mm-handle-disposition handle)
+                                               'name)
+                        (mail-content-type-get (mm-handle-disposition handle)
+                                               'filename))
+                    nil t)
+                   (buffer-string))))
        (cond
         ((not arg)
-         (setq charset (or (mail-content-type-get
-                            (mm-handle-type handle) 'charset)
-                           gnus-newsgroup-charset)))
+         (unless (setq charset (mail-content-type-get
+                                (mm-handle-type handle) 'charset))
+           (unless (setq coding-system
+                         (mm-with-unibyte-buffer
+                           (insert contents)
+                           (mm-find-buffer-file-coding-system)))
+             (setq charset gnus-newsgroup-charset))))
         ((numberp arg)
          (if (mm-handle-undisplayer handle)
              (mm-remove-part handle))
          (setq charset
                (or (cdr (assq arg
                               gnus-summary-show-article-charset-alist))
-                   (mm-read-coding-system "Charset: ")))))
+                   (mm-read-coding-system "Charset: "))))
+        (t
+         (if (mm-handle-undisplayer handle)
+             (mm-remove-part handle))))
        (forward-line 2)
-       (mm-insert-inline handle
-                         (if (and charset
-                                  (setq charset (mm-charset-to-coding-system
-                                                 charset))
-                                  (not (eq charset 'ascii)))
-                             (mm-decode-coding-string contents charset)
-                           contents))
+       (mm-insert-inline
+        handle
+        (if (or coding-system
+                (and charset
+                     (setq coding-system
+                           (mm-charset-to-coding-system charset))
+                     (not (eq coding-system 'ascii))))
+            (mm-decode-coding-string contents coding-system)
+          (mm-string-to-multibyte contents)))
        (goto-char b)))))
 
 (defun gnus-mime-view-part-as-charset (&optional handle arg)
@@ -4261,7 +4424,7 @@ specified charset."
   (let* ((handle (or handle (get-text-property (point) 'gnus-data)))
         contents charset
         (b (point))
-        buffer-read-only)
+        (inhibit-read-only t))
     (when handle
       (if (mm-handle-undisplayer handle)
          (mm-remove-part handle))
@@ -4300,7 +4463,7 @@ If no internal viewer is available, use an external viewer."
         (mail-parse-ignored-charsets
          (with-current-buffer gnus-summary-buffer
            gnus-newsgroup-ignored-charsets))
-        buffer-read-only)
+        (inhibit-read-only t))
     (when handle
       (if (mm-handle-undisplayer handle)
          (mm-remove-part handle)
@@ -4361,8 +4524,8 @@ N is the numerical prefix."
 
 (defun gnus-article-mime-match-handle-first (condition)
   (if condition
-      (let ((alist gnus-article-mime-handle-alist) ihandle n)
-       (while (setq ihandle (pop alist))
+      (let (n)
+       (dolist (ihandle gnus-article-mime-handle-alist)
          (if (and (cond
                    ((functionp condition)
                     (funcall condition (cdr ihandle)))
@@ -4403,7 +4566,7 @@ N is the numerical prefix."
   "Display HANDLE and fix MIME button."
   (let ((id (get-text-property (point) 'gnus-part))
        (point (point))
-       buffer-read-only)
+       (inhibit-read-only t))
     (forward-line 1)
     (prog1
        (let ((window (selected-window))
@@ -4529,11 +4692,15 @@ N is the numerical prefix."
          ;; We have to do this since selecting the window
          ;; may change the point.  So we set the window point.
          (set-window-point window point)))
-      (let* ((handles (or ihandles
-                         (mm-dissect-buffer nil gnus-article-loose-mime)
-                         (and gnus-article-emulate-mime
-                              (mm-uu-dissect))))
-            buffer-read-only handle name type b e display)
+      (let ((handles ihandles)
+           (inhibit-read-only t)
+           handle)
+       (cond (handles)
+             ((setq handles (mm-dissect-buffer nil gnus-article-loose-mime))
+              (when gnus-article-emulate-mime
+                (mm-uu-dissect-text-parts handles)))
+             (gnus-article-emulate-mime
+              (setq handles (mm-uu-dissect))))
        (when (and (not ihandles)
                   (not gnus-displaying-mime))
          ;; Top-level call; we clean up.
@@ -4566,7 +4733,8 @@ N is the numerical prefix."
            (save-restriction
              (article-goto-body)
              (narrow-to-region (point-min) (point))
-             (gnus-treat-article 'head))))))))
+             (gnus-article-save-original-date
+              (gnus-treat-article 'head)))))))))
 
 (defcustom gnus-mime-display-multipart-as-mixed nil
   "Display \"multipart\" parts as  \"multipart/mixed\".
@@ -4579,6 +4747,7 @@ If t, it overrides nil values of
 
 (defcustom gnus-mime-display-multipart-alternative-as-mixed nil
   "Display \"multipart/alternative\" parts as  \"multipart/mixed\"."
+  :version "22.1"
   :group 'gnus-article-mime
   :type 'boolean)
 
@@ -4588,6 +4757,7 @@ If t, it overrides nil values of
 If displaying \"text/html\" is discouraged \(see
 `mm-discouraged-alternatives'\) images or other material inside a
 \"multipart/related\" part might be overlooked when this variable is nil."
+  :version "22.1"
   :group 'gnus-article-mime
   :type 'boolean)
 
@@ -4692,7 +4862,7 @@ If displaying \"text/html\" is discouraged \(see
              (forward-line -1)
              (setq beg (point)))
            (gnus-article-insert-newline)
-           (mm-insert-inline handle (mm-get-part handle))
+           (mm-display-inline handle)
            (goto-char (point-max))))
          ;; Do highlighting.
          (save-excursion
@@ -4726,7 +4896,7 @@ If displaying \"text/html\" is discouraged \(see
   (let* ((preferred (or preferred (mm-preferred-alternative handles)))
         (ihandles handles)
         (point (point))
-        handle buffer-read-only from props begend not-pref)
+        handle (inhibit-read-only t) from props begend not-pref)
     (save-window-excursion
       (save-restriction
        (when ibegend
@@ -4928,7 +5098,7 @@ If given a numerical ARG, move forward ARG pages."
     (widen)
     ;; Remove any old next/prev buttons.
     (when (gnus-visual-p 'page-marker)
-      (let ((buffer-read-only nil))
+      (let ((inhibit-read-only t))
        (gnus-remove-text-with-property 'gnus-prev)
        (gnus-remove-text-with-property 'gnus-next)))
     (if
@@ -4948,12 +5118,12 @@ If given a numerical ARG, move forward ARG pages."
           (match-beginning 0)
         (point)))
       (when (and (gnus-visual-p 'page-marker)
-                (not (= (point-min) 1)))
+                (> (point-min) (save-restriction (widen) (point-min))))
        (save-excursion
          (goto-char (point-min))
          (gnus-insert-prev-page-button)))
       (when (and (gnus-visual-p 'page-marker)
-                (< (+ (point-max) 2) (buffer-size)))
+                (< (point-max) (save-restriction (widen) (point-max))))
        (save-excursion
          (goto-char (point-max))
          (gnus-insert-next-page-button))))))
@@ -5179,7 +5349,7 @@ not have a face in `gnus-article-boring-faces'."
              (when (eq win (selected-window))
                (setq new-sum-point (point)
                      new-sum-start (window-start win)
-                     new-sum-hscroll (window-hscroll win))
+                     new-sum-hscroll (window-hscroll win)))
              (when (eq in-buffer (current-buffer))
                (setq selected (gnus-summary-select-article))
                (set-buffer obuf)
@@ -5195,7 +5365,7 @@ not have a face in `gnus-article-boring-faces'."
                           new-sum-point)
                  (set-window-point win new-sum-point)
                  (set-window-start win new-sum-start)
-                 (set-window-hscroll win new-sum-hscroll)))))
+                 (set-window-hscroll win new-sum-hscroll))))
          (set-window-configuration owin)
          (ding))))))
 
@@ -5406,7 +5576,7 @@ If given a prefix, show the hidden text instead."
                  (backend (car (gnus-find-method-for-group
                                 gnus-newsgroup-name)))
                  result
-                 (buffer-read-only nil))
+                 (inhibit-read-only t))
              (if (or (not (listp methods))
                      (and (symbolp (car methods))
                           (assq (car methods) nnoo-definition-alist)))
@@ -5458,7 +5628,7 @@ If given a prefix, show the hidden text instead."
            (buffer-disable-undo)
            (setq major-mode 'gnus-original-article-mode)
            (setq buffer-read-only t))
-         (let (buffer-read-only)
+         (let ((inhibit-read-only t))
            (erase-buffer)
            (insert-buffer-substring gnus-article-buffer))
          (setq gnus-original-article (cons group article)))
@@ -5556,7 +5726,7 @@ If given a prefix, show the hidden text instead."
     ["Body" message-goto-body t]
     ["Signature" message-goto-signature t]))
 
-(define-derived-mode gnus-article-edit-mode text-mode "Article Edit"
+(define-derived-mode gnus-article-edit-mode message-mode "Article Edit"
   "Major mode for editing articles.
 This is an extended text-mode.
 
@@ -5595,7 +5765,10 @@ groups."
   "Start editing the contents of the current article buffer."
   (let ((winconf (current-window-configuration)))
     (set-buffer gnus-article-buffer)
-    (gnus-article-edit-mode)
+    (let ((message-auto-save-directory
+          ;; Don't associate the article buffer with a draft file.
+          nil))
+      (gnus-article-edit-mode))
     (funcall start-func)
     (set-buffer-modified-p nil)
     (gnus-configure-windows 'edit-article)
@@ -5686,6 +5859,7 @@ groups."
 (defcustom gnus-button-valid-fqdn-regexp
   message-valid-fqdn-regexp
   "Regular expression that matches a valid FQDN."
+  :version "22.1"
   :group 'gnus-article-buttons
   :type 'regexp)
 
@@ -5693,6 +5867,7 @@ groups."
   "Function to use for displaying man pages.
 The function must take at least one argument with a string naming the
 man page."
+  :version "22.1"
   :type '(choice (function-item :tag "Man" manual-entry)
                 (function-item :tag "Woman" woman)
                 (function :tag "Other"))
@@ -5703,6 +5878,7 @@ man page."
 If the default site is too slow, try to find a CTAN mirror, see
 <URL:http://tug.ctan.org/tex-archive/CTAN.sites?action=/index.html>.  See also
 the variable `gnus-button-handle-ctan'."
+  :version "22.1"
   :group 'gnus-article-buttons
   :link '(custom-manual "(gnus)Group Parameters")
   :type '(choice (const "http://www.tex.ac.uk/tex-archive/")
@@ -5713,12 +5889,14 @@ the variable `gnus-button-handle-ctan'."
 (defcustom gnus-button-ctan-handler 'browse-url
   "Function to use for displaying CTAN links.
 The function must take one argument, the string naming the URL."
+  :version "22.1"
   :type '(choice (function-item :tag "Browse Url" browse-url)
                 (function :tag "Other"))
   :group 'gnus-article-buttons)
 
 (defcustom gnus-button-handle-ctan-bogus-regexp "^/?tex-archive/\\|^/"
   "Bogus strings removed from CTAN URLs."
+  :version "22.1"
   :group 'gnus-article-buttons
   :type '(choice (const "^/?tex-archive/\\|/")
                 (regexp :tag "Other")))
@@ -5732,6 +5910,7 @@ The function must take one argument, the string naming the URL."
    "\\)")
   "Regular expression for ctan directories.
 It should match all directories in the top level of `gnus-ctan-url'."
+  :version "22.1"
   :group 'gnus-article-buttons
   :type 'regexp)
 
@@ -5741,6 +5920,7 @@ It should match all directories in the top level of `gnus-ctan-url'."
          gnus-button-valid-fqdn-regexp
          ">?\\)\\b")
   "Regular expression that matches a message ID or a mail address."
+  :version "22.1"
   :group 'gnus-article-buttons
   :type 'regexp)
 
@@ -5752,6 +5932,7 @@ 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
 must return `mid', `mail', `invalid' or `ask'."
+  :version "22.1"
   :group 'gnus-article-buttons
   :type '(choice (function-item :tag "Heuristic function"
                                gnus-button-mid-or-mail-heuristic)
@@ -5815,6 +5996,7 @@ must return `mid', `mail', `invalid' or `ask'."
 
 A negative RATE indicates a message IDs, whereas a positive indicates a mail
 address.  The REGEXP is processed with `case-fold-search' set to nil."
+  :version "22.1"
   :group 'gnus-article-buttons
   :type '(repeat (cons (number :tag "Rate")
                       (regexp :tag "Regexp"))))
@@ -5999,6 +6181,7 @@ positives are possible.  Note that you can set this variable local to
 specific groups.  Setting it higher in TeX groups is probably a good idea.
 See Info node `(gnus)Group Parameters' and the variable `gnus-parameters' on
 how to set variables in specific groups."
+  :version "22.1"
   :group 'gnus-article-buttons
   :link '(custom-manual "(gnus)Group Parameters")
   :type 'integer)
@@ -6010,6 +6193,7 @@ positives are possible.  Note that you can set this variable local to
 specific groups.  Setting it higher in Unix groups is probably a good idea.
 See Info node `(gnus)Group Parameters' and the variable `gnus-parameters' on
 how to set variables in specific groups."
+  :version "22.1"
   :group 'gnus-article-buttons
   :link '(custom-manual "(gnus)Group Parameters")
   :type 'integer)
@@ -6021,6 +6205,7 @@ positives are possible.  Note that you can set this variable local to
 specific groups.  Setting it higher in Emacs or Gnus related groups is
 probably a good idea.  See Info node `(gnus)Group Parameters' and the variable
 `gnus-parameters' on how to set variables in specific groups."
+  :version "22.1"
   :group 'gnus-article-buttons
   :link '(custom-manual "(gnus)Group Parameters")
   :type 'integer)
@@ -6030,6 +6215,7 @@ probably a good idea.  See Info node `(gnus)Group Parameters' and the variable
 The higher the number, the more buttons will appear and the more false
 positives are possible."
   ;; mail addresses, MIDs, URLs for news, ...
+  :version "22.1"
   :group 'gnus-article-buttons
   :type 'integer)
 
@@ -6038,6 +6224,7 @@ positives are possible."
 The higher the number, the more buttons will appear and the more false
 positives are possible."
   ;; stuff handled by `browse-url' or `gnus-button-embedded-url'
+  :version "22.1"
   :group 'gnus-article-buttons
   :type 'integer)
 
@@ -6187,13 +6374,15 @@ variable it the real callback function."
     ("^\\(From\\|Reply-To\\):" ": *\\(.+\\)$"
      1 (>= gnus-button-message-level 0) gnus-button-reply 1)
     ("^\\(Cc\\|To\\):" "[^ \t\n<>,()\"]+@[^ \t\n<>,()\"]+"
-     0 (>= gnus-button-message-level 0) gnus-button-mailto 0)
+     0 (>= gnus-button-message-level 0) gnus-msg-mail 0)
     ("^X-[Uu][Rr][Ll]:" gnus-button-url-regexp
      0 (>= gnus-button-browse-level 0) browse-url 0)
     ("^Subject:" gnus-button-url-regexp
      0 (>= gnus-button-browse-level 0) browse-url 0)
     ("^[^:]+:" gnus-button-url-regexp
      0 (>= gnus-button-browse-level 0) browse-url 0)
+    ("^OpenPGP:.*url=" gnus-button-url-regexp
+     0 (>= gnus-button-browse-level 0) gnus-button-openpgp 0)
     ("^[^:]+:" "\\bmailto:\\([-a-z.@_+0-9%=?&/]+\\)"
      0 (>= gnus-button-message-level 0) gnus-url-mailto 1)
     ("^[^:]+:" "\\(<\\(url: \\)?\\(nntp\\|news\\):\\([^>\n ]*\\)>\\)"
@@ -6270,9 +6459,8 @@ do the highlighting.  See the documentation for those functions."
   "Highlight article headers as specified by `gnus-header-face-alist'."
   (interactive)
   (gnus-with-article-headers
-    (let ((alist gnus-header-face-alist)
-         entry regexp header-face field-face from hpoints fpoints)
-      (while (setq entry (pop alist))
+    (let (regexp header-face field-face from hpoints fpoints)
+      (dolist (entry gnus-header-face-alist)
        (goto-char (point-min))
        (setq regexp (concat "^\\("
                             (if (string-equal "" (nth 0 entry))
@@ -6302,7 +6490,7 @@ do the highlighting.  See the documentation for those functions."
 (defun gnus-article-highlight-signature ()
   "Highlight the signature in an article.
 It does this by highlighting everything after
-`gnus-signature-separator' using `gnus-signature-face'."
+`gnus-signature-separator' using the face `gnus-signature'."
   (interactive)
   (gnus-with-article-buffer
     (let ((inhibit-point-motion-hooks t))
@@ -6370,11 +6558,9 @@ specified by `gnus-button-alist'."
   "Add buttons to the head of the article."
   (interactive)
   (gnus-with-article-headers
-    (let ((alist gnus-header-button-alist)
-         entry beg end)
-      (while alist
+    (let (beg end)
+      (dolist (entry gnus-header-button-alist)
        ;; Each alist entry.
-       (setq entry (pop alist))
        (goto-char (point-min))
        (while (re-search-forward (car entry) nil t)
          ;; Each header matching the entry.
@@ -6472,15 +6658,18 @@ specified by `gnus-button-alist'."
                      (cons fun args)))))))
 
 (defun gnus-parse-news-url (url)
-  (let (scheme server group message-id articles)
+  (let (scheme server port group message-id articles)
     (with-temp-buffer
       (insert url)
       (goto-char (point-min))
       (when (looking-at "\\([A-Za-z]+\\):")
        (setq scheme (match-string 1))
        (goto-char (match-end 0)))
-      (when (looking-at "//\\([^/]+\\)/")
+      (when (looking-at "//\\([^:/]+\\)\\(:?\\)\\([0-9]+\\)?/")
        (setq server (match-string 1))
+       (setq port (if (stringp (match-string 3))
+                      (string-to-number (match-string 3))
+                    (match-string 3)))
        (goto-char (match-end 0)))
 
       (cond
@@ -6493,18 +6682,23 @@ specified by `gnus-button-alist'."
        (setq group (match-string 1)))
        (t
        (error "Unknown news URL syntax"))))
-    (list scheme server group message-id articles)))
+    (list scheme server port group message-id articles)))
 
 (defun gnus-button-handle-news (url)
   "Fetch a news URL."
-  (destructuring-bind (scheme server group message-id articles)
+  (destructuring-bind (scheme server port group message-id articles)
       (gnus-parse-news-url url)
     (cond
      (message-id
       (save-excursion
        (set-buffer gnus-summary-buffer)
        (if server
-           (let ((gnus-refer-article-method (list (list 'nntp server))))
+           (let ((gnus-refer-article-method
+                  (nconc (list (list 'nntp server))
+                         gnus-refer-article-method))
+                 (nntp-port-number (or port "nntp")))
+             (gnus-message 7 "Fetching %s with %s"
+                           message-id gnus-refer-article-method)
              (gnus-summary-refer-article message-id))
          (gnus-summary-refer-article message-id))))
      (group
@@ -6540,10 +6734,10 @@ specified by `gnus-button-alist'."
   (if (string-match "\\([^#]+\\)#?\\(.*\\)" url)
       (gnus-info-find-node
        (concat "("
-              (gnus-url-unhex-string 
+              (gnus-url-unhex-string
                 (match-string 1 url))
               ")"
-              (or (gnus-url-unhex-string 
+              (or (gnus-url-unhex-string
                    (match-string 2 url))
                   "Top")))
     (error "Can't parse %s" url)))
@@ -6559,6 +6753,13 @@ specified by `gnus-button-alist'."
   (Info-directory)
   (Info-menu url))
 
+(defun gnus-button-openpgp (url)
+  "Retrieve and add an OpenPGP key given URL from an OpenPGP header."
+  (with-temp-buffer
+    (mm-url-insert-file-contents-external url)
+    (pgg-snarf-keys-region (point-min) (point-max))
+    (pgg-display-output-buffer nil nil nil)))
+
 (defun gnus-button-message-id (message-id)
   "Fetch MESSAGE-ID."
   (with-current-buffer gnus-summary-buffer
@@ -6582,7 +6783,7 @@ specified by `gnus-button-alist'."
                                     (match-string 3 address)
                                   "nntp")))
        nil nil nil
-       (and (match-end 6) (list (string-to-int (match-string 6 address))))))))
+       (and (match-end 6) (list (string-to-number (match-string 6 address))))))))
 
 (defun gnus-url-parse-query-string (query &optional downcase)
   (let (retval pairs cur key val)
@@ -6647,13 +6848,16 @@ specified by `gnus-button-alist'."
 
 (defvar gnus-next-page-map
   (let ((map (make-sparse-keymap)))
+    (unless (>= emacs-major-version 21)
+      ;; XEmacs doesn't care.
+      (set-keymap-parent map gnus-article-mode-map))
     (define-key map gnus-mouse-2 'gnus-button-next-page)
     (define-key map "\r" 'gnus-button-next-page)
     map))
 
 (defun gnus-insert-prev-page-button ()
   (let ((b (point))
-       (buffer-read-only nil))
+       (inhibit-read-only t))
     (gnus-eval-format
      gnus-prev-page-line-format nil
      `(keymap ,gnus-prev-page-map
@@ -6686,7 +6890,7 @@ specified by `gnus-button-alist'."
 
 (defun gnus-insert-next-page-button ()
   (let ((b (point))
-       (buffer-read-only nil))
+       (inhibit-read-only t))
     (gnus-eval-format gnus-next-page-line-format nil
                      `(keymap ,gnus-next-page-map
                          gnus-next t
@@ -6721,7 +6925,7 @@ specified by `gnus-button-alist'."
   "List of methods used to decode headers.
 
 This variable is a list of FUNCTION or (REGEXP . FUNCTION).  If item
-is FUNCTION, FUNCTION will be apply to all newsgroups.  If item is a
+is FUNCTION, FUNCTION will be applied to all newsgroups.  If item is a
 \(REGEXP . FUNCTION), FUNCTION will be only apply to the newsgroups
 whose names match REGEXP.
 
@@ -6840,7 +7044,7 @@ For example:
     current-prefix-arg))
   (let ((func (cdr (assoc protocol gnus-article-encrypt-protocol-alist))))
     (unless func
-      (error (format "Can't find the encrypt protocol %s" protocol)))
+      (error "Can't find the encrypt protocol %s" protocol))
     (if (member gnus-newsgroup-name '("nndraft:delayed"
                                      "nndraft:drafts"
                                      "nndraft:queue"))
@@ -6860,7 +7064,7 @@ For example:
          (setq references
              (or (mail-header-references gnus-current-headers) ""))
          (set-buffer gnus-article-buffer)
-         (let* ((buffer-read-only nil)
+         (let* ((inhibit-read-only t)
                 (headers
                  (mapcar (lambda (field)
                            (and (save-restriction
@@ -6938,7 +7142,7 @@ For example:
 (defun gnus-mime-security-verify-or-decrypt (handle)
   (mm-remove-parts (cdr handle))
   (let ((region (mm-handle-multipart-ctl-parameter handle 'gnus-region))
-       point buffer-read-only)
+       point (inhibit-read-only t))
     (if region
        (goto-char (car region)))
     (save-restriction
@@ -6968,7 +7172,7 @@ For example:
                 (not (get-text-property (point) 'gnus-mime-details)))
                (gnus-mime-security-button-line-format
                 (get-text-property (point) 'gnus-line-format))
-               buffer-read-only)
+               (inhibit-read-only t))
            (forward-char -1)
            (while (eq (get-text-property (point) 'gnus-line-format)
                       gnus-mime-security-button-line-format)