*** empty log message ***
[gnus] / lisp / gnus-art.el
index 6c54dcb..935d001 100644 (file)
@@ -1,7 +1,7 @@
 ;;; gnus-art.el --- article mode commands for Gnus
-;; Copyright (C) 1996,97 Free Software Foundation, Inc.
+;; Copyright (C) 1996,97,98 Free Software Foundation, Inc.
 
-;; Author: Lars Magne Ingebrigtsen <larsi@ifi.uio.no>
+;; Author: Lars Magne Ingebrigtsen <larsi@gnus.org>
 ;; Keywords: news
 
 ;; This file is part of GNU Emacs.
 
 ;;; Code:
 
+(eval-when-compile (require 'cl))
+
 (require 'custom)
 (require 'gnus)
 (require 'gnus-sum)
 (require 'gnus-spec)
 (require 'gnus-int)
 (require 'browse-url)
+(require 'mm-bodies)
 
 (defgroup gnus-article nil
   "Article display."
   :group 'gnus-article)
 
 (defcustom gnus-ignored-headers
-  '("^Path:" "^Posting-Version:" "^Article-I.D.:" "^Expires:"
-    "^Date-Received:" "^References:" "^Control:" "^Xref:" "^Lines:"
-    "^Posted:" "^Relay-Version:" "^Message-ID:" "^Nf-ID:" "^Nf-From:"
-    "^Approved:" "^Sender:" "^Received:" "^Mail-from:") 
-  "All headers that match this regexp will be hidden.
+  '("^Path:" "^Expires:" "^Date-Received:" "^References:" "^Xref:" "^Lines:"
+    "^Relay-Version:" "^Message-ID:" "^Approved:" "^Sender:" "^Received:" 
+    "^X-UIDL:" "^MIME-Version:" "^Return-Path:" "^In-Reply-To:"
+    "^Content-Type:" "^Content-Transfer-Encoding:" "^X-WebTV-Signature:"
+    "^X-MimeOLE:" "^X-MSMail-Priority:" "^X-Priority:" "^X-Loop:"
+    "^X-Authentication-Warning:" "^X-MIME-Autoconverted:" "^X-Face:"
+    "^X-Attribution:" "^X-Originating-IP:" "^Delivered-To:"
+    "^NNTP-[-A-Za-z]+:" "^Distribution:" "^X-no-archive:" "^X-Trace:"
+    "^X-Complaints-To:" "^X-NNTP-Posting-Host:" "^X-Orig.*:"
+    "^Abuse-Reports-To:" "^Cache-Post-Path:" "^X-Article-Creation-Date:"
+    "^X-Poster:" "^X-Mail2News-Path:" "^X-Server-Date:" "^X-Cache:"
+    "^Originator:" "^X-Problems-To:" "^X-Auth-User:" "^X-Post-Time:" 
+    "^X-Admin:" "^X-UID:" "^Resent-[-A-Za-z]+:" "^X-Mailing-List:"
+    "^Precedence:" "^Original-[-A-Za-z]+:" "^X-filename:" "^X-Orcpt:"
+    "^Old-Received:" "^X-Pgp-Fingerprint:" "^X-Pgp-Key-Id:"
+    "^X-Pgp-Public-Key-Url:" "^X-Auth:" "^X-From-Line:"
+    "^X-Gnus-Article-Number:" "^X-Majordomo:" "^X-Url:" "^X-Sender:"
+    "^X-Mailing-List:" "^MBOX-Line" "^Priority:" "^X-Pgp" "^X400-[-A-Za-z]+:"
+    "^Status:")
+  "*All headers that start with this regexp will be hidden.
 This variable can also be a list of regexps of headers to be ignored.
 If `gnus-visible-headers' is non-nil, this variable will be ignored."
   :type '(choice :custom-show nil
@@ -101,9 +119,9 @@ If `gnus-visible-headers' is non-nil, this variable will be ignored."
                 (repeat regexp))
   :group 'gnus-article-hiding)
 
-(defcustom gnus-visible-headers 
-  "^From:\\|^Newsgroups:\\|^Subject:\\|^Date:\\|^Followup-To:\\|^Reply-To:\\|^Organization:\\|^Summary:\\|^Keywords:\\|^To:\\|^Cc:\\|^Posted-To:\\|^Mail-Copies-To:\\|^Apparently-To:\\|^Gnus-Warning:\\|^Resent-From"
-  "All headers that do not match this regexp will be hidden.
+(defcustom gnus-visible-headers
+  "From:\\|^Newsgroups:\\|^Subject:\\|^Date:\\|^Followup-To:\\|^Reply-To:\\|^Organization:\\|^Summary:\\|^Keywords:\\|^To:\\|^Cc:\\|^Posted-To:\\|^Mail-Copies-To:\\|^Apparently-To:\\|^Gnus-Warning:\\|^Resent-From:\\|X-Sent:"
+  "*All headers that do not match this regexp will be hidden.
 This variable can also be a list of regexp of headers to remain visible.
 If this variable is non-nil, `gnus-ignored-headers' will be ignored."
   :type '(repeat :value-to-internal (lambda (widget value)
@@ -115,9 +133,9 @@ If this variable is non-nil, `gnus-ignored-headers' will be ignored."
   :group 'gnus-article-hiding)
 
 (defcustom gnus-sorted-header-list
-  '("^From:" "^Subject:" "^Summary:" "^Keywords:" "^Newsgroups:" "^To:"
-    "^Cc:" "^Date:" "^Organization:")
-  "This variable is a list of regular expressions.
+  '("^From:" "^Subject:" "^Summary:" "^Keywords:" "^Newsgroups:"
+    "^Followup-To:" "^To:" "^Cc:" "^Date:" "^Organization:")
+  "*This variable is a list of regular expressions.
 If it is non-nil, headers that match the regular expressions will
 be placed first in the article buffer in the sequence specified by
 this list."
@@ -127,12 +145,14 @@ this list."
 (defcustom gnus-boring-article-headers '(empty followup-to reply-to)
   "Headers that are only to be displayed if they have interesting data.
 Possible values in this list are `empty', `newsgroups', `followup-to',
-`reply-to', and `date'."
+`reply-to', `date', `long-to', and `many-to'."
   :type '(set (const :tag "Headers with no content." empty)
              (const :tag "Newsgroups with only one group." newsgroups)
              (const :tag "Followup-to identical to newsgroups." followup-to)
              (const :tag "Reply-to identical to from." reply-to)
-             (const :tag "Date less than four days old." date))
+             (const :tag "Date less than four days old." date)
+             (const :tag "Very long To header." long-to)
+             (const :tag "Multiple To headers." many-to))
   :group 'gnus-article-hiding)
 
 (defcustom gnus-signature-separator '("^-- $" "^-- *$")
@@ -151,17 +171,20 @@ longer (in lines) than that number.  If it is a function, the function
 will be called without any parameters, and if it returns nil, there is
 no signature in the buffer.  If it is a string, it will be used as a
 regexp.  If it matches, the text in question is not a signature."
-  :type '(choice integer number function regexp)
+  :type '(choice (integer :value 200)
+                (number :value 4.0)
+                (function :value fun)
+                (regexp :value ".*"))
   :group 'gnus-article-signature)
 
 (defcustom gnus-hidden-properties '(invisible t intangible t)
   "Property list to use for hiding text."
-  :type 'sexp 
+  :type 'sexp
   :group 'gnus-article-hiding)
 
 (defcustom gnus-article-x-face-command
   "{ echo '/* Width=48, Height=48 */'; uncompface; } | icontopbm | xv -quit -"
-  "String or function to be executed to display an X-Face header.
+  "*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."
   :type 'string                                ;Leave function case to Lisp.
@@ -169,12 +192,12 @@ asynchronously.    The compressed face will be piped to this command."
 
 (defcustom gnus-article-x-face-too-ugly nil
   "Regexp matching posters whose face shouldn't be shown automatically."
-  :type 'regexp
+  :type '(choice regexp (const nil))
   :group 'gnus-article-washing)
 
 (defcustom gnus-emphasis-alist
   (let ((format
-        "\\(\\s-\\|^\\|[-\"\(]\\)\\(%s\\(\\w+\\(\\s-+\\w+\\)*[.,]?\\)%s\\)\\(\\s-\\|[-?!.,;:\"\)]\\)")
+        "\\(\\s-\\|^\\|[-\"]\\|\\s(\\|\\s)\\)\\(%s\\(\\w+\\(\\s-+\\w+\\)*[.,]?\\)%s\\)\\(\\s-\\|[-?!.,;:\"]\\|\\s(\\|\\s)\\)")
        (types
         '(("_" "_" underline)
           ("/" "/" italic)
@@ -189,9 +212,9 @@ asynchronously.      The compressed face will be piped to this command."
         (lambda (spec)
           (list
            (format format (car spec) (cadr spec))
-           2 3 (intern (format "gnus-emphasis-%s" (caddr spec)))))
+           2 3 (intern (format "gnus-emphasis-%s" (nth 2 spec)))))
         types)))
-  "Alist that says how to fontify certain phrases.
+  "*Alist that says how to fontify certain phrases.
 Each item looks like this:
 
   (\"_\\\\(\\\\w+\\\\)_\" 0 1 'underline)
@@ -232,7 +255,7 @@ is the face used for highlighting."
   "Face used for displaying bold italic emphasized text (/*word*/)."
   :group 'gnus-article-emphasis)
 
-(defface gnus-emphasis-underline-bold-italic 
+(defface gnus-emphasis-underline-bold-italic
   '((t (:bold t :italic t :underline t)))
   "Face used for displaying underlined bold italic emphasized text.
 Esample: (_/*word*/_)."
@@ -240,21 +263,18 @@ Esample: (_/*word*/_)."
 
 (defcustom gnus-article-time-format "%a, %b %d %Y %T %Z"
   "Format for display of Date headers in article bodies.
-See `format-time-zone' for the possible values."
-  :type 'string
+See `format-time-string' for the possible values.
+
+The variable can also be function, which should return a complete Date
+header.  The function is called with one argument, the time, which can
+be fed to `format-time-string'."
+  :type '(choice string symbol)
   :link '(custom-manual "(gnus)Article Date")
   :group 'gnus-article-washing)
 
 (eval-and-compile
-  (autoload 'hexl-hex-string-to-integer "hexl")
-  (autoload 'timezone-make-date-arpa-standard "timezone")
   (autoload 'mail-extract-address-components "mail-extr"))
 
-(defcustom gnus-article-save-directory gnus-directory
-  "*Name of the directory articles will be saved in (default \"~/News\")."
-  :group 'gnus-article-saving
-  :type 'directory)
-
 (defcustom gnus-save-all-headers t
   "*If non-nil, don't remove any headers before saving."
   :group 'gnus-article-saving
@@ -271,7 +291,7 @@ each invocation of the saving commands."
   :group 'gnus-article-saving
   :type '(choice (item always)
                 (item :tag "never" nil)
-                (sexp :tag "once" :format "%t")))
+                (sexp :tag "once" :format "%t\n" :value t)))
 
 (defcustom gnus-saved-headers gnus-visible-headers
   "Headers to keep if `gnus-save-all-headers' is nil.
@@ -279,7 +299,7 @@ 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."
   :group 'gnus-article-saving
-  :type '(repeat string))
+  :type 'regexp)
 
 (defcustom gnus-default-article-saver 'gnus-summary-save-in-rmail
   "A function to save articles in your favourite format.
@@ -328,8 +348,9 @@ LAST-FILE."
   :type 'function)
 
 (defcustom gnus-split-methods
-  '((gnus-article-archive-name))
-  "Variable used to suggest where articles are to be saved.
+  '((gnus-article-archive-name)
+    (gnus-article-nndoc-name))
+  "*Variable used to suggest where articles are to be saved.
 For instance, if you would like to save articles related to Gnus in
 the file \"gnus-stuff\", and articles related to VM in \"vm-stuff\",
 you could set this variable to something like:
@@ -349,9 +370,9 @@ 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 function)
-                        (cons regexp (repeat string))
-                        sexp)))
+  :type '(repeat (choice (list :value (fun) function)
+                        (cons :value ("" "") regexp (repeat string))
+                        (sexp :value nil))))
 
 (defcustom gnus-strict-mime t
   "*If nil, MIME-decode even if there is no Mime-Version header."
@@ -377,7 +398,7 @@ beginning of a line."
   :type 'regexp
   :group 'gnus-article-various)
 
-(defcustom gnus-article-mode-line-format "Gnus: %%b %S"
+(defcustom gnus-article-mode-line-format "Gnus: %g %S"
   "*The format specification for the article mode line.
 See `gnus-summary-mode-line-format' for a closer description."
   :type 'string
@@ -399,6 +420,11 @@ If you want to run a special decoding program like nkf, use this hook."
   :type 'hook
   :group 'gnus-article-various)
 
+(defcustom gnus-article-hide-pgp-hook nil
+  "*A hook called after successfully hiding a PGP signature."
+  :type 'hook
+  :group 'gnus-article-various)
+
 (defcustom gnus-article-button-face 'bold
   "Face used for highlighting buttons in the article buffer.
 
@@ -415,59 +441,67 @@ above them."
   :type 'face
   :group 'gnus-article-buttons)
 
-(defcustom gnus-signature-face 'italic
-  "Face used for highlighting a signature in the article buffer."
+(defcustom gnus-signature-face 'gnus-signature-face
+  "Face used for highlighting a signature in the article buffer.
+Obsolete; use the face `gnus-signature-face' for customizations instead."
   :type 'face
   :group 'gnus-article-highlight
   :group 'gnus-article-signature)
 
-(defface gnus-header-from-face 
+(defface gnus-signature-face
+  '((((type x))
+     (:italic t)))
+  "Face used for highlighting a signature in the article buffer."
+  :group 'gnus-article-highlight
+  :group 'gnus-article-signature)
+
+(defface gnus-header-from-face
   '((((class color)
       (background dark))
-     (:foreground "light blue" :bold t :italic t))
+     (:foreground "spring green"))
     (((class color)
       (background light))
-     (:foreground "MidnightBlue" :bold t :italic t))
-    (t 
-     (:bold t :italic t)))
+     (:foreground "red3"))
+    (t
+     (:italic t)))
   "Face used for displaying from headers."
   :group 'gnus-article-headers
   :group 'gnus-article-highlight)
 
-(defface gnus-header-subject-face 
+(defface gnus-header-subject-face
   '((((class color)
       (background dark))
-     (:foreground "pink" :bold t :italic t))
+     (:foreground "SeaGreen3"))
     (((class color)
       (background light))
-     (:foreground "firebrick" :bold t :italic t))
-    (t 
+     (:foreground "red4"))
+    (t
      (:bold t :italic t)))
   "Face used for displaying subject headers."
   :group 'gnus-article-headers
   :group 'gnus-article-highlight)
 
-(defface gnus-header-newsgroups-face 
+(defface gnus-header-newsgroups-face
   '((((class color)
       (background dark))
-     (:foreground "yellow" :bold t :italic t))
+     (:foreground "yellow" :italic t))
     (((class color)
       (background light))
-     (:foreground "indianred" :bold t :italic t))
-    (t 
-     (:bold t :italic t)))
+     (:foreground "MidnightBlue" :italic t))
+    (t
+     (:italic t)))
   "Face used for displaying newsgroups headers."
   :group 'gnus-article-headers
   :group 'gnus-article-highlight)
 
-(defface gnus-header-name-face 
+(defface gnus-header-name-face
   '((((class color)
       (background dark))
-     (:foreground "cyan" :bold t))
+     (:foreground "SeaGreen"))
     (((class color)
       (background light))
-     (:foreground "DarkGreen" :bold t))
-    (t 
+     (:foreground "maroon"))
+    (t
      (:bold t)))
   "Face used for displaying header names."
   :group 'gnus-article-headers
@@ -479,8 +513,8 @@ above them."
      (:foreground "forest green" :italic t))
     (((class color)
       (background light))
-     (:foreground "DarkGreen" :italic t))
-    (t 
+     (:foreground "indianred4" :italic t))
+    (t
      (:italic t)))  "Face used for displaying header content."
   :group 'gnus-article-headers
   :group 'gnus-article-highlight)
@@ -490,9 +524,9 @@ above them."
     ("Subject" nil gnus-header-subject-face)
     ("Newsgroups:.*," nil gnus-header-newsgroups-face)
     ("" gnus-header-name-face gnus-header-content-face))
-  "Controls highlighting of article header.
+  "*Controls highlighting of article header.
 
-An alist of the form (HEADER NAME CONTENT). 
+An alist of the form (HEADER NAME CONTENT).
 
 HEADER is a regular expression which should match the name of an
 header header and NAME and CONTENT are either face names or nil.
@@ -513,9 +547,14 @@ displayed by the first non-nil matching CONTENT face."
 
 ;;; Internal variables
 
+(defvar article-lapsed-timer nil)
+(defvar gnus-article-current-summary nil)
+
 (defvar gnus-article-mode-syntax-table
   (let ((table (copy-syntax-table text-mode-syntax-table)))
-    ;;(modify-syntax-entry ?_ "w" table)
+    (modify-syntax-entry ?- "w" table)
+    (modify-syntax-entry ?> ")" table)
+    (modify-syntax-entry ?< "(" table)
     table)
   "Syntax table used in article mode buffers.
 Initialized from `text-mode-syntax-table.")
@@ -523,19 +562,18 @@ Initialized from `text-mode-syntax-table.")
 (defvar gnus-save-article-buffer nil)
 
 (defvar gnus-article-mode-line-format-alist
-    (nconc '((?w (gnus-article-wash-status) ?s))
-          gnus-summary-mode-line-format-alist))
+  (nconc '((?w (gnus-article-wash-status) ?s))
+        gnus-summary-mode-line-format-alist))
 
 (defvar gnus-number-of-articles-to-be-saved nil)
 
 (defvar gnus-inhibit-hiding nil)
-(defvar gnus-newsgroup-name)
 
 (defsubst gnus-article-hide-text (b e props)
   "Set text PROPS on the B to E region, extending `intangible' 1 past B."
   (add-text-properties b e props)
   (when (memq 'intangible props)
-    (put-text-property 
+    (put-text-property
      (max (1- b) (point-min))
      b 'intangible (cddr (memq 'intangible props)))))
 
@@ -552,7 +590,7 @@ Initialized from `text-mode-syntax-table.")
    b e (cons 'article-type (cons type gnus-hidden-properties))))
 
 (defun gnus-article-unhide-text-type (b e type)
-  "Hide text of TYPE between B and E."
+  "Unhide text of TYPE between B and E."
   (remove-text-properties
    b e (cons 'article-type (cons type gnus-hidden-properties)))
   (when (memq 'intangible gnus-hidden-properties)
@@ -572,14 +610,18 @@ Initialized from `text-mode-syntax-table.")
   (save-excursion
     (let ((b (point-min)))
       (while (setq b (text-property-any b (point-max) 'article-type type))
-       (delete-region b (incf b))))))
+       (delete-region
+        b (or (text-property-not-all b (point-max) 'article-type type)
+              (point-max)))))))
 
 (defun gnus-article-delete-invisible-text ()
   "Delete all invisible text in the current buffer."
   (save-excursion
     (let ((b (point-min)))
       (while (setq b (text-property-any b (point-max) 'invisible t))
-       (delete-region b (incf b))))))
+       (delete-region
+        b (or (text-property-not-all b (point-max) 'invisible t)
+              (point-max)))))))
 
 (defun gnus-article-text-type-exists-p (type)
   "Say whether any text of type TYPE exists in the buffer."
@@ -601,6 +643,7 @@ Initialized from `text-mode-syntax-table.")
 If given a negative prefix, always show; if given a positive prefix,
 always hide."
   (interactive (gnus-article-hidden-arg))
+  (current-buffer)
   (if (gnus-article-check-hidden-text 'headers arg)
       ;; Show boring headers as well.
       (gnus-article-show-hidden-text 'boring-headers)
@@ -609,6 +652,7 @@ always hide."
       (save-excursion
        (save-restriction
          (let ((buffer-read-only nil)
+               (case-fold-search t)
                (props (nconc (list 'article-type 'headers)
                              gnus-hidden-properties))
                (max (1+ (length gnus-sorted-header-list)))
@@ -625,7 +669,7 @@ always hide."
                             (listp gnus-visible-headers))
                        (mapconcat 'identity gnus-visible-headers "\\|"))))
                (inhibit-point-motion-hooks t)
-               want-list beg)
+               beg)
            ;; First we narrow to just the headers.
            (widen)
            (goto-char (point-min))
@@ -650,16 +694,16 @@ always hide."
            (while (re-search-forward "^[^ \t]*:" nil t)
              (beginning-of-line)
              ;; Mark the rank of the header.
-             (put-text-property 
+             (put-text-property
               (point) (1+ (point)) 'message-rank
               (if (or (and visible (looking-at visible))
                       (and ignored
                            (not (looking-at ignored))))
-                  (gnus-article-header-rank) 
+                  (gnus-article-header-rank)
                 (+ 2 max)))
              (forward-line 1))
            (message-sort-headers-1)
-           (when (setq beg (text-property-any 
+           (when (setq beg (text-property-any
                             (point-min) (point-max) 'message-rank (+ 2 max)))
              ;; We make the unwanted headers invisible.
              (if delete
@@ -693,7 +737,7 @@ always hide."
                (forward-line -1)
                (gnus-article-hide-text-type
                 (progn (beginning-of-line) (point))
-                (progn 
+                (progn
                   (end-of-line)
                   (if (re-search-forward "^[^ \t]" nil t)
                       (match-beginning 0)
@@ -717,16 +761,34 @@ always hide."
                (when (and
                       from reply-to
                       (ignore-errors
-                        (equal 
+                        (equal
                          (nth 1 (mail-extract-address-components from))
                          (nth 1 (mail-extract-address-components reply-to)))))
                  (gnus-article-hide-header "reply-to"))))
             ((eq elem 'date)
              (let ((date (message-fetch-field "date")))
                (when (and date
-                          (< (gnus-days-between (current-time-string) date)
+                          (< (days-between (current-time-string) date)
                              4))
-                 (gnus-article-hide-header "date")))))))))))
+                 (gnus-article-hide-header "date"))))
+            ((eq elem 'long-to)
+             (let ((to (message-fetch-field "to")))
+               (when (> (length to) 1024)
+                 (gnus-article-hide-header "to"))))
+            ((eq elem 'many-to)
+             (let ((to-count 0))
+               (goto-char (point-min))
+               (while (re-search-forward "^to:" nil t)
+                 (setq to-count (1+ to-count)))
+               (when (> to-count 1)
+                 (while (> to-count 0)
+                   (goto-char (point-min))
+                   (save-restriction
+                     (re-search-forward "^to:" nil nil to-count)
+                     (forward-line -1)
+                     (narrow-to-region (point) (point-max))
+                     (gnus-article-hide-header "to"))
+                   (setq to-count (1- to-count)))))))))))))
 
 (defun gnus-article-hide-header (header)
   (save-excursion
@@ -734,37 +796,62 @@ always hide."
     (when (re-search-forward (concat "^" header ":") nil t)
       (gnus-article-hide-text-type
        (progn (beginning-of-line) (point))
-       (progn 
+       (progn
         (end-of-line)
         (if (re-search-forward "^[^ \t]" nil t)
             (match-beginning 0)
           (point-max)))
        'boring-headers))))
 
-;; Written by Per Abrahamsen <amanda@iesd.auc.dk>.
+(defun article-treat-dumbquotes ()
+  "Translate M******** sm*rtq**t*s into proper text."
+  (interactive)
+  (article-translate-characters "\221\222\223\223" "`'\"\""))
+
+(defun article-translate-characters (from to)
+  "Translate all characters in the body of the article according to FROM and TO.
+FROM is a string of characters to translate from; to is a string of
+characters to translate to."
+  (save-excursion
+    (goto-char (point-min))
+    (when (search-forward "\n\n" nil t)
+      (let ((buffer-read-only nil)
+           (x (make-string 225 ?x))
+           (i -1))
+       (while (< (incf i) (length x))
+         (aset x i i))
+       (setq i 0)
+       (while (< i (length from))
+         (aset x (aref from i) (aref to i))
+         (incf i))
+       (translate-region (point) (point-max) x)))))
+
 (defun article-treat-overstrike ()
   "Translate overstrikes into bold text."
   (interactive)
   (save-excursion
-    (let ((buffer-read-only nil))
-      (while (search-forward "\b" nil t)
-       (let ((next (following-char))
-             (previous (char-after (- (point) 2))))
-         ;; We do the boldification/underlining by hiding the
-         ;; overstrikes and putting the proper text property
-         ;; on the letters.
-         (cond 
-          ((eq next previous)
-           (gnus-article-hide-text-type (- (point) 2) (point) 'overstrike)
-           (put-text-property (point) (1+ (point)) 'face 'bold))
-          ((eq next ?_)
-           (gnus-article-hide-text-type (1- (point)) (1+ (point)) 'overstrike)
-           (put-text-property
-            (- (point) 2) (1- (point)) 'face 'underline))
-          ((eq previous ?_)
-           (gnus-article-hide-text-type (- (point) 2) (point) 'overstrike)
-           (put-text-property
-            (point) (1+ (point)) 'face 'underline))))))))
+    (goto-char (point-min))
+    (when (search-forward "\n\n" nil t)
+      (let ((buffer-read-only nil))
+       (while (search-forward "\b" nil t)
+         (let ((next (following-char))
+               (previous (char-after (- (point) 2))))
+           ;; We do the boldification/underlining by hiding the
+           ;; overstrikes and putting the proper text property
+           ;; on the letters.
+           (cond
+            ((eq next previous)
+             (gnus-article-hide-text-type (- (point) 2) (point) 'overstrike)
+             (put-text-property (point) (1+ (point)) 'face 'bold))
+            ((eq next ?_)
+             (gnus-article-hide-text-type
+              (1- (point)) (1+ (point)) 'overstrike)
+             (put-text-property
+              (- (point) 2) (1- (point)) 'face 'underline))
+            ((eq previous ?_)
+             (gnus-article-hide-text-type (- (point) 2) (point) 'overstrike)
+             (put-text-property
+              (point) (1+ (point)) 'face 'underline)))))))))
 
 (defun article-fill ()
   "Format too long lines."
@@ -816,106 +903,111 @@ always hide."
     (when (process-status "article-x-face")
       (delete-process "article-x-face"))
     (let ((inhibit-point-motion-hooks t)
-         (case-fold-search nil)
-         from)
+         (case-fold-search t)
+         from last)
       (save-restriction
        (nnheader-narrow-to-headers)
        (setq from (message-fetch-field "from"))
        (goto-char (point-min))
-       (when (and 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))))
-                  ;; Has to be present.
-                  (re-search-forward "^X-Face: " nil t))
+       (while (and gnus-article-x-face-command
+                   (not last)
+                   (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))))
+                   ;; Has to be present.
+                   (re-search-forward "^X-Face: " nil t))
+         ;; This used to try to do multiple faces (`while' instead of
+         ;; `when' above), but (a) sending multiple EOFs to xv doesn't
+         ;; work (b) it can crash some versions of Emacs (c) are
+         ;; multiple faces really something to encourage?
+         (when (stringp gnus-article-x-face-command)
+           (setq last t))
          ;; We now have the area of the buffer where the X-Face is stored.
-         (let ((beg (point))
-               (end (1- (re-search-forward "^\\($\\|[^ \t]\\)" nil t))))
-           ;; We display the face.
-           (if (symbolp gnus-article-x-face-command)
-               ;; The command is a lisp function, so we call it.
-               (if (gnus-functionp gnus-article-x-face-command)
-                   (funcall gnus-article-x-face-command beg end)
-                 (error "%s is not a function" 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))
-               (process-send-region "article-x-face" beg end)
-               (process-send-eof "article-x-face")))))))))
+         (save-excursion
+           (let ((beg (point))
+                 (end (1- (re-search-forward "^\\($\\|[^ \t]\\)" nil t))))
+             ;; We display the face.
+             (if (symbolp gnus-article-x-face-command)
+                 ;; The command is a lisp function, so we call it.
+                 (if (gnus-functionp gnus-article-x-face-command)
+                     (funcall gnus-article-x-face-command beg end)
+                   (error "%s is not a function" 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))
+                 (process-send-region "article-x-face" beg end)
+                 (process-send-eof "article-x-face"))))))))))
+
+(defun gnus-article-decode-mime-words ()
+  "Decode all MIME-encoded words in the article."
+  (interactive)
+  (save-excursion
+    (set-buffer gnus-article-buffer)
+    (let ((inhibit-point-motion-hooks t)
+         buffer-read-only)
+      (rfc2047-decode-region (point-min) (point-max)))))
+
+(defun gnus-article-decode-charset (&optional prompt)
+  "Decode charset-encoded text in the article.
+If PROMPT (the prefix), prompt for a coding system to use."
+  (interactive "P")
+  (save-excursion
+    (set-buffer gnus-article-buffer)
+    (save-restriction
+      (message-narrow-to-head)
+      (let* ((inhibit-point-motion-hooks t)
+            (ct (message-fetch-field "Content-Type" t))
+            (cte (message-fetch-field "Content-Transfer-Encoding" t))
+            (charset (cond
+                      (prompt
+                       (mm-read-coding-system "Charset to decode: "))
+                      (ct
+                       (mm-content-type-charset ct))
+                      (gnus-newsgroup-name
+                       (gnus-group-find-parameter
+                        gnus-newsgroup-name 'charset))))
+            buffer-read-only)
+       (goto-char (point-max))
+       (widen)
+       (narrow-to-region (point) (point-max))
+       (mm-decode-body
+        charset (and cte (intern (downcase (gnus-strip-whitespace cte)))))))))
 
 (defalias 'gnus-decode-rfc1522 'article-decode-rfc1522)
 (defalias 'gnus-article-decode-rfc1522 'article-decode-rfc1522)
 (defun article-decode-rfc1522 ()
-  "Hack to remove QP encoding from headers."
-  (let ((case-fold-search t)
-       (inhibit-point-motion-hooks t)
-       (buffer-read-only nil)
-       string)
+  "Remove QP encoding from headers."
+  (let ((inhibit-point-motion-hooks t)
+       (buffer-read-only nil))
     (save-restriction
-      (narrow-to-region
-       (goto-char (point-min))
-       (or (search-forward "\n\n" nil t) (point-max)))
-      (goto-char (point-min))
-      (while (re-search-forward 
-             "=\\?iso-8859-1\\?q\\?\\([^?\t\n]*\\)\\?=" nil t)
-       (setq string (match-string 1))
-       (save-restriction
-         (narrow-to-region (match-beginning 0) (match-end 0))
-         (delete-region (point-min) (point-max))
-         (insert string)
-         (article-mime-decode-quoted-printable 
-          (goto-char (point-min)) (point-max))
-         (subst-char-in-region (point-min) (point-max) ?_ ? )
-         (goto-char (point-max)))
-       (goto-char (point-min))))))
+      (message-narrow-to-head)
+      (rfc2047-decode-region (point-min) (point-max)))))
 
 (defun article-de-quoted-unreadable (&optional force)
-  "Do a naive translation of a quoted-printable-encoded article.
-This is in no way, shape or form meant as a replacement for real MIME
-processing, but is simply a stop-gap measure until MIME support is
-written.
+  "Translate a quoted-printable-encoded article.
 If FORCE, decode the article whether it is marked as quoted-printable
 or not."
   (interactive (list 'force))
   (save-excursion
-    (let ((case-fold-search t)
-         (buffer-read-only nil)
+    (let ((buffer-read-only nil)
          (type (gnus-fetch-field "content-transfer-encoding")))
       (gnus-article-decode-rfc1522)
       (when (or force
                (and type (string-match "quoted-printable" (downcase type))))
        (goto-char (point-min))
        (search-forward "\n\n" nil 'move)
-       (article-mime-decode-quoted-printable (point) (point-max))))))
+       (quoted-printable-decode-region (point) (point-max))))))
 
 (defun article-mime-decode-quoted-printable-buffer ()
   "Decode Quoted-Printable in the current buffer."
-  (article-mime-decode-quoted-printable (point-min) (point-max)))
-  
-(defun article-mime-decode-quoted-printable (from to)
-  "Decode Quoted-Printable in the region between FROM and TO."
-  (interactive "r")
-  (goto-char from)
-  (while (search-forward "=" to t)
-    (cond ((eq (following-char) ?\n)
-          (delete-char -1)
-          (delete-char 1))
-         ((looking-at "[0-9A-F][0-9A-F]")
-          (subst-char-in-region
-           (1- (point)) (point) ?=
-           (hexl-hex-string-to-integer
-            (buffer-substring (point) (+ 2 (point)))))
-          (delete-char 2))
-         ((looking-at "=")
-          (delete-char 1))
-         ((gnus-message 3 "Malformed MIME quoted-printable message")))))
+  (quoted-printable-decode-region (point-min) (point-max)))
 
 (defun article-hide-pgp (&optional arg)
   "Toggle hiding of any PGP headers and signatures in the current article.
@@ -930,28 +1022,27 @@ always hide."
        (goto-char (point-min))
        ;; Hide the "header".
        (when (search-forward "\n-----BEGIN PGP SIGNED MESSAGE-----\n" nil t)
-         (gnus-article-hide-text-type (1+ (match-beginning 0))
-                                      (match-end 0) 'pgp))
-       (setq beg (point))
-       ;; Hide the actual signature.
-       (and (search-forward "\n-----BEGIN PGP SIGNATURE-----\n" nil t)
-            (setq end (1+ (match-beginning 0)))
-            (gnus-article-hide-text-type
-             end
-             (if (search-forward "\n-----END PGP SIGNATURE-----\n" nil t)
-                 (match-end 0)
-               ;; Perhaps we shouldn't hide to the end of the buffer
-               ;; if there is no end to the signature?
-               (point-max))
-             'pgp))
-       ;; Hide "- " PGP quotation markers.
-       (when (and beg end)
-         (narrow-to-region beg end)
-         (goto-char (point-min))
-         (while (re-search-forward "^- " nil t)
-           (gnus-article-hide-text-type 
-            (match-beginning 0) (match-end 0) 'pgp))
-         (widen))))))
+         (delete-region (1+ (match-beginning 0)) (match-end 0))
+         (setq beg (point))
+         ;; Hide the actual signature.
+         (and (search-forward "\n-----BEGIN PGP SIGNATURE-----\n" nil t)
+              (setq end (1+ (match-beginning 0)))
+              (delete-region
+               end
+               (if (search-forward "\n-----END PGP SIGNATURE-----\n" nil t)
+                   (match-end 0)
+                 ;; Perhaps we shouldn't hide to the end of the buffer
+                 ;; if there is no end to the signature?
+                 (point-max))))
+         ;; Hide "- " PGP quotation markers.
+         (when (and beg end)
+           (narrow-to-region beg end)
+           (goto-char (point-min))
+           (while (re-search-forward "^- " nil t)
+             (delete-region
+              (match-beginning 0) (match-end 0)))
+           (widen))
+         (gnus-run-hooks 'gnus-article-hide-pgp-hook))))))
 
 (defun article-hide-pem (&optional arg)
   "Toggle hiding of any PEM headers and signatures in the current article.
@@ -991,7 +1082,7 @@ always hide."
       (save-restriction
        (let ((buffer-read-only nil))
          (when (gnus-article-narrow-to-signature)
-           (gnus-article-hide-text-type 
+           (gnus-article-hide-text-type
             (point-min) (point-max) 'signature)))))))
 
 (defun article-strip-leading-blank-lines ()
@@ -1023,6 +1114,17 @@ always hide."
       (while (re-search-forward "\n\n\n+" nil t)
        (replace-match "\n\n" t t)))))
 
+(defun article-strip-leading-space ()
+  "Remove all white space from the beginning of the lines in the article."
+  (interactive)
+  (save-excursion
+    (let ((inhibit-point-motion-hooks t)
+         buffer-read-only)
+      (goto-char (point-min))
+      (search-forward "\n\n" nil t)
+      (while (re-search-forward "^[ \t]+" nil t)
+       (replace-match "" t t)))))
+
 (defun article-strip-blank-lines ()
   "Strip leading, trailing and multiple blank lines."
   (interactive)
@@ -1030,42 +1132,54 @@ always hide."
   (article-remove-trailing-blank-lines)
   (article-strip-multiple-blank-lines))
 
+(defun article-strip-all-blank-lines ()
+  "Strip all blank lines."
+  (interactive)
+  (save-excursion
+    (let ((inhibit-point-motion-hooks t)
+         buffer-read-only)
+      (goto-char (point-min))
+      (search-forward "\n\n" nil t)
+      (while (re-search-forward "^[ \t]*\n" nil t)
+       (replace-match "" t t)))))
+
 (defvar mime::preview/content-list)
 (defvar mime::preview-content-info/point-min)
 (defun gnus-article-narrow-to-signature ()
   "Narrow to the signature; return t if a signature is found, else nil."
   (widen)
-  (when (and (boundp 'mime::preview/content-list)
-            mime::preview/content-list)
-    ;; We have a MIMEish article, so we use the MIME data to narrow.
-    (let ((pcinfo (car (last mime::preview/content-list))))
-      (ignore-errors
-       (narrow-to-region
-        (funcall (intern "mime::preview-content-info/point-min") pcinfo)
-        (point-max)))))
-  
-  (when (gnus-article-search-signature)
-    (forward-line 1)
-    ;; Check whether we have some limits to what we consider
-    ;; to be a signature.
-    (let ((limits (if (listp gnus-signature-limit) gnus-signature-limit
-                   (list gnus-signature-limit)))
-         limit limited)
-      (while (setq limit (pop limits))
-       (if (or (and (integerp limit)
-                    (< (- (point-max) (point)) limit))
-               (and (floatp limit)
-                    (< (count-lines (point) (point-max)) limit))
-               (and (gnus-functionp limit)
-                    (funcall limit))
-               (and (stringp limit)
-                    (not (re-search-forward limit nil t))))
-           ()                          ; This limit did not succeed.
-         (setq limited t
-               limits nil)))
-      (unless limited
-       (narrow-to-region (point) (point-max))
-       t))))
+  (let ((inhibit-point-motion-hooks t))
+    (when (and (boundp 'mime::preview/content-list)
+              mime::preview/content-list)
+      ;; We have a MIMEish article, so we use the MIME data to narrow.
+      (let ((pcinfo (car (last mime::preview/content-list))))
+       (ignore-errors
+         (narrow-to-region
+          (funcall (intern "mime::preview-content-info/point-min") pcinfo)
+          (point-max)))))
+
+    (when (gnus-article-search-signature)
+      (forward-line 1)
+      ;; Check whether we have some limits to what we consider
+      ;; to be a signature.
+      (let ((limits (if (listp gnus-signature-limit) gnus-signature-limit
+                     (list gnus-signature-limit)))
+           limit limited)
+       (while (setq limit (pop limits))
+         (if (or (and (integerp limit)
+                      (< (- (point-max) (point)) limit))
+                 (and (floatp limit)
+                      (< (count-lines (point) (point-max)) limit))
+                 (and (gnus-functionp limit)
+                      (funcall limit))
+                 (and (stringp limit)
+                      (not (re-search-forward limit nil t))))
+             ()                        ; This limit did not succeed.
+           (setq limited t
+                 limits nil)))
+       (unless limited
+         (narrow-to-region (point) (point-max))
+         t)))))
 
 (defun gnus-article-search-signature ()
   "Search the current buffer for the signature separator.
@@ -1084,7 +1198,9 @@ Put point at the beginning of the signature separator."
       nil)))
 
 (eval-and-compile
-  (autoload 'w3-parse-buffer "w3-parse"))
+  (autoload 'w3-display "w3-parse")
+  (autoload 'w3-do-setup "w3" "" t)
+  (autoload 'w3-region "w3-display" "" t))
 
 (defun gnus-article-treat-html ()
   "Render HTML."
@@ -1092,20 +1208,22 @@ Put point at the beginning of the signature separator."
   (let ((cbuf (current-buffer)))
     (set-buffer gnus-article-buffer)
     (let (buf buffer-read-only b e)
+      (w3-do-setup)
       (goto-char (point-min))
       (narrow-to-region
        (if (search-forward "\n\n" nil t)
           (setq b (point))
         (point-max))
        (setq e (point-max)))
-      (nnheader-temp-write nil
+      (with-temp-buffer
        (insert-buffer-substring gnus-article-buffer b e)
+       (require 'url)
        (save-window-excursion
-         (setq buf (car (w3-parse-buffer (current-buffer))))))
+         (w3-region (point-min) (point-max))
+         (setq buf (buffer-substring-no-properties (point-min) (point-max)))))
       (when buf
        (delete-region (point-min) (point-max))
-       (insert-buffer-substring buf)
-       (kill-buffer buf))
+       (insert buf))
       (widen)
       (goto-char (point-min))
       (set-window-start (get-buffer-window (current-buffer)) (point-min))
@@ -1139,10 +1257,13 @@ means show, 0 means toggle."
 (defun gnus-article-hidden-text-p (type)
   "Say whether the current buffer contains hidden text of type TYPE."
   (let ((pos (text-property-any (point-min) (point-max) 'article-type type)))
-    (when pos
-      (if (get-text-property pos 'invisible)
-         'hidden
-       'shown))))
+    (while (and pos
+               (not (get-text-property pos 'invisible)))
+      (setq pos
+           (text-property-any (1+ pos) (point-max) 'article-type type)))
+    (if pos
+       'hidden
+      'shown)))
 
 (defun gnus-article-show-hidden-text (type &optional hide)
   "Show all hidden text of type TYPE.
@@ -1177,7 +1298,7 @@ If HIDE, hide the text instead."
 If TYPE is `local', convert to local time; if it is `lapsed', output
 how much time has lapsed since DATE."
   (interactive (list 'ut t))
-  (let* ((header (or header 
+  (let* ((header (or header
                     (mail-header-date gnus-current-headers)
                     (message-fetch-field "date")
                     ""))
@@ -1185,7 +1306,7 @@ how much time has lapsed since DATE."
                 header))
         (date-regexp "^Date:[ \t]\\|^X-Sent:[ \t]")
         (inhibit-point-motion-hooks t)
-        bface eface)
+        bface eface newline)
     (when (and date (not (string= date "")))
       (save-excursion
        (save-restriction
@@ -1197,17 +1318,22 @@ how much time has lapsed since DATE."
                  (setq bface (get-text-property (gnus-point-at-bol) 'face)
                        eface (get-text-property (1- (gnus-point-at-eol))
                                                 'face))
-                 (message-remove-header date-regexp t)
+                 (delete-region (progn (beginning-of-line) (point))
+                                (progn (end-of-line) (point)))
                  (beginning-of-line))
-             (goto-char (point-max)))
+             (goto-char (point-max))
+             (setq newline t))
            (insert (article-make-date-line date type))
            ;; Do highlighting.
-           (forward-line -1)
+           (beginning-of-line)
            (when (looking-at "\\([^:]+\\): *\\(.*\\)$")
-             (put-text-property (match-beginning 1) (match-end 1)
+             (put-text-property (match-beginning 1) (1+ (match-end 1))
                                 'face bface)
              (put-text-property (match-beginning 2) (match-end 2)
-                                'face eface))))))))
+                                'face eface))
+           (when newline
+             (end-of-line)
+             (insert "\n"))))))))
 
 (defun article-make-date-line (date type)
   "Return a DATE line of TYPE."
@@ -1217,45 +1343,35 @@ how much time has lapsed since DATE."
    ;; functions since they aren't particularly resistant to
    ;; buggy dates.
    ((eq type 'local)
-    (concat "Date: " (condition-case ()
-                        (timezone-make-date-arpa-standard date)
-                      (error date))
-           "\n"))
+    (concat "Date: " (current-time-string (date-to-time date))))
    ;; Convert to Universal Time.
    ((eq type 'ut)
     (concat "Date: "
-           (condition-case ()
-               (timezone-make-date-arpa-standard date nil "UT")
-             (error date))
-           "\n"))
+           (current-time-string
+            (let ((e (parse-time-string date)))
+              (setcar (last e) 0)
+              (encode-time e)))))
    ;; Get the original date from the article.
    ((eq type 'original)
-    (concat "Date: " date "\n"))
+    (concat "Date: " date))
    ;; Let the user define the format.
    ((eq type 'user)
+    (if (gnus-functionp gnus-article-time-format)
+       (funcall gnus-article-time-format (date-to-time date))
+      (concat
+       "Date: "
+       (format-time-string gnus-article-time-format (date-to-time date)))))
+   ;; ISO 8601.
+   ((eq type 'iso8601)
     (concat
      "Date: "
-     (format-time-string gnus-article-time-format
-                        (ignore-errors
-                          (gnus-encode-date
-                           (timezone-make-date-arpa-standard
-                            date nil "UT"))))
-     "\n"))
+     (format-time-string "%Y%M%DT%h%m%s" (date-to-time date))))
    ;; Do an X-Sent lapsed format.
    ((eq type 'lapsed)
     ;; If the date is seriously mangled, the timezone functions are
     ;; liable to bug out, so we ignore all errors.
     (let* ((now (current-time))
-          (real-time
-           (ignore-errors
-             (gnus-time-minus
-              (gnus-encode-date
-               (timezone-make-date-arpa-standard
-                (current-time-string now)
-                (current-time-zone now) "UT"))
-              (gnus-encode-date
-               (timezone-make-date-arpa-standard
-                date nil "UT")))))
+          (real-time (subtract-time now (date-to-time date)))
           (real-sec (and real-time
                          (+ (* (float (car real-time)) 65536)
                             (cadr real-time))))
@@ -1263,9 +1379,9 @@ how much time has lapsed since DATE."
           num prev)
       (cond
        ((null real-time)
-       "X-Sent: Unknown\n")
+       "X-Sent: Unknown")
        ((zerop sec)
-       "X-Sent: Now\n")
+       "X-Sent: Now")
        (t
        (concat
         "X-Sent: "
@@ -1284,15 +1400,15 @@ how much time has lapsed since DATE."
              (prog1
                  (concat (if prev ", " "") (int-to-string
                                             (floor num))
-                         " " (symbol-name (car unit)) 
+                         " " (symbol-name (car unit))
                          (if (> num 1) "s" ""))
                (setq prev t))))
          article-time-units "")
         ;; If dates are odd, then it might appear like the
         ;; article was sent in the future.
         (if (> real-sec 0)
-            " ago\n"
-          " in the future\n"))))))
+            " ago"
+          " in the future"))))))
    (t
     (error "Unknown conversion type: %s" type))))
 
@@ -1313,12 +1429,46 @@ function and want to see what the date was before converting."
   (interactive (list t))
   (article-date-ut 'lapsed highlight))
 
+(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
+        (when (gnus-buffer-live-p gnus-article-buffer)
+          (set-buffer gnus-article-buffer)
+          (goto-char (point-min))
+          (when (re-search-forward "^X-Sent:" nil t)
+            (article-date-lapsed t)))))))
+
+(defun gnus-start-date-timer (&optional n)
+  "Start a timer to update the X-Sent header in the article buffers.
+The numerical prefix says how frequently (in seconds) the function
+is to run."
+  (interactive "p")
+  (unless n
+    (setq n 1))
+  (gnus-stop-date-timer)
+  (setq article-lapsed-timer
+       (nnheader-run-at-time 1 n 'article-update-date-lapsed)))
+
+(defun gnus-stop-date-timer ()
+  "Stop the X-Sent timer."
+  (interactive)
+  (when article-lapsed-timer
+    (nnheader-cancel-timer article-lapsed-timer)
+    (setq article-lapsed-timer nil)))
+
 (defun article-date-user (&optional highlight)
   "Convert the current article date to the user-defined format.
 This format is defined by the `gnus-article-time-format' variable."
   (interactive (list t))
   (article-date-ut 'user highlight))
 
+(defun article-date-iso8601 (&optional highlight)
+  "Convert the current article date to ISO8601."
+  (interactive (list t))
+  (article-date-ut 'iso8601 highlight))
+
 (defun article-show-all ()
   "Show all hidden text in the article buffer."
   (interactive)
@@ -1367,10 +1517,12 @@ This format is defined by the `gnus-article-time-format' variable."
     (let ((gnus-visible-headers
           (or gnus-saved-headers gnus-visible-headers))
          (gnus-article-buffer save-buffer))
-      (gnus-article-hide-headers 1 t)))
+      (save-excursion
+       (set-buffer save-buffer)
+       (article-hide-headers 1 t))))
   (save-window-excursion
     (if (not gnus-default-article-saver)
-       (error "No default saver is defined.")
+       (error "No default saver is defined")
       ;; !!! Magic!  The saving functions all save
       ;; `gnus-original-article-buffer' (or so they think), but we
       ;; bind that variable to our save-buffer.
@@ -1384,63 +1536,86 @@ This format is defined by the `gnus-article-time-format' variable."
             (gnus-number-of-articles-to-be-saved
              (when (eq gnus-prompt-before-saving t)
                num)))                  ; Magic
-       (set-buffer gnus-summary-buffer)
-       (funcall gnus-default-article-saver filename))))) 
+       (set-buffer gnus-article-current-summary)
+       (funcall gnus-default-article-saver filename)))))
 
-(defun gnus-read-save-file-name (prompt default-name &optional filename)
-  (cond
-   ((eq filename 'default)
-    default-name)
-   (filename filename)
-   (t
-    (let* ((split-name (gnus-get-split-value gnus-split-methods))
-          (prompt
-           (format prompt (if (and gnus-number-of-articles-to-be-saved
-                                   (> gnus-number-of-articles-to-be-saved 1))
-                              (format "these %d articles"
-                                      gnus-number-of-articles-to-be-saved)
-                            "this article")))
-          (file
-           ;; Let the split methods have their say.
-           (cond
-            ;; No split name was found.
-            ((null split-name)
-             (read-file-name
-              (concat prompt " (default "
-                      (file-name-nondirectory default-name) ") ")
-              (file-name-directory default-name)
-              default-name))
-            ;; A single split name was found
-            ((= 1 (length split-name))
-             (let* ((name (car split-name))
-                    (dir (cond ((file-directory-p name)
-                                (file-name-as-directory name))
-                               ((file-exists-p name) name)
-                               (t gnus-article-save-directory))))
-               (read-file-name
-                (concat prompt " (default " name ") ")
-                dir name)))
-            ;; A list of splits was found.
-            (t
-             (setq split-name (nreverse split-name))
-             (let (result)
-               (let ((file-name-history (nconc split-name file-name-history)))
-                 (setq result
-                       (expand-file-name
-                        (read-file-name
-                         (concat prompt " (`M-p' for defaults) ")
-                         gnus-article-save-directory
-                         (car split-name))
-                        gnus-article-save-directory)))
-               (car (push result file-name-history)))))))
-      ;; Create the directory.
-      (gnus-make-directory (file-name-directory file))
-      ;; If we have read a directory, we append the default file name.
-      (when (file-directory-p file)
-       (setq file (concat (file-name-as-directory file)
-                          (file-name-nondirectory default-name))))
-      ;; Possibly translate some characters.
-      (nnheader-translate-file-chars file)))))
+(defun gnus-read-save-file-name (prompt &optional filename
+                                       function group headers variable)
+  (let ((default-name
+         (funcall function group headers (symbol-value variable)))
+       result)
+    (setq
+     result
+     (cond
+      ((eq filename 'default)
+       default-name)
+      ((eq filename t)
+       default-name)
+      (filename filename)
+      (t
+       (let* ((split-name (gnus-get-split-value gnus-split-methods))
+             (prompt
+              (format prompt
+                      (if (and gnus-number-of-articles-to-be-saved
+                               (> gnus-number-of-articles-to-be-saved 1))
+                          (format "these %d articles"
+                                  gnus-number-of-articles-to-be-saved)
+                        "this article")))
+             (file
+              ;; Let the split methods have their say.
+              (cond
+               ;; No split name was found.
+               ((null split-name)
+                (read-file-name
+                 (concat prompt " (default "
+                         (file-name-nondirectory default-name) ") ")
+                 (file-name-directory default-name)
+                 default-name))
+               ;; A single group name is returned.
+               ((stringp split-name)
+                (setq default-name
+                      (funcall function split-name headers
+                               (symbol-value variable)))
+                (read-file-name
+                 (concat prompt " (default "
+                         (file-name-nondirectory default-name) ") ")
+                 (file-name-directory default-name)
+                 default-name))
+               ;; A single split name was found
+               ((= 1 (length split-name))
+                (let* ((name (expand-file-name
+                              (car split-name) gnus-article-save-directory))
+                       (dir (cond ((file-directory-p name)
+                                   (file-name-as-directory name))
+                                  ((file-exists-p name) name)
+                                  (t gnus-article-save-directory))))
+                  (read-file-name
+                   (concat prompt " (default " name ") ")
+                   dir name)))
+               ;; A list of splits was found.
+               (t
+                (setq split-name (nreverse split-name))
+                (let (result)
+                  (let ((file-name-history
+                         (nconc split-name file-name-history)))
+                    (setq result
+                          (expand-file-name
+                           (read-file-name
+                            (concat prompt " (`M-p' for defaults) ")
+                            gnus-article-save-directory
+                            (car split-name))
+                           gnus-article-save-directory)))
+                  (car (push result file-name-history)))))))
+        ;; Create the directory.
+        (gnus-make-directory (file-name-directory file))
+        ;; If we have read a directory, we append the default file name.
+        (when (file-directory-p file)
+          (setq file (concat (file-name-as-directory file)
+                             (file-name-nondirectory default-name))))
+        ;; Possibly translate some characters.
+        (nnheader-translate-file-chars file)))))
+    (gnus-make-directory (file-name-directory result))
+    (set variable result)))
 
 (defun gnus-article-archive-name (group)
   "Return the first instance of an \"Archive-name\" in the current buffer."
@@ -1449,115 +1624,93 @@ This format is defined by the `gnus-article-time-format' variable."
       (nnheader-concat gnus-article-save-directory
                       (match-string 1)))))
 
+(defun gnus-article-nndoc-name (group)
+  "If GROUP is an nndoc group, return the name of the parent group."
+  (when (eq (car (gnus-find-method-for-group group)) 'nndoc)
+    (gnus-group-get-parameter group 'save-article-group)))
+
 (defun gnus-summary-save-in-rmail (&optional filename)
   "Append this article to Rmail file.
 Optional argument FILENAME specifies file name.
 Directory to save to is default to `gnus-article-save-directory'."
-  (interactive)
-  (gnus-set-global-variables)
-  (let ((default-name
-         (funcall gnus-rmail-save-name gnus-newsgroup-name
-                  gnus-current-headers gnus-newsgroup-last-rmail)))
-    (setq filename (gnus-read-save-file-name
-                   "Save %s in rmail file:" default-name filename))
-    (gnus-make-directory (file-name-directory filename))
-    (gnus-eval-in-buffer-window gnus-save-article-buffer
-      (save-excursion
-       (save-restriction
-         (widen)
-         (gnus-output-to-rmail filename))))
-    ;; Remember the directory name to save articles
-    (setq gnus-newsgroup-last-rmail filename)))
+  (setq filename (gnus-read-save-file-name
+                 "Save %s in rmail file:" filename
+                 gnus-rmail-save-name gnus-newsgroup-name
+                 gnus-current-headers 'gnus-newsgroup-last-rmail))
+  (gnus-eval-in-buffer-window gnus-save-article-buffer
+    (save-excursion
+      (save-restriction
+       (widen)
+       (rmail-output-to-rmail-file filename))))
+  filename)
 
 (defun gnus-summary-save-in-mail (&optional filename)
   "Append this article to Unix mail file.
 Optional argument FILENAME specifies file name.
 Directory to save to is default to `gnus-article-save-directory'."
-  (interactive)
-  (gnus-set-global-variables)
-  (let ((default-name
-         (funcall gnus-mail-save-name gnus-newsgroup-name
-                  gnus-current-headers gnus-newsgroup-last-mail)))
-    (setq filename (gnus-read-save-file-name
-                   "Save %s in Unix mail file:" default-name filename))
-    (setq filename
-         (expand-file-name filename
-                           (and default-name
-                                (file-name-directory default-name))))
-    (gnus-make-directory (file-name-directory filename))
-    (gnus-eval-in-buffer-window gnus-save-article-buffer
-      (save-excursion
-       (save-restriction
-         (widen)
-         (if (and (file-readable-p filename)
-                  (mail-file-babyl-p filename))
-             (gnus-output-to-rmail filename t)
-           (gnus-output-to-mail filename)))))
-    ;; Remember the directory name to save articles.
-    (setq gnus-newsgroup-last-mail filename)))
+  (setq filename (gnus-read-save-file-name
+                 "Save %s in Unix mail file:" filename
+                 gnus-mail-save-name gnus-newsgroup-name
+                 gnus-current-headers 'gnus-newsgroup-last-mail))
+  (gnus-eval-in-buffer-window gnus-save-article-buffer
+    (save-excursion
+      (save-restriction
+       (widen)
+       (if (and (file-readable-p filename)
+                (mail-file-babyl-p filename))
+           (rmail-output-to-rmail-file filename t)
+         (gnus-output-to-mail filename)))))
+  filename)
 
 (defun gnus-summary-save-in-file (&optional filename overwrite)
   "Append this article to file.
 Optional argument FILENAME specifies file name.
 Directory to save to is default to `gnus-article-save-directory'."
-  (interactive)
-  (gnus-set-global-variables)
-  (let ((default-name
-         (funcall gnus-file-save-name gnus-newsgroup-name
-                  gnus-current-headers gnus-newsgroup-last-file)))
-    (setq filename (gnus-read-save-file-name
-                   "Save %s in file:" default-name filename))
-    (gnus-make-directory (file-name-directory filename))
-    (gnus-eval-in-buffer-window gnus-save-article-buffer
-      (save-excursion
-       (save-restriction
-         (widen)
-         (when (and overwrite
-                    (file-exists-p filename))
-           (delete-file filename))
-         (gnus-output-to-file filename))))
-    ;; Remember the directory name to save articles.
-    (setq gnus-newsgroup-last-file filename)))
+  (setq filename (gnus-read-save-file-name
+                 "Save %s in file:" filename
+                 gnus-file-save-name gnus-newsgroup-name
+                 gnus-current-headers 'gnus-newsgroup-last-file))
+  (gnus-eval-in-buffer-window gnus-save-article-buffer
+    (save-excursion
+      (save-restriction
+       (widen)
+       (when (and overwrite
+                  (file-exists-p filename))
+         (delete-file filename))
+       (gnus-output-to-file filename))))
+  filename)
 
 (defun gnus-summary-write-to-file (&optional filename)
   "Write this article to a file.
 Optional argument FILENAME specifies file name.
 The directory to save in defaults to `gnus-article-save-directory'."
-  (interactive)
   (gnus-summary-save-in-file nil t))
 
 (defun gnus-summary-save-body-in-file (&optional filename)
   "Append this article body to a file.
 Optional argument FILENAME specifies file name.
 The directory to save in defaults to `gnus-article-save-directory'."
-  (interactive)
-  (gnus-set-global-variables)
-  (let ((default-name
-         (funcall gnus-file-save-name gnus-newsgroup-name
-                  gnus-current-headers gnus-newsgroup-last-file)))
-    (setq filename (gnus-read-save-file-name
-                   "Save %s body in file:" default-name filename))
-    (gnus-make-directory (file-name-directory filename))
-    (gnus-eval-in-buffer-window gnus-save-article-buffer
-      (save-excursion
-       (save-restriction
-         (widen)
-         (goto-char (point-min))
-         (when (search-forward "\n\n" nil t)
-           (narrow-to-region (point) (point-max)))
-         (gnus-output-to-file filename))))
-    ;; Remember the directory name to save articles.
-    (setq gnus-newsgroup-last-file filename)))
+  (setq filename (gnus-read-save-file-name
+                 "Save %s body in file:" filename
+                 gnus-file-save-name gnus-newsgroup-name
+                 gnus-current-headers 'gnus-newsgroup-last-file))
+  (gnus-eval-in-buffer-window gnus-save-article-buffer
+    (save-excursion
+      (save-restriction
+       (widen)
+       (goto-char (point-min))
+       (when (search-forward "\n\n" nil t)
+         (narrow-to-region (point) (point-max)))
+       (gnus-output-to-file filename))))
+  filename)
 
 (defun gnus-summary-save-in-pipe (&optional command)
   "Pipe this article to subprocess."
-  (interactive)
-  (gnus-set-global-variables)
   (setq command
        (cond ((eq command 'default)
               gnus-last-shell-command)
              (command command)
-             (t (read-string 
+             (t (read-string
                  (format
                   "Shell command on %s: "
                   (if (and gnus-number-of-articles-to-be-saved
@@ -1649,7 +1802,7 @@ If variable `gnus-use-long-file-name' is non-nil, it is
                 gfunc (cdr func))
         (setq afunc func
               gfunc (intern (format "gnus-%s" func))))
-       (fset gfunc 
+       (fset gfunc
             (if (not (fboundp afunc))
                 nil
               `(lambda (&optional interactive &rest args)
@@ -1674,13 +1827,17 @@ If variable `gnus-use-long-file-name' is non-nil, it is
      article-remove-trailing-blank-lines
      article-strip-leading-blank-lines
      article-strip-multiple-blank-lines
+     article-strip-leading-space
      article-strip-blank-lines
+     article-strip-all-blank-lines
      article-date-local
+     article-date-iso8601
      article-date-original
      article-date-ut
      article-date-user
      article-date-lapsed
      article-emphasize
+     article-treat-dumbquotes
      (article-show-all . gnus-article-show-all-headers))))
 \f
 ;;;
@@ -1689,34 +1846,33 @@ If variable `gnus-use-long-file-name' is non-nil, it is
 
 (put 'gnus-article-mode 'mode-class 'special)
 
-(when t
-  (gnus-define-keys gnus-article-mode-map
-    " " gnus-article-goto-next-page
-    "\177" gnus-article-goto-prev-page
-    [delete] gnus-article-goto-prev-page
-    "\C-c^" gnus-article-refer-article
-    "h" gnus-article-show-summary
-    "s" gnus-article-show-summary
-    "\C-c\C-m" gnus-article-mail
-    "?" gnus-article-describe-briefly
-    gnus-mouse-2 gnus-article-push-button
-    "\r" gnus-article-press-button
-    "\t" gnus-article-next-button
-    "\M-\t" gnus-article-prev-button
-    "e" gnus-article-edit
-    "<" beginning-of-buffer
-    ">" end-of-buffer
-    "\C-c\C-i" gnus-info-find-node
-    "\C-c\C-b" gnus-bug
-
-    "\C-d" gnus-article-read-summary-keys
-    "\M-*" gnus-article-read-summary-keys
-    "\M-#" gnus-article-read-summary-keys
-    "\M-^" gnus-article-read-summary-keys
-    "\M-g" gnus-article-read-summary-keys)
-
-  (substitute-key-definition
-   'undefined 'gnus-article-read-summary-keys gnus-article-mode-map))
+(gnus-define-keys gnus-article-mode-map
+  " " gnus-article-goto-next-page
+  "\177" gnus-article-goto-prev-page
+  [delete] gnus-article-goto-prev-page
+  "\C-c^" gnus-article-refer-article
+  "h" gnus-article-show-summary
+  "s" gnus-article-show-summary
+  "\C-c\C-m" gnus-article-mail
+  "?" gnus-article-describe-briefly
+  gnus-mouse-2 gnus-article-push-button
+  "\r" gnus-article-press-button
+  "\t" gnus-article-next-button
+  "\M-\t" gnus-article-prev-button
+  "e" gnus-article-edit
+  "<" beginning-of-buffer
+  ">" end-of-buffer
+  "\C-c\C-i" gnus-info-find-node
+  "\C-c\C-b" gnus-bug
+
+  "\C-d" gnus-article-read-summary-keys
+  "\M-*" gnus-article-read-summary-keys
+  "\M-#" gnus-article-read-summary-keys
+  "\M-^" gnus-article-read-summary-keys
+  "\M-g" gnus-article-read-summary-keys)
+
+(substitute-key-definition
+ 'undefined 'gnus-article-read-summary-keys gnus-article-mode-map)
 
 (defun gnus-article-make-menu-bar ()
   (gnus-turn-off-edit-menu 'article)
@@ -1728,7 +1884,8 @@ If variable `gnus-use-long-file-name' is non-nil, it is
        ["Scroll backwards" gnus-article-goto-prev-page t]
        ["Show summary" gnus-article-show-summary t]
        ["Fetch Message-ID at point" gnus-article-refer-article t]
-       ["Mail to address at point" gnus-article-mail t]))
+       ["Mail to address at point" gnus-article-mail t]
+       ["Send a bug report" gnus-bug t]))
 
     (easy-menu-define
      gnus-article-treatment-menu gnus-article-mode-map ""
@@ -1740,16 +1897,13 @@ If variable `gnus-use-long-file-name' is non-nil, it is
        ["Remove carriage return" gnus-article-remove-cr t]
        ["Remove quoted-unreadable" gnus-article-de-quoted-unreadable t]))
 
-    (when nil
-      (when (boundp 'gnus-summary-article-menu)
-       (define-key gnus-article-mode-map [menu-bar commands]
-         (cons "Commands" gnus-summary-article-menu))))
+    ;; Note "Commands" menu is defined in gnus-sum.el for consistency
 
     (when (boundp 'gnus-summary-post-menu)
       (define-key gnus-article-mode-map [menu-bar post]
        (cons "Post" gnus-summary-post-menu)))
 
-    (run-hooks 'gnus-article-menu-hook)))
+    (gnus-run-hooks 'gnus-article-menu-hook)))
 
 (defun gnus-article-mode ()
   "Major mode for displaying an article.
@@ -1769,7 +1923,6 @@ commands:
   (interactive)
   (when (gnus-visual-p 'article-menu 'menu)
     (gnus-article-make-menu-bar))
-  (kill-all-local-variables)
   (gnus-simplify-mode-line)
   (setq mode-name "Article")
   (setq major-mode 'gnus-article-mode)
@@ -1779,12 +1932,15 @@ commands:
   (use-local-map gnus-article-mode-map)
   (gnus-update-format-specifications nil 'article-mode)
   (set (make-local-variable 'page-delimiter) gnus-page-delimiter)
-  (set (make-local-variable 'gnus-button-marker-list) nil)
+  (make-local-variable 'gnus-page-broken)
+  (make-local-variable 'gnus-button-marker-list)
+  (make-local-variable 'gnus-article-current-summary)
   (gnus-set-default-directory)
   (buffer-disable-undo (current-buffer))
   (setq buffer-read-only t)
   (set-syntax-table gnus-article-mode-syntax-table)
-  (run-hooks 'gnus-article-mode-hook))
+  (mm-enable-multibyte)
+  (gnus-run-hooks 'gnus-article-mode-hook))
 
 (defun gnus-article-setup-buffer ()
   "Initialize the article buffer."
@@ -1805,23 +1961,20 @@ commands:
        (gnus-set-global-variables)))
     ;; Init original article buffer.
     (save-excursion
-      (set-buffer (get-buffer-create gnus-original-article-buffer))
+      (set-buffer (gnus-get-buffer-create gnus-original-article-buffer))
       (buffer-disable-undo (current-buffer))
       (setq major-mode 'gnus-original-article-mode)
-      (gnus-add-current-to-buffer-list)
       (make-local-variable 'gnus-original-article))
     (if (get-buffer name)
        (save-excursion
          (set-buffer name)
          (buffer-disable-undo (current-buffer))
          (setq buffer-read-only t)
-         (gnus-add-current-to-buffer-list)
          (unless (eq major-mode 'gnus-article-mode)
            (gnus-article-mode))
          (current-buffer))
       (save-excursion
-       (set-buffer (get-buffer-create name))
-       (gnus-add-current-to-buffer-list)
+       (set-buffer (gnus-get-buffer-create name))
        (gnus-article-mode)
        (make-local-variable 'gnus-summary-buffer)
        (current-buffer)))))
@@ -1851,14 +2004,9 @@ If ALL-HEADERS is non-nil, no headers are hidden."
     (unless (eq major-mode 'gnus-summary-mode)
       (set-buffer gnus-summary-buffer))
     (setq gnus-summary-buffer (current-buffer))
-    ;; Make sure the connection to the server is alive.
-    (unless (gnus-server-opened
-            (gnus-find-method-for-group gnus-newsgroup-name))
-      (gnus-check-server (gnus-find-method-for-group gnus-newsgroup-name))
-      (gnus-request-group gnus-newsgroup-name t))
     (let* ((gnus-article (if header (mail-header-number header) article))
           (summary-buffer (current-buffer))
-          (internal-hook gnus-article-internal-prepare-hook)
+          (gnus-tmp-internal-hook gnus-article-internal-prepare-hook)
           (group gnus-newsgroup-name)
           result)
       (save-excursion
@@ -1879,17 +2027,21 @@ If ALL-HEADERS is non-nil, no headers are hidden."
                      (cons gnus-newsgroup-name article))
                (set-buffer gnus-summary-buffer)
                (setq gnus-current-article article)
-               (gnus-summary-mark-article article gnus-canceled-mark))
-             (unless (memq article gnus-newsgroup-sparse)
-               (gnus-error
-                1 "No such article (may have expired or been canceled)")))
-         (if (or (eq result 'pseudo) (eq result 'nneething))
+               (if (eq (gnus-article-mark article) gnus-undownloaded-mark)
+                   (progn
+                     (gnus-summary-set-agent-mark article)
+                     (message "Message marked for downloading"))
+                 (gnus-summary-mark-article article gnus-canceled-mark)
+                 (unless (memq article gnus-newsgroup-sparse)
+                   (gnus-error 1
+                    "No such article (may have expired or been canceled)")))))
+         (if (or (eq result 'pseudo)
+                 (eq result 'nneething))
              (progn
                (save-excursion
                  (set-buffer summary-buffer)
+                 (push article gnus-newsgroup-history)
                  (setq gnus-last-article gnus-current-article
-                       gnus-newsgroup-history (cons gnus-current-article
-                                                    gnus-newsgroup-history)
                        gnus-current-article 0
                        gnus-current-headers nil
                        gnus-article-current nil)
@@ -1907,9 +2059,8 @@ If ALL-HEADERS is non-nil, no headers are hidden."
              ;; `gnus-current-article' must be an article number.
              (save-excursion
                (set-buffer summary-buffer)
+               (push article gnus-newsgroup-history)
                (setq gnus-last-article gnus-current-article
-                     gnus-newsgroup-history (cons gnus-current-article
-                                                  gnus-newsgroup-history)
                      gnus-current-article article
                      gnus-current-headers
                      (gnus-summary-article-header gnus-current-article)
@@ -1917,47 +2068,51 @@ If ALL-HEADERS is non-nil, no headers are hidden."
                      (cons gnus-newsgroup-name gnus-current-article))
                (unless (vectorp gnus-current-headers)
                  (setq gnus-current-headers nil))
-               (gnus-summary-show-thread)
-               (run-hooks 'gnus-mark-article-hook)
+               (gnus-summary-goto-subject gnus-current-article)
+               (when (gnus-summary-show-thread)
+                 ;; If the summary buffer really was folded, the
+                 ;; previous goto may not actually have gone to
+                 ;; the right article, but the thread root instead.
+                 ;; So we go again.
+                 (gnus-summary-goto-subject gnus-current-article))
+               (gnus-run-hooks 'gnus-mark-article-hook)
                (gnus-set-mode-line 'summary)
                (when (gnus-visual-p 'article-highlight 'highlight)
-                 (run-hooks 'gnus-visual-mark-article-hook))
+                 (gnus-run-hooks 'gnus-visual-mark-article-hook))
                ;; Set the global newsgroup variables here.
                ;; Suggested by Jim Sisolak
                ;; <sisolak@trans4.neep.wisc.edu>.
                (gnus-set-global-variables)
                (setq gnus-have-all-headers
-                     (or all-headers gnus-show-all-headers))
-               (and gnus-use-cache
-                    (vectorp (gnus-summary-article-header article))
-                    (gnus-cache-possibly-enter-article
-                     group article
-                     (gnus-summary-article-header article)
-                     (memq article gnus-newsgroup-marked)
-                     (memq article gnus-newsgroup-dormant)
-                     (memq article gnus-newsgroup-unreads)))))
+                     (or all-headers gnus-show-all-headers))))
            (when (or (numberp article)
                      (stringp article))
              ;; Hooks for getting information from the article.
              ;; This hook must be called before being narrowed.
              (let (buffer-read-only)
-               (run-hooks 'internal-hook)
-               (run-hooks 'gnus-article-prepare-hook)
+               (gnus-run-hooks 'gnus-tmp-internal-hook)
+               (gnus-run-hooks 'gnus-article-prepare-hook)
                ;; Decode MIME message.
                (when gnus-show-mime
                  (if (or (not gnus-strict-mime)
                          (gnus-fetch-field "Mime-Version"))
-                     (funcall gnus-show-mime-method)
+                     (let ((coding-system-for-write 'binary)
+                           (coding-system-for-read 'binary))
+                       (funcall gnus-show-mime-method))
                    (funcall gnus-decode-encoded-word-method)))
                ;; Perform the article display hooks.
-               (run-hooks 'gnus-article-display-hook))
+               (gnus-run-hooks 'gnus-article-display-hook))
              ;; Do page break.
              (goto-char (point-min))
-             (when gnus-break-pages
-               (gnus-narrow-to-page)))
+             (setq gnus-page-broken
+                   (when gnus-break-pages
+                     (gnus-narrow-to-page)
+                     t)))
            (gnus-set-mode-line 'article)
            (gnus-configure-windows 'article)
            (goto-char (point-min))
+           (search-forward "\n\n" nil t)
+           (set-window-point (get-buffer-window (current-buffer)) (point))
            t))))))
 
 (defun gnus-article-wash-status ()
@@ -1982,7 +2137,9 @@ If ALL-HEADERS is non-nil, no headers are hidden."
              (if mime ?m ? )
              (if emphasis ?e ? )))))
 
-(defun gnus-article-hide-headers-if-wanted ()
+(fset 'gnus-article-hide-headers-if-wanted 'gnus-article-maybe-hide-headers)
+
+(defun gnus-article-maybe-hide-headers ()
   "Hide unwanted headers if `gnus-have-all-headers' is nil.
 Provided for backwards compatibility."
   (or (save-excursion (set-buffer gnus-summary-buffer) gnus-have-all-headers)
@@ -1994,13 +2151,14 @@ Provided for backwards compatibility."
 (defun gnus-output-to-file (file-name)
   "Append the current article to a file named FILE-NAME."
   (let ((artbuf (current-buffer)))
-    (nnheader-temp-write nil
+    (with-temp-buffer
       (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")
-      (append-to-file (point-min) (point-max) file-name))))
+      (append-to-file (point-min) (point-max) file-name)
+      t)))
 
 (defun gnus-narrow-to-page (&optional arg)
   "Narrow the article buffer to a page.
@@ -2044,6 +2202,7 @@ If given a numerical ARG, move forward ARG pages."
   "Show the next page of the article."
   (interactive)
   (when (gnus-article-next-page)
+    (goto-char (point-min))
     (gnus-article-read-summary-keys nil (gnus-character-to-event ?n))))
 
 (defun gnus-article-goto-prev-page ()
@@ -2063,7 +2222,7 @@ Argument LINES specifies lines to be scrolled up."
        (and (pos-visible-in-window-p)  ;Not continuation line.
             (eobp)))
       ;; Nothing in this page.
-      (if (or (not gnus-break-pages)
+      (if (or (not gnus-page-broken)
              (save-excursion
                (save-restriction
                  (widen) (forward-line 1) (eobp)))) ;Real end-of-buffer?
@@ -2085,7 +2244,7 @@ Argument LINES specifies lines to be scrolled up."
 Argument LINES specifies lines to be scrolled down."
   (interactive "p")
   (move-to-window-line 0)
-  (if (and gnus-break-pages
+  (if (and gnus-page-broken
           (bobp)
           (not (save-restriction (widen) (bobp)))) ;Real beginning-of-buffer?
       (progn
@@ -2094,8 +2253,10 @@ Argument LINES specifies lines to be scrolled down."
        (recenter -1))
     (let ((scroll-in-place nil))
       (prog1
-         (ignore-errors
-           (scroll-down lines))
+         (condition-case ()
+             (scroll-down lines)
+           (beginning-of-buffer
+            (goto-char (point-min))))
        (move-to-window-line 0)))))
 
 (defun gnus-article-refer-article ()
@@ -2114,8 +2275,12 @@ Argument LINES specifies lines to be scrolled down."
 (defun gnus-article-show-summary ()
   "Reconfigure windows to show summary buffer."
   (interactive)
-  (gnus-configure-windows 'article)
-  (gnus-summary-goto-subject gnus-current-article))
+  (if (not (gnus-buffer-live-p gnus-summary-buffer))
+      (error "There is no summary buffer for this article buffer")
+    (gnus-article-set-globals)
+    (gnus-configure-windows 'article)
+    (gnus-summary-goto-subject gnus-current-article)
+    (gnus-summary-position-point)))
 
 (defun gnus-article-describe-briefly ()
   "Describe article mode commands briefly."
@@ -2129,7 +2294,7 @@ Argument LINES specifies lines to be scrolled down."
   (let ((obuf (current-buffer))
        (owin (current-window-configuration))
        func)
-    (switch-to-buffer gnus-summary-buffer 'norecord)
+    (switch-to-buffer gnus-article-current-summary 'norecord)
     (setq func (lookup-key (current-local-map) (this-command-keys)))
     (call-interactively func)
     (set-buffer obuf)
@@ -2140,7 +2305,7 @@ Argument LINES specifies lines to be scrolled down."
   "Execute the last keystroke in the summary buffer."
   (interactive)
   (let (func)
-    (pop-to-buffer gnus-summary-buffer 'norecord)
+    (pop-to-buffer gnus-article-current-summary 'norecord)
     (setq func (lookup-key (current-local-map) (this-command-keys)))
     (call-interactively func)))
 
@@ -2148,85 +2313,101 @@ Argument LINES specifies lines to be scrolled down."
   "Read a summary buffer key sequence and execute it from the article buffer."
   (interactive "P")
   (let ((nosaves
-        '("q" "Q"  "c" "r" "R" "\C-c\C-f" "m"  "a" "f" "F"
-          "Zc" "ZC" "ZE" "ZQ" "ZZ" "Zn" "ZR" "ZG" "ZN" "ZP"
-          "=" "^" "\M-^" "|"))
-       (nosave-but-article
-        '("A\r"))
-       (nosave-in-article
-        '("\C-d"))
-       keys)
+         '("q" "Q"  "c" "r" "R" "\C-c\C-f" "m"  "a" "f" "F"
+           "Zc" "ZC" "ZE" "ZQ" "ZZ" "Zn" "ZR" "ZG" "ZN" "ZP"
+           "=" "^" "\M-^" "|"))
+        (nosave-but-article
+         '("A\r"))
+        (nosave-in-article
+         '("\C-d"))
+        (up-to-top
+         '("n" "Gn" "p" "Gp"))
+        keys new-sum-point)
     (save-excursion
-      (set-buffer gnus-summary-buffer)
+      (set-buffer gnus-article-current-summary)
       (let (gnus-pick-mode)
-       (push (or key last-command-event) unread-command-events)
-       (setq keys (read-key-sequence nil))))
+        (push (or key last-command-event) unread-command-events)
+        (setq keys (read-key-sequence nil))))
     (message "")
 
     (if (or (member keys nosaves)
-           (member keys nosave-but-article)
-           (member keys nosave-in-article))
-       (let (func)
-         (save-window-excursion
-           (pop-to-buffer gnus-summary-buffer 'norecord)
-           ;; We disable the pick minor mode commands.
-           (let (gnus-pick-mode)
-             (setq func (lookup-key (current-local-map) keys))))
-         (if (not func)
-             (ding)
-           (unless (member keys nosave-in-article)
-             (set-buffer gnus-summary-buffer))
-           (call-interactively func))
-         (when (member keys nosave-but-article)
-           (pop-to-buffer gnus-article-buffer 'norecord)))
+            (member keys nosave-but-article)
+            (member keys nosave-in-article))
+        (let (func)
+          (save-window-excursion
+            (pop-to-buffer gnus-article-current-summary 'norecord)
+            ;; We disable the pick minor mode commands.
+            (let (gnus-pick-mode)
+              (setq func (lookup-key (current-local-map) keys))))
+          (if (not func)
+              (ding)
+            (unless (member keys nosave-in-article)
+              (set-buffer gnus-article-current-summary))
+            (call-interactively func)
+            (setq new-sum-point (point)))
+          (when (member keys nosave-but-article)
+            (pop-to-buffer gnus-article-buffer 'norecord)))
       ;; These commands should restore window configuration.
       (let ((obuf (current-buffer))
-           (owin (current-window-configuration))
-           (opoint (point))
-           func in-buffer)
-       (if not-restore-window
-           (pop-to-buffer gnus-summary-buffer 'norecord)
-         (switch-to-buffer gnus-summary-buffer 'norecord))
-       (setq in-buffer (current-buffer))
-       ;; We disable the pick minor mode commands.
-       (if (setq func (let (gnus-pick-mode)
-                        (lookup-key (current-local-map) keys)))
-           (call-interactively func)
-         (ding))
-       (when (eq in-buffer (current-buffer))
-         (set-buffer obuf)
-         (unless not-restore-window
-           (set-window-configuration owin))
-         (set-window-point (get-buffer-window (current-buffer)) opoint))))))
+            (owin (current-window-configuration))
+            (opoint (point))
+            (summary gnus-article-current-summary)
+            func in-buffer selected)
+        (if not-restore-window
+            (pop-to-buffer summary 'norecord)
+          (switch-to-buffer summary 'norecord))
+        (setq in-buffer (current-buffer))
+        ;; We disable the pick minor mode commands.
+        (if (setq func (let (gnus-pick-mode)
+                         (lookup-key (current-local-map) keys)))
+            (progn
+              (call-interactively func)
+              (setq new-sum-point (point)))
+          (ding))
+        (when (eq in-buffer (current-buffer))
+          (setq selected (gnus-summary-select-article))
+          (set-buffer obuf)
+          (unless not-restore-window
+            (set-window-configuration owin))
+          (unless (or (not (eq selected 'old)) (member keys up-to-top))
+            (set-window-point (get-buffer-window (current-buffer))
+                              opoint))
+          (let ((win (get-buffer-window gnus-article-current-summary)))
+            (when win
+              (set-window-point win new-sum-point))))))))
 
 (defun gnus-article-hide (&optional arg force)
   "Hide all the gruft in the current article.
 This means that PGP stuff, signatures, cited text and (some)
 headers will be hidden.
 If given a prefix, show the hidden text instead."
-  (interactive (list current-prefix-arg 'force))
+  (interactive (append (gnus-article-hidden-arg) (list 'force)))
   (gnus-article-hide-headers arg)
   (gnus-article-hide-pgp arg)
   (gnus-article-hide-citation-maybe arg force)
   (gnus-article-hide-signature arg))
 
 (defun gnus-article-maybe-highlight ()
-  "Do some article highlighting if `article-visual' is non-nil."
+  "Do some article highlighting if article highlighting is requested."
   (when (gnus-visual-p 'article-highlight 'highlight)
     (gnus-article-highlight-some)))
 
+(defun gnus-check-group-server ()
+  ;; Make sure the connection to the server is alive.
+  (unless (gnus-server-opened
+          (gnus-find-method-for-group gnus-newsgroup-name))
+    (gnus-check-server (gnus-find-method-for-group gnus-newsgroup-name))
+    (gnus-request-group gnus-newsgroup-name t)))
+
 (defun gnus-request-article-this-buffer (article group)
   "Get an article and insert it into this buffer."
-  (let (do-update-line)
+  (let (do-update-line sparse-header)
     (prog1
        (save-excursion
          (erase-buffer)
          (gnus-kill-all-overlays)
          (setq group (or group gnus-newsgroup-name))
 
-         ;; Open server if it has closed.
-         (gnus-check-server (gnus-find-method-for-group group))
-
          ;; Using `gnus-request-article' directly will insert the article into
          ;; `nntp-server-buffer' - so we'll save some time by not having to
          ;; copy it from the server buffer into the article buffer.
@@ -2243,18 +2424,18 @@ If given a prefix, show the hidden text instead."
          (when (and (numberp article)
                     gnus-summary-buffer
                     (get-buffer gnus-summary-buffer)
-                    (buffer-name (get-buffer gnus-summary-buffer)))
+                    (gnus-buffer-exists-p gnus-summary-buffer))
            (save-excursion
              (set-buffer gnus-summary-buffer)
              (let ((header (gnus-summary-article-header article)))
                (when (< article 0)
-                 (cond 
+                 (cond
                   ((memq article gnus-newsgroup-sparse)
                    ;; This is a sparse gap article.
                    (setq do-update-line article)
                    (setq article (mail-header-id header))
                    (let ((gnus-override-method gnus-refer-article-method))
-                     (gnus-read-header article))
+                     (setq sparse-header (gnus-read-header article)))
                    (setq gnus-newsgroup-sparse
                          (delq article gnus-newsgroup-sparse)))
                   ((vectorp header)
@@ -2264,13 +2445,16 @@ If given a prefix, show the hidden text instead."
                    ;; It is an extracted pseudo-article.
                    (setq article 'pseudo)
                    (gnus-request-pseudo-article header))))
-               
-               (let ((method (gnus-find-method-for-group 
+
+               (let ((method (gnus-find-method-for-group
                               gnus-newsgroup-name)))
-                 (if (not (eq (car method) 'nneething))
-                     ()
-                   (let ((dir (concat (file-name-as-directory (nth 1 method))
-                                      (mail-header-subject header))))
+                 (when (and (eq (car method) 'nneething)
+                            (vectorp header))
+                   (let ((dir (concat
+                               (file-name-as-directory
+                                (or (cadr (assq 'nneething-address method))
+                                    (nth 1 method)))
+                               (mail-header-subject header))))
                      (when (file-directory-p dir)
                        (setq article 'nneething)
                        (gnus-group-enter-directory dir))))))))
@@ -2280,7 +2464,7 @@ If given a prefix, show the hidden text instead."
           ((and (numberp article)
                 gnus-summary-buffer
                 (get-buffer gnus-summary-buffer)
-                (buffer-name (get-buffer gnus-summary-buffer))
+                (gnus-buffer-exists-p gnus-summary-buffer)
                 (eq (cdr (save-excursion
                            (set-buffer gnus-summary-buffer)
                            (assq article gnus-newsgroup-reads)))
@@ -2302,6 +2486,8 @@ If given a prefix, show the hidden text instead."
           ;; Check asynchronous pre-fetch.
           ((gnus-async-request-fetched-article group article (current-buffer))
            (gnus-async-prefetch-next group article gnus-summary-buffer)
+           (when (and (numberp article) gnus-keep-backlog)
+             (gnus-backlog-enter-article group article (current-buffer)))
            'article)
           ;; Check the cache.
           ((and gnus-use-cache
@@ -2315,42 +2501,44 @@ If given a prefix, show the hidden text instead."
                  (buffer-read-only nil))
              (erase-buffer)
              (gnus-kill-all-overlays)
+             (gnus-check-group-server)
              (when (gnus-request-article article group (current-buffer))
                (when (numberp article)
                  (gnus-async-prefetch-next group article gnus-summary-buffer)
                  (when gnus-keep-backlog
-                   (gnus-backlog-enter-article 
+                   (gnus-backlog-enter-article
                     group article (current-buffer))))
                'article)))
           ;; It was a pseudo.
           (t article)))
 
+      ;; Associate this article with the current summary buffer.
+      (setq gnus-article-current-summary gnus-summary-buffer)
+
       ;; Take the article from the original article buffer
       ;; and place it in the buffer it's supposed to be in.
       (when (and (get-buffer gnus-article-buffer)
-                ;;(numberp article)
                 (equal (buffer-name (current-buffer))
                        (buffer-name (get-buffer gnus-article-buffer))))
        (save-excursion
          (if (get-buffer gnus-original-article-buffer)
-             (set-buffer (get-buffer gnus-original-article-buffer))
-           (set-buffer (get-buffer-create gnus-original-article-buffer))
+             (set-buffer gnus-original-article-buffer)
+           (set-buffer (gnus-get-buffer-create gnus-original-article-buffer))
            (buffer-disable-undo (current-buffer))
            (setq major-mode 'gnus-original-article-mode)
-           (setq buffer-read-only t)
-           (gnus-add-current-to-buffer-list))
+           (setq buffer-read-only t))
          (let (buffer-read-only)
            (erase-buffer)
            (insert-buffer-substring gnus-article-buffer))
          (setq gnus-original-article (cons group article))))
-    
+
       ;; Update sparse articles.
       (when (and do-update-line
                 (or (numberp article)
                     (stringp article)))
        (let ((buf (current-buffer)))
          (set-buffer gnus-summary-buffer)
-         (gnus-summary-update-article do-update-line)
+         (gnus-summary-update-article do-update-line sparse-header)
          (gnus-summary-goto-subject do-update-line nil t)
          (set-window-point (get-buffer-window (current-buffer) t)
                            (point))
@@ -2369,7 +2557,7 @@ If given a prefix, show the hidden text instead."
 
 (defvar gnus-article-edit-mode-map nil)
 
-(unless gnus-article-edit-mode-map 
+(unless gnus-article-edit-mode-map
   (setq gnus-article-edit-mode-map (copy-keymap text-mode-map))
 
   (gnus-define-keys gnus-article-edit-mode-map
@@ -2386,7 +2574,6 @@ This is an extended text-mode.
 
 \\{gnus-article-edit-mode-map}"
   (interactive)
-  (kill-all-local-variables)
   (setq major-mode 'gnus-article-edit-mode)
   (setq mode-name "Article Edit")
   (use-local-map gnus-article-edit-mode-map)
@@ -2395,7 +2582,7 @@ This is an extended text-mode.
   (setq buffer-read-only nil)
   (buffer-enable-undo)
   (widen)
-  (run-hooks 'text-mode 'gnus-article-edit-mode-hook))
+  (gnus-run-hooks 'text-mode-hook 'gnus-article-edit-mode-hook))
 
 (defun gnus-article-edit (&optional force)
   "Edit the current article.
@@ -2405,27 +2592,51 @@ groups."
   (interactive "P")
   (when (and (not force)
             (gnus-group-read-only-p))
-    (error "The current newsgroup does not support article editing."))
+    (error "The current newsgroup does not support article editing"))
+  (gnus-article-date-original)
   (gnus-article-edit-article
-   `(lambda ()
+   `(lambda (no-highlight)
       (gnus-summary-edit-article-done
        ,(or (mail-header-references gnus-current-headers) "")
-       ,(gnus-group-read-only-p) ,gnus-summary-buffer))))
+       ,(gnus-group-read-only-p) ,gnus-summary-buffer no-highlight))))
 
 (defun gnus-article-edit-article (exit-func)
   "Start editing the contents of the current article buffer."
   (let ((winconf (current-window-configuration)))
     (set-buffer gnus-article-buffer)
     (gnus-article-edit-mode)
-    (set-text-properties (point-min) (point-max) nil)
+    (gnus-article-delete-text-of-type 'annotation)
+    (gnus-set-text-properties (point-min) (point-max) nil)
     (gnus-configure-windows 'edit-article)
     (setq gnus-article-edit-done-function exit-func)
     (setq gnus-prev-winconf winconf)
     (gnus-message 6 "C-c C-c to end edits")))
 
-(defun gnus-article-edit-done ()
+(defun gnus-article-edit-done (&optional arg)
   "Update the article edits and exit."
-  (interactive)
+  (interactive "P")
+  (save-excursion
+    (save-restriction
+      (widen)
+      (goto-char (point-min))
+      (when (search-forward "\n\n" nil 1)
+       (let ((lines (count-lines (point) (point-max)))
+             (length (- (point-max) (point)))
+             (case-fold-search t)
+             (body (copy-marker (point))))
+         (goto-char (point-min))
+         (when (re-search-forward "^content-length:[ \t]\\([0-9]+\\)" body t)
+           (delete-region (match-beginning 1) (match-end 1))
+           (insert (number-to-string length)))
+         (goto-char (point-min))
+         (when (re-search-forward
+                "^x-content-length:[ \t]\\([0-9]+\\)" body t)
+           (delete-region (match-beginning 1) (match-end 1))
+           (insert (number-to-string length)))
+         (goto-char (point-min))
+         (when (re-search-forward "^lines:[ \t]\\([0-9]+\\)" body t)
+           (delete-region (match-beginning 1) (match-end 1))
+           (insert (number-to-string lines)))))))
   (let ((func gnus-article-edit-done-function)
        (buf (current-buffer))
        (start (window-start)))
@@ -2433,7 +2644,7 @@ groups."
     (save-excursion
       (set-buffer buf)
       (let ((buffer-read-only nil))
-       (funcall func)))
+       (funcall func arg)))
     (set-buffer buf)
     (set-window-start (get-buffer-window buf) start)
     (set-window-point (get-buffer-window buf) (point))))
@@ -2452,10 +2663,10 @@ groups."
       (gnus-article-mode)
       ;; The cache and backlog have to be flushed somewhat.
       (when gnus-use-cache
-       (gnus-cache-update-article      
+       (gnus-cache-update-article
         (car gnus-article-current) (cdr gnus-article-current)))
       (when gnus-keep-backlog
-       (gnus-backlog-remove-article 
+       (gnus-backlog-remove-article
         (car gnus-article-current) (cdr gnus-article-current)))
       ;; Flush original article as well.
       (save-excursion
@@ -2469,7 +2680,7 @@ groups."
        (set-window-start (get-buffer-window (current-buffer)) window-start)
        (goto-char p)
        (set-buffer buf)))))
-      
+
 (defun gnus-article-edit-full-stops ()
   "Interactively repair spacing at end of sentences."
   (interactive)
@@ -2479,7 +2690,7 @@ groups."
     (let ((case-fold-search nil))
       (query-replace-regexp "\\([.!?][])}]* \\)\\([[({A-Z]\\)" "\\1 \\2"))))
 
-;;; 
+;;;
 ;;; Article highlights
 ;;;
 
@@ -2492,35 +2703,37 @@ groups."
   :group 'gnus-article-buttons
   :type 'regexp)
 
-(defcustom gnus-button-alist 
-  `(("<\\(url: ?\\)?news:\\([^>\n\t ]*@[^>\n\t ]*\\)>" 0 t
+(defcustom gnus-button-alist
+  `(("<\\(url:[>\n\t ]*?\\)?news:[>\n\t ]*\\([^>\n\t ]*@[^>\n\t ]*\\)>" 0 t
      gnus-button-message-id 2)
-    ("\\bnews:\\([^>\n\t ]*@[^>\n\t ]*+\\)" 0 t gnus-button-message-id 1)
-    ("\\(\\b<\\(url: ?\\)?news:\\(//\\)?\\([^>\n\t ]*\\)>\\)" 1 t
+    ("\\bnews:\\([^>\n\t ]*@[^>\n\t ]*\\)" 0 t gnus-button-message-id 1)
+    ("\\(\\b<\\(url:[>\n\t ]*\\)?news:[>\n\t ]*\\(//\\)?\\([^>\n\t ]*\\)>\\)"
+     1 t
      gnus-button-fetch-group 4)
-    ("\\bnews:\\(//\\)?\\([^>\n\t ]+\\)" 0 t gnus-button-fetch-group 2)
-    ("\\bin\\( +article\\)? +\\(<\\([^\n @<>]+@[^\n @<>]+\\)>\\)" 2 
+    ("\\bnews:\\(//\\)?\\([^'\">\n\t ]+\\)" 0 t gnus-button-fetch-group 2)
+    ("\\bin\\( +article\\| +message\\)? +\\(<\\([^\n @<>]+@[^\n @<>]+\\)>\\)" 2
      t gnus-button-message-id 3)
-    ("\\(<URL: *\\)mailto: *\\([^> \n\t]+\\)>" 0 t gnus-url-mailto 1)
-    ("\\bmailto:\\([^ \n\t]+\\)" 0 t gnus-url-mailto 2)
+    ("\\(<URL: *\\)mailto: *\\([^> \n\t]+\\)>" 0 t gnus-url-mailto 2)
+    ("mailto:\\([a-zA-Z.-@_+0-9%]+\\)" 0 t gnus-url-mailto 1)
+    ("\\bmailto:\\([^ \n\t]+\\)" 0 t gnus-url-mailto 1)
     ;; This is how URLs _should_ be embedded in text...
     ("<URL: *\\([^>]*\\)>" 0 t gnus-button-embedded-url 1)
     ;; Raw URLs.
     (,gnus-button-url-regexp 0 t gnus-button-url 0))
-  "Alist of regexps matching buttons in article bodies.
+  "*Alist of regexps matching buttons in article bodies.
 
 Each entry has the form (REGEXP BUTTON FORM CALLBACK PAR...), where
 REGEXP: is the string matching text around the button,
 BUTTON: is the number of the regexp grouping actually matching the button,
 FORM: is a lisp expression which must eval to true for the button to
-be added, 
+be added,
 CALLBACK: is the function to call when the user push this button, and each
 PAR: is a number of a regexp grouping whose text will be passed to CALLBACK.
 
 CALLBACK can also be a variable, in that case the value of that
 variable it the real callback function."
   :group 'gnus-article-buttons
-  :type '(repeat (list regexp 
+  :type '(repeat (list regexp
                       (integer :tag "Button")
                       (sexp :tag "Form")
                       (function :tag "Callback")
@@ -2528,17 +2741,18 @@ variable it the real callback function."
                               :inline t
                               (integer :tag "Regexp group")))))
 
-(defcustom gnus-header-button-alist 
+(defcustom gnus-header-button-alist
   `(("^\\(References\\|Message-I[Dd]\\):" "<[^>]+>"
      0 t gnus-button-message-id 0)
     ("^\\(From\\|Reply-To\\):" ": *\\(.+\\)$" 1 t gnus-button-reply 1)
-    ("^\\(Cc\\|To\\):" "[^ \t\n<>,()\"]+@[^ \t\n<>,()\"]+" 
+    ("^\\(Cc\\|To\\):" "[^ \t\n<>,()\"]+@[^ \t\n<>,()\"]+"
      0 t gnus-button-mailto 0)
     ("^X-[Uu][Rr][Ll]:" ,gnus-button-url-regexp 0 t gnus-button-url 0)
+    ("^Subject:" ,gnus-button-url-regexp 0 t gnus-button-url 0)
     ("^[^:]+:" ,gnus-button-url-regexp 0 t gnus-button-url 0)
     ("^[^:]+:" "\\(<\\(url: \\)?news:\\([^>\n ]*\\)>\\)" 1 t
      gnus-button-message-id 3))
-  "Alist of headers and regexps to match buttons in article heads.
+  "*Alist of headers and regexps to match buttons in article heads.
 
 This alist is very similar to `gnus-button-alist', except that each
 alist has an additional HEADER element first in each entry:
@@ -2550,7 +2764,7 @@ HEADER is a regexp to match a header.  For a fuller explanation, see
   :group 'gnus-article-buttons
   :group 'gnus-article-headers
   :type '(repeat (list (regexp :tag "Header")
-                      regexp 
+                      regexp
                       (integer :tag "Button")
                       (sexp :tag "Form")
                       (function :tag "Callback")
@@ -2576,6 +2790,7 @@ call it with the value of the `gnus-data' text property."
   (let* ((pos (posn-point (event-start event)))
          (data (get-text-property pos 'gnus-data))
         (fun (get-text-property pos 'gnus-callback)))
+    (goto-char pos)
     (when fun
       (funcall fun data))))
 
@@ -2626,7 +2841,7 @@ If N is negative, move backward instead."
 (defun gnus-article-highlight (&optional force)
   "Highlight current article.
 This function calls `gnus-article-highlight-headers',
-`gnus-article-highlight-citation', 
+`gnus-article-highlight-citation',
 `gnus-article-highlight-signature', and `gnus-article-add-buttons' to
 do the highlighting.  See the documentation for those functions."
   (interactive (list 'force))
@@ -2657,40 +2872,38 @@ do the highlighting.  See the documentation for those functions."
            (case-fold-search t)
            (inhibit-point-motion-hooks t)
            entry regexp header-face field-face from hpoints fpoints)
-       (goto-char (point-min))
-       (when (search-forward "\n\n" nil t)
-         (narrow-to-region (1- (point)) (point-min))
-         (while (setq entry (pop alist))
-           (goto-char (point-min))
-           (setq regexp (concat "^\\("
-                                (if (string-equal "" (nth 0 entry))
-                                    "[^\t ]"
-                                  (nth 0 entry))
-                                "\\)")
-                 header-face (nth 1 entry)
-                 field-face (nth 2 entry))
-           (while (and (re-search-forward regexp nil t)
-                       (not (eobp)))
-             (beginning-of-line)
-             (setq from (point))
-             (unless (search-forward ":" nil t)
-               (forward-char 1))
-             (when (and header-face
-                        (not (memq (point) hpoints)))
-               (push (point) hpoints)
-               (gnus-put-text-property from (point) 'face header-face))
-             (when (and field-face
-                        (not (memq (setq from (point)) fpoints)))
-               (push from fpoints)
-               (if (re-search-forward "^[^ \t]" nil t)
-                   (forward-char -2)
-                 (goto-char (point-max)))
-               (gnus-put-text-property from (point) 'face field-face)))))))))
+       (message-narrow-to-head)
+       (while (setq entry (pop alist))
+         (goto-char (point-min))
+         (setq regexp (concat "^\\("
+                              (if (string-equal "" (nth 0 entry))
+                                  "[^\t ]"
+                                (nth 0 entry))
+                              "\\)")
+               header-face (nth 1 entry)
+               field-face (nth 2 entry))
+         (while (and (re-search-forward regexp nil t)
+                     (not (eobp)))
+           (beginning-of-line)
+           (setq from (point))
+           (unless (search-forward ":" nil t)
+             (forward-char 1))
+           (when (and header-face
+                      (not (memq (point) hpoints)))
+             (push (point) hpoints)
+             (gnus-put-text-property from (point) 'face header-face))
+           (when (and field-face
+                      (not (memq (setq from (point)) fpoints)))
+             (push from fpoints)
+             (if (re-search-forward "^[^ \t]" nil t)
+                 (forward-char -2)
+               (goto-char (point-max)))
+             (gnus-put-text-property from (point) 'face field-face))))))))
 
 (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 `gnus-signature-face'."
   (interactive)
   (save-excursion
     (set-buffer gnus-article-buffer)
@@ -2719,22 +2932,22 @@ specified by `gnus-button-alist'."
   (interactive (list 'force))
   (save-excursion
     (set-buffer gnus-article-buffer)
-    ;; Remove all old markers.
-    (let (marker entry)
-      (while (setq marker (pop gnus-button-marker-list))
-       (goto-char marker)
-       (when (setq entry (gnus-button-entry))
-         (put-text-property (match-beginning (nth 1 entry))
-                            (match-end (nth 1 entry))
-                            'gnus-callback nil))
-       (set-marker marker nil)))
     (let ((buffer-read-only nil)
          (inhibit-point-motion-hooks t)
          (case-fold-search t)
          (alist gnus-button-alist)
          beg entry regexp)
-      (goto-char (point-min))
+      ;; Remove all old markers.
+      (let (marker entry)
+       (while (setq marker (pop gnus-button-marker-list))
+         (goto-char marker)
+         (when (setq entry (gnus-button-entry))
+           (put-text-property (match-beginning (nth 1 entry))
+                              (match-end (nth 1 entry))
+                              'gnus-callback nil))
+         (set-marker marker nil)))
       ;; We skip the headers.
+      (goto-char (point-min))
       (unless (search-forward "\n\n" nil t)
        (goto-char (point-max)))
       (setq beg (point))
@@ -2745,14 +2958,14 @@ specified by `gnus-button-alist'."
          (let* ((start (and entry (match-beginning (nth 1 entry))))
                 (end (and entry (match-end (nth 1 entry))))
                 (from (match-beginning 0)))
-           (when (and (or (eq t (nth 1 entry))
-                          (eval (nth 1 entry)))
+           (when (and (or (eq t (nth 2 entry))
+                          (eval (nth 2 entry)))
                       (not (gnus-button-in-region-p
                             start end 'gnus-callback)))
              ;; That optional form returned non-nil, so we add the
              ;; button.
-             (gnus-article-add-button 
-              start end 'gnus-button-push 
+             (gnus-article-add-button
+              start end 'gnus-button-push
               (car (push (set-marker (make-marker) from)
                          gnus-button-marker-list))))))))))
 
@@ -2788,7 +3001,7 @@ specified by `gnus-button-alist'."
                   (form (nth 2 entry)))
              (goto-char (match-end 0))
              (when (eval form)
-               (gnus-article-add-button 
+               (gnus-article-add-button
                 start end (nth 3 entry)
                 (buffer-substring (match-beginning (nth 4 entry))
                                   (match-end (nth 4 entry)))))))
@@ -2802,7 +3015,7 @@ specified by `gnus-button-alist'."
   (when gnus-article-button-face
     (gnus-overlay-put (gnus-make-overlay from to)
                      'face gnus-article-button-face))
-  (gnus-add-text-properties 
+  (gnus-add-text-properties
    from to
    (nconc (and gnus-article-mouse-face
               (list gnus-mouse-face-prop gnus-article-mouse-face))
@@ -2811,6 +3024,11 @@ specified by `gnus-button-alist'."
 
 ;;; Internal functions:
 
+(defun gnus-article-set-globals ()
+  (save-excursion
+    (set-buffer gnus-summary-buffer)
+    (gnus-set-global-variables)))
+
 (defun gnus-signature-toggle (end)
   (save-excursion
     (set-buffer gnus-article-buffer)
@@ -2877,17 +3095,9 @@ specified by `gnus-button-alist'."
                                     (match-string 3 address)
                                   "nntp")))))))
 
-(defun gnus-split-string (string pattern)
-  "Return a list of substrings of STRING which are separated by PATTERN."
-  (let (parts (start 0))
-    (while (string-match pattern string start)
-      (setq parts (cons (substring string start (match-beginning 0)) parts)
-           start (match-end 0)))
-    (nreverse (cons (substring string start) parts))))
 (defun gnus-url-parse-query-string (query &optional downcase)
   (let (retval pairs cur key val)
-    (setq pairs (gnus-split-string query "&"))
+    (setq pairs (split-string query "&"))
     (while pairs
       (setq cur (car pairs)
             pairs (cdr pairs))
@@ -2902,14 +3112,14 @@ specified by `gnus-button-alist'."
             (setcdr cur (cons val (cdr cur)))
           (setq retval (cons (list key val) retval)))))
     retval))
+
 (defun gnus-url-unhex (x)
   (if (> x ?9)
       (if (>= x ?a)
           (+ 10 (- x ?a))
         (+ 10 (- x ?A)))
     (- x ?0)))
+
 (defun gnus-url-unhex-string (str &optional allow-newlines)
   "Remove %XXX embedded spaces, etc in a url.
 If optional second argument ALLOW-NEWLINES is non-nil, then allow the
@@ -2923,7 +3133,7 @@ forbidden in URL encoding."
              (ch1 (gnus-url-unhex (elt str (+ start 1))))
              (code (+ (* 16 ch1)
                       (gnus-url-unhex (elt str (+ start 2))))))
-        (setq tmp (concat 
+        (setq tmp (concat
                    tmp (substring str 0 start)
                    (cond
                     (allow-newlines
@@ -2934,12 +3144,12 @@ forbidden in URL encoding."
               str (substring str (match-end 0)))))
     (setq tmp (concat tmp str))
     tmp))
+
 (defun gnus-url-mailto (url)
   ;; Send mail to someone
   (when (string-match "mailto:/*\\(.*\\)" url)
     (setq url (substring url (match-beginning 1) nil)))
-  (let (to args source-url subject func)
+  (let (to args subject func)
     (if (string-match (regexp-quote "?") url)
         (setq to (gnus-url-unhex-string (substring url 0 (match-beginning 0)))
               args (gnus-url-parse-query-string
@@ -2970,11 +3180,17 @@ forbidden in URL encoding."
 
 (defun gnus-button-url (address)
   "Browse ADDRESS."
-  (funcall browse-url-browser-function address))
+  ;; In Emacs 20, `browse-url-browser-function' may be an alist.
+  (if (listp browse-url-browser-function)
+      (browse-url address)
+    (funcall browse-url-browser-function address)))
 
 (defun gnus-button-embedded-url (address)
   "Browse ADDRESS."
-  (funcall browse-url-browser-function (gnus-strip-whitespace address)))
+  ;; In Emacs 20, `browse-url-browser-function' may be an alist.
+  (if (listp browse-url-browser-function)
+      (browse-url (gnus-strip-whitespace address))
+    (funcall browse-url-browser-function (gnus-strip-whitespace address))))
 
 ;;; Next/prev buttons in the article buffer.
 
@@ -2989,10 +3205,11 @@ forbidden in URL encoding."
 
 (defun gnus-insert-prev-page-button ()
   (let ((buffer-read-only nil))
-    (gnus-eval-format 
+    (gnus-eval-format
      gnus-prev-page-line-format nil
      `(gnus-prev t local-map ,gnus-prev-page-map
-                gnus-callback gnus-article-button-prev-page))))
+                gnus-callback gnus-article-button-prev-page
+                gnus-type annotation))))
 
 (defvar gnus-next-page-map nil)
 (unless gnus-next-page-map
@@ -3020,9 +3237,10 @@ forbidden in URL encoding."
 (defun gnus-insert-next-page-button ()
   (let ((buffer-read-only nil))
     (gnus-eval-format gnus-next-page-line-format nil
-                     `(gnus-next t local-map ,gnus-next-page-map
-                                 gnus-callback 
-                                 gnus-article-button-next-page))))
+                     `(gnus-next
+                       t local-map ,gnus-next-page-map
+                       gnus-callback gnus-article-button-next-page
+                       gnus-type annotation))))
 
 (defun gnus-article-button-next-page (arg)
   "Go to the next page."
@@ -3038,7 +3256,7 @@ forbidden in URL encoding."
   (let ((win (selected-window)))
     (select-window (get-buffer-window gnus-article-buffer t))
     (gnus-article-prev-page)
-    (select-window win))) 
+    (select-window win)))
 
 (gnus-ems-redefine)