*** empty log message ***
[gnus] / lisp / gnus-art.el
index f107e84..6d1bc02 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)
+(require 'mail-parse)
+(require 'mm-decode)
+(require 'mm-view)
+(require 'wid-edit)
 
 (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
@@ -102,8 +124,8 @@ If `gnus-visible-headers' is non-nil, this variable will be ignored."
   :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.
+  "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)
@@ -117,7 +139,7 @@ If this variable is non-nil, `gnus-ignored-headers' will be ignored."
 (defcustom gnus-sorted-header-list
   '("^From:" "^Subject:" "^Summary:" "^Keywords:" "^Newsgroups:"
     "^Followup-To:" "^To:" "^Cc:" "^Date:" "^Organization:")
-  "This variable is a list of regular expressions.
+  "*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 +149,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,7 +175,10 @@ 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)
@@ -161,7 +188,7 @@ regexp.  If it matches, the text in question is not a signature."
 
 (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,7 +196,7 @@ 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
@@ -189,9 +216,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)
@@ -240,14 +267,16 @@ 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-save-all-headers t
@@ -266,7 +295,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.
@@ -274,7 +303,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.
@@ -325,7 +354,7 @@ LAST-FILE."
 (defcustom gnus-split-methods
   '((gnus-article-archive-name)
     (gnus-article-nndoc-name))
-  "Variable used to suggest where articles are to be saved.
+  "*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:
@@ -345,26 +374,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)))
-
-(defcustom gnus-strict-mime t
-  "*If nil, MIME-decode even if there is no Mime-Version header."
-  :group 'gnus-article-mime
-  :type 'boolean)
-
-(defcustom gnus-show-mime-method 'metamail-buffer
-  "Function to process a MIME message.
-The function is called from the article buffer."
-  :group 'gnus-article-mime
-  :type 'function)
-
-(defcustom gnus-decode-encoded-word-method 'gnus-article-de-quoted-unreadable
-  "*Function to decode MIME encoded words.
-The function is called from the article buffer."
-  :group 'gnus-article-mime
-  :type 'function)
+  :type '(repeat (choice (list :value (fun) function)
+                        (cons :value ("" "") regexp (repeat string))
+                        (sexp :value nil))))
 
 (defcustom gnus-page-delimiter "^\^L"
   "*Regexp describing what to use as article page delimiters.
@@ -373,7 +385,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
@@ -433,12 +445,12 @@ Obsolete; use the face `gnus-signature-face' for customizations instead."
 (defface gnus-header-from-face
   '((((class color)
       (background dark))
-     (:foreground "spring green" :bold t))
+     (:foreground "spring green"))
     (((class color)
       (background light))
-     (:foreground "red3" :bold t))
+     (:foreground "red3"))
     (t
-     (:bold t :italic t)))
+     (:italic t)))
   "Face used for displaying from headers."
   :group 'gnus-article-headers
   :group 'gnus-article-highlight)
@@ -446,10 +458,10 @@ Obsolete; use the face `gnus-signature-face' for customizations instead."
 (defface gnus-header-subject-face
   '((((class color)
       (background dark))
-     (:foreground "SeaGreen3" :bold t))
+     (:foreground "SeaGreen3"))
     (((class color)
       (background light))
-     (:foreground "red4" :bold t))
+     (:foreground "red4"))
     (t
      (:bold t :italic t)))
   "Face used for displaying subject headers."
@@ -459,12 +471,12 @@ Obsolete; use the face `gnus-signature-face' for customizations instead."
 (defface gnus-header-newsgroups-face
   '((((class color)
       (background dark))
-     (:foreground "yellow" :bold t :italic t))
+     (:foreground "yellow" :italic t))
     (((class color)
       (background light))
-     (:foreground "MidnightBlue" :bold t :italic t))
+     (:foreground "MidnightBlue" :italic t))
     (t
-     (:bold t :italic t)))
+     (:italic t)))
   "Face used for displaying newsgroups headers."
   :group 'gnus-article-headers
   :group 'gnus-article-highlight)
@@ -499,7 +511,7 @@ Obsolete; use the face `gnus-signature-face' for customizations instead."
     ("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).
 
@@ -520,8 +532,25 @@ displayed by the first non-nil matching CONTENT face."
                               (item :tag "skip" nil)
                               (face :value default)))))
 
+(defcustom gnus-article-decode-hook
+  '(article-decode-charset article-decode-encoded-words)
+  "*Hook run to decode charsets in articles."
+  :group 'gnus-article-headers
+  :type 'hook)
+
+(defcustom gnus-display-mime-function 'gnus-display-mime
+  "Function to display MIME articles."
+  :group 'gnus-article-headers
+  :type 'function)
+
+(defvar gnus-decode-header-function 'mail-decode-encoded-word-region
+  "Function used to decode headers.")
+
 ;;; 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)
@@ -534,8 +563,8 @@ 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)
 
@@ -562,7 +591,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)
@@ -583,7 +612,8 @@ Initialized from `text-mode-syntax-table.")
     (let ((b (point-min)))
       (while (setq b (text-property-any b (point-max) 'article-type type))
        (delete-region
-        b (text-property-not-all b (point-max) 'article-type type))))))
+        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."
@@ -591,7 +621,8 @@ Initialized from `text-mode-syntax-table.")
     (let ((b (point-min)))
       (while (setq b (text-property-any b (point-max) 'invisible t))
        (delete-region
-        b (text-property-not-all b (point-max) 'invisible t))))))
+        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."
@@ -613,6 +644,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)
@@ -621,6 +653,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)))
@@ -637,7 +670,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))
@@ -736,9 +769,27 @@ always hide."
             ((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
@@ -753,7 +804,29 @@ always hide."
           (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\224" "`'\"\""))
+
+(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)
@@ -818,7 +891,9 @@ always hide."
        (point)
        (progn
         (while (and (not (bobp))
-                    (looking-at "^[ \t]*$"))
+                    (looking-at "^[ \t]*$")
+                    (not (gnus-annotation-in-region-p
+                          (point) (gnus-point-at-eol))))
           (forward-line -1))
         (forward-line 1)
         (point))))))
@@ -831,13 +906,14 @@ 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))
        (while (and gnus-article-x-face-command
+                   (not last)
                    (or force
                        ;; Check whether this face is censored.
                        (not gnus-article-x-face-too-ugly)
@@ -846,6 +922,12 @@ always hide."
                                                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.
          (save-excursion
            (let ((beg (point))
@@ -866,72 +948,73 @@ always hide."
                  (process-send-region "article-x-face" beg end)
                  (process-send-eof "article-x-face"))))))))))
 
-(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)
+(defun 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)
+      (mail-decode-encoded-word-region (point-min) (point-max)))))
+
+(defun 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
     (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)
+      (let* ((inhibit-point-motion-hooks t)
+            (case-fold-search t)
+            (ct (message-fetch-field "Content-Type" t))
+            (cte (message-fetch-field "Content-Transfer-Encoding" t))
+            (ctl (and ct (condition-case ()
+                             (mail-header-parse-content-type ct)
+                           (error nil))))
+            (charset (cond
+                      (prompt
+                       (mm-read-coding-system "Charset to decode: "))
+                      (ctl
+                       (mail-content-type-get ctl 'charset))
+                      (gnus-newsgroup-name
+                       (gnus-group-find-parameter
+                        gnus-newsgroup-name 'charset))))
+            buffer-read-only)
+       (goto-char (point-max))
+       (widen)
+       (forward-line 1)
+       (narrow-to-region (point) (point-max))
+       (when (or (not ct)
+                 (equal (car ctl) "text/plain"))
+         (mm-decode-body
+          charset (and cte (intern (downcase
+                                    (gnus-strip-whitespace cte))))))))))
+
+(defun article-decode-encoded-words ()
+  "Remove encoded-word encoding from headers."
+  (let ((inhibit-point-motion-hooks t)
+       (buffer-read-only nil))
+    (save-restriction
+      (message-narrow-to-head)
+      (funcall gnus-decode-header-function (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.
@@ -946,29 +1029,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)))
-      (run-hooks 'gnus-article-hide-pgp-hook))))
+         (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.
@@ -1033,12 +1114,16 @@ always hide."
       (goto-char (point-min))
       (search-forward "\n\n" nil t)
       (while (re-search-forward "^[ \t]+$" nil t)
-       (replace-match "" nil t))
+       (unless (gnus-annotation-in-region-p
+                (match-beginning 0) (match-end 0))
+         (replace-match "" nil t)))
       ;; Then replace multiple empty lines with a single empty line.
       (goto-char (point-min))
       (search-forward "\n\n" nil t)
       (while (re-search-forward "\n\n\n+" nil t)
-       (replace-match "\n\n" t t)))))
+       (unless (gnus-annotation-in-region-p
+                (match-beginning 0) (match-end 0))
+         (replace-match "\n\n" t t))))))
 
 (defun article-strip-leading-space ()
   "Remove all white space from the beginning of the lines in the article."
@@ -1058,42 +1143,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.
@@ -1112,7 +1209,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."
@@ -1120,18 +1219,19 @@ 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
-         (w3-parse-buffer (current-buffer))
-         (setq buf (buffer-string))))
+         (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 buf))
@@ -1167,8 +1267,7 @@ means show, 0 means toggle."
 
 (defun gnus-article-hidden-text-p (type)
   "Say whether the current buffer contains hidden text of type TYPE."
-  (let ((start (point-min))
-       (pos (text-property-any (point-min) (point-max) 'article-type type)))
+  (let ((pos (text-property-any (point-min) (point-max) 'article-type type)))
     (while (and pos
                (not (get-text-property pos 'invisible)))
       (setq pos
@@ -1218,7 +1317,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
@@ -1230,104 +1329,111 @@ 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."
-  (cond
-   ;; Convert to the local timezone.  We have to slap a
-   ;; `condition-case' round the calls to the timezone
-   ;; 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"))
-   ;; Convert to Universal Time.
-   ((eq type 'ut)
-    (concat "Date: "
-           (condition-case ()
-               (timezone-make-date-arpa-standard date nil "UT")
-             (error date))
-           "\n"))
-   ;; Get the original date from the article.
-   ((eq type 'original)
-    (concat "Date: " date "\n"))
-   ;; Let the user define the format.
-   ((eq type 'user)
-    (concat
-     "Date: "
-     (format-time-string gnus-article-time-format
-                        (ignore-errors
-                          (gnus-encode-date
-                           (timezone-make-date-arpa-standard
-                            date nil "UT"))))
-     "\n"))
-   ;; 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-sec (and real-time
-                         (+ (* (float (car real-time)) 65536)
-                            (cadr real-time))))
-          (sec (and real-time (abs real-sec)))
-          num prev)
-      (cond
-       ((null real-time)
-       "X-Sent: Unknown\n")
-       ((zerop sec)
-       "X-Sent: Now\n")
-       (t
+  (let ((time (condition-case ()
+                 (date-to-time date)
+               (error '(0 0)))))
+    (cond
+     ;; Convert to the local timezone.  We have to slap a
+     ;; `condition-case' round the calls to the timezone
+     ;; functions since they aren't particularly resistant to
+     ;; buggy dates.
+     ((eq type 'local)
+      (let ((tz (car (current-time-zone))))
+       (format "Date: %s %s%04d" (current-time-string time)
+               (if (> tz 0) "+" "-") (abs (/ tz 36)))))
+     ;; Convert to Universal Time.
+     ((eq type 'ut)
+      (concat "Date: "
+             (current-time-string
+              (let* ((e (parse-time-string date))
+                    (tm (apply 'encode-time e))
+                    (ms (car tm))
+                    (ls (- (cadr tm) (car (current-time-zone)))))
+                (cond ((< ls 0) (list (1- ms) (+ ls 65536)))
+                      ((> ls 65535) (list (1+ ms) (- ls 65536)))
+                      (t (list ms ls)))))
+             " UT"))
+     ;; Get the original date from the article.
+     ((eq type 'original)
+      (concat "Date: " (if (string-match "\n+$" date)
+                          (substring date 0 (match-beginning 0))
+                        date)))
+     ;; Let the user define the format.
+     ((eq type 'user)
+      (if (gnus-functionp gnus-article-time-format)
+         (funcall gnus-article-time-format time)
        (concat
-        "X-Sent: "
-        ;; This is a bit convoluted, but basically we go
-        ;; through the time units for years, weeks, etc,
-        ;; and divide things to see whether that results
-        ;; in positive answers.
-        (mapconcat
-         (lambda (unit)
-           (if (zerop (setq num (ffloor (/ sec (cdr unit)))))
-               ;; The (remaining) seconds are too few to
-               ;; be divided into this time unit.
-               ""
-             ;; It's big enough, so we output it.
-             (setq sec (- sec (* num (cdr unit))))
-             (prog1
-                 (concat (if prev ", " "") (int-to-string
-                                            (floor num))
-                         " " (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"))))))
-   (t
-    (error "Unknown conversion type: %s" type))))
+        "Date: "
+        (format-time-string gnus-article-time-format time))))
+     ;; ISO 8601.
+     ((eq type 'iso8601)
+      (concat
+       "Date: "
+       (format-time-string "%Y%M%DT%h%m%s" time)))
+     ;; 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 (subtract-time now time))
+            (real-sec (and real-time
+                           (+ (* (float (car real-time)) 65536)
+                              (cadr real-time))))
+            (sec (and real-time (abs real-sec)))
+            num prev)
+       (cond
+        ((null real-time)
+         "X-Sent: Unknown")
+        ((zerop sec)
+         "X-Sent: Now")
+        (t
+         (concat
+          "X-Sent: "
+          ;; This is a bit convoluted, but basically we go
+          ;; through the time units for years, weeks, etc,
+          ;; and divide things to see whether that results
+          ;; in positive answers.
+          (mapconcat
+           (lambda (unit)
+             (if (zerop (setq num (ffloor (/ sec (cdr unit)))))
+                 ;; The (remaining) seconds are too few to
+                 ;; be divided into this time unit.
+                 ""
+               ;; It's big enough, so we output it.
+               (setq sec (- sec (* num (cdr unit))))
+               (prog1
+                   (concat (if prev ", " "") (int-to-string
+                                              (floor num))
+                           " " (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"
+            " in the future"))))))
+     (t
+      (error "Unknown conversion type: %s" type)))))
 
 (defun article-date-local (&optional highlight)
   "Convert the current article date to the local timezone."
@@ -1346,12 +1452,47 @@ 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 (and (gnus-buffer-live-p gnus-article-buffer)
+                  (get-buffer-window 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)
@@ -1400,12 +1541,14 @@ 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
+      ;; `gnus-save-article-buffer' (or so they think), but we
       ;; bind that variable to our save-buffer.
       (set-buffer gnus-article-buffer)
       (let* ((gnus-save-article-buffer save-buffer)
@@ -1417,7 +1560,7 @@ 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)
+       (set-buffer gnus-article-current-summary)
        (funcall gnus-default-article-saver filename)))))
 
 (defun gnus-read-save-file-name (prompt &optional filename
@@ -1514,8 +1657,6 @@ This format is defined by the `gnus-article-time-format' variable."
   "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)
   (setq filename (gnus-read-save-file-name
                  "Save %s in rmail file:" filename
                  gnus-rmail-save-name gnus-newsgroup-name
@@ -1524,14 +1665,13 @@ Directory to save to is default to `gnus-article-save-directory'."
     (save-excursion
       (save-restriction
        (widen)
-       (gnus-output-to-rmail filename)))))
+       (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)
   (setq filename (gnus-read-save-file-name
                  "Save %s in Unix mail file:" filename
                  gnus-mail-save-name gnus-newsgroup-name
@@ -1542,15 +1682,14 @@ Directory to save to is default to `gnus-article-save-directory'."
        (widen)
        (if (and (file-readable-p filename)
                 (mail-file-babyl-p filename))
-           (gnus-output-to-rmail filename t)
-         (gnus-output-to-mail 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)
   (setq filename (gnus-read-save-file-name
                  "Save %s in file:" filename
                  gnus-file-save-name gnus-newsgroup-name
@@ -1562,21 +1701,19 @@ Directory to save to is default to `gnus-article-save-directory'."
        (when (and overwrite
                   (file-exists-p filename))
          (delete-file filename))
-       (gnus-output-to-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)
   (setq filename (gnus-read-save-file-name
                  "Save %s body in file:" filename
                  gnus-file-save-name gnus-newsgroup-name
@@ -1588,12 +1725,11 @@ The directory to save in defaults to `gnus-article-save-directory'."
        (goto-char (point-min))
        (when (search-forward "\n\n" nil t)
          (narrow-to-region (point) (point-max)))
-       (gnus-output-to-file filename)))))
+       (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)
@@ -1717,12 +1853,18 @@ If variable `gnus-use-long-file-name' is non-nil, it is
      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-decode-mime-words
+     article-decode-charset
+     article-decode-encoded-words
      article-date-user
      article-date-lapsed
      article-emphasize
+     article-treat-dumbquotes
      (article-show-all . gnus-article-show-all-headers))))
 \f
 ;;;
@@ -1731,6 +1873,8 @@ If variable `gnus-use-long-file-name' is non-nil, it is
 
 (put 'gnus-article-mode 'mode-class 'special)
 
+(set-keymap-parent gnus-article-mode-map widget-keymap)
+
 (gnus-define-keys gnus-article-mode-map
   " " gnus-article-goto-next-page
   "\177" gnus-article-goto-prev-page
@@ -1740,16 +1884,14 @@ If variable `gnus-use-long-file-name' is non-nil, it is
   "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
 
+  gnus-mouse-2 'widget-button-click
+  
   "\C-d" gnus-article-read-summary-keys
   "\M-*" gnus-article-read-summary-keys
   "\M-#" gnus-article-read-summary-keys
@@ -1769,7 +1911,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 ""
@@ -1781,16 +1924,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.
@@ -1810,23 +1950,24 @@ 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)
   (make-local-variable 'minor-mode-alist)
-  (unless (assq 'gnus-show-mime minor-mode-alist)
-    (push (list 'gnus-show-mime " MIME") minor-mode-alist))
   (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-page-broken) nil)
-  (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)
+  (make-local-variable 'gnus-article-mime-handles)
+  (make-local-variable 'gnus-article-decoded-p)
   (gnus-set-default-directory)
-  (buffer-disable-undo (current-buffer))
+  (buffer-disable-undo)
   (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."
@@ -1847,23 +1988,20 @@ commands:
        (gnus-set-global-variables)))
     ;; Init original article buffer.
     (save-excursion
-      (set-buffer (get-buffer-create gnus-original-article-buffer))
-      (buffer-disable-undo (current-buffer))
+      (set-buffer (gnus-get-buffer-create gnus-original-article-buffer))
+      (mm-enable-multibyte)
       (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))
+         (buffer-disable-undo)
          (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)))))
@@ -1893,14 +2031,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
@@ -1921,17 +2054,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)
@@ -1949,9 +2086,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)
@@ -1959,40 +2095,26 @@ 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)
-               ;; Decode MIME message.
-               (when gnus-show-mime
-                 (if (or (not gnus-strict-mime)
-                         (gnus-fetch-field "Mime-Version"))
-                     (funcall gnus-show-mime-method)
-                   (funcall gnus-decode-encoded-word-method)))
-               ;; Perform the article display hooks.
-               (run-hooks 'gnus-article-display-hook))
+             (gnus-article-prepare-display)
              ;; Do page break.
              (goto-char (point-min))
              (setq gnus-page-broken
@@ -2002,8 +2124,189 @@ If ALL-HEADERS is non-nil, no headers are hidden."
            (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-prepare-display ()
+  "Make the current buffer look like a nice article."
+  ;; Hooks for getting information from the article.
+  ;; This hook must be called before being narrowed.
+  (let ((gnus-article-buffer (current-buffer))
+       buffer-read-only)
+    (unless (eq major-mode 'gnus-article-mode)
+      (gnus-article-mode))
+    (gnus-run-hooks 'gnus-tmp-internal-hook)
+    (gnus-run-hooks 'gnus-article-prepare-hook)
+    (when gnus-display-mime-function
+      (let ((url-standalone-mode (not gnus-plugged)))
+       (funcall gnus-display-mime-function)))
+    ;; Perform the article display hooks.
+    (gnus-run-hooks 'gnus-article-display-hook)))
+
+;;;
+;;; Gnus MIME viewing functions
+;;;
+
+(defvar gnus-mime-button-line-format "%{%([%t%d%n]%)%}\n"
+  "The following specs can be used:
+%t  The MIME type
+%n  The `name' parameter
+%d  The description, if any
+%l  The length of the encoded part")
+
+(defvar gnus-mime-button-line-format-alist
+  '((?t gnus-tmp-type ?s)
+    (?n gnus-tmp-name ?s)
+    (?d gnus-tmp-description ?s)
+    (?l gnus-tmp-length ?d)))
+
+(defvar gnus-mime-button-map nil)
+(unless gnus-mime-button-map
+  (setq gnus-mime-button-map (copy-keymap gnus-article-mode-map))
+  (define-key gnus-mime-button-map gnus-mouse-2 'gnus-article-push-button)
+  (define-key gnus-mime-button-map "\r" 'gnus-article-press-button)
+  (define-key gnus-mime-button-map "\M-\r" 'gnus-mime-view-part)
+  (define-key gnus-mime-button-map "v" 'gnus-mime-view-part)
+  (define-key gnus-mime-button-map "o" 'gnus-mime-save-part)
+  (define-key gnus-mime-button-map "c" 'gnus-mime-copy-part)
+  (define-key gnus-mime-button-map "i" 'gnus-mime-inline-part)
+  (define-key gnus-mime-button-map "|" 'gnus-mime-pipe-part))
+
+(defun gnus-mime-save-part ()
+  "Save the MIME part under point."
+  (interactive)
+  (let ((data (get-text-property (point) 'gnus-data)))
+    (mm-save-part data)))
+
+(defun gnus-mime-pipe-part ()
+  "Pipe the MIME part under point to a process."
+  (interactive)
+  (let ((data (get-text-property (point) 'gnus-data)))
+    (mm-pipe-part data)))
+
+(defun gnus-mime-view-part ()
+  "Interactively choose a view method for the MIME part under point."
+  (interactive)
+  (let ((data (get-text-property (point) 'gnus-data))
+       (url-standalone-mode (not gnus-plugged)))
+    (mm-interactively-view-part data)))
+
+(defun gnus-mime-copy-part ()
+  "Put the the MIME part under point into a new buffer."
+  (interactive)
+  (let* ((data (get-text-property (point) 'gnus-data))
+        (contents (mm-get-part data)))
+    (switch-to-buffer (generate-new-buffer "*decoded*"))
+    (insert contents)
+    (goto-char (point-min))))
+
+(defun gnus-mime-inline-part ()
+  "Insert the MIME part under point into the current buffer."
+  (interactive)
+  (let* ((data (get-text-property (point) 'gnus-data))
+        (contents (mm-get-part data))
+        (url-standalone-mode (not gnus-plugged))
+        (b (point))
+        buffer-read-only)
+    (if (mm-handle-undisplayer data)
+       (mm-remove-part data)
+      (forward-line 2)
+      (mm-insert-inline data contents)
+      (goto-char b))))
+
+(defun gnus-insert-mime-button (handle)
+  (let ((gnus-tmp-name (mail-content-type-get (mm-handle-type handle) 'name))
+       (gnus-tmp-type (car (mm-handle-type handle)))
+       (gnus-tmp-description (mm-handle-description handle))
+       (gnus-tmp-length (save-excursion
+                          (set-buffer (mm-handle-buffer handle))
+                          (buffer-size)))
+       b e)
+    (setq gnus-tmp-name
+         (if gnus-tmp-name
+             (concat " (" gnus-tmp-name ")")
+           ""))
+    (setq gnus-tmp-description
+         (if gnus-tmp-description
+             (concat " (" gnus-tmp-description ")")
+           ""))
+    (setq b (point))
+    (gnus-eval-format
+     gnus-mime-button-line-format gnus-mime-button-line-format-alist
+     `(local-map ,gnus-mime-button-map
+                keymap ,gnus-mime-button-map
+                gnus-callback mm-display-part
+                gnus-data ,handle))
+    (setq e (point))
+    (widget-convert-button 'link b e :action 'gnus-widget-press-button
+                          :button-keymap gnus-widget-button-keymap)))
+
+(defun gnus-widget-press-button (elems el)
+  (goto-char (widget-get elems :from))
+  (let ((url-standalone-mode (not gnus-plugged)))
+    (gnus-article-press-button)))
+
+(defun gnus-display-mime ()
+  "Insert MIME buttons in the buffer."
+  (let (ct ctl)
+    (save-restriction
+      (mail-narrow-to-head)
+      (when (setq ct (mail-fetch-field "content-type"))
+       (setq ctl (condition-case ()
+                     (mail-header-parse-content-type ct) (error nil)))))
+    (let* ((handles (mm-dissect-buffer))
+          handle name type b e)
+      (mapcar 'mm-destroy-part gnus-article-mime-handles)
+      (setq gnus-article-mime-handles handles)
+      (when handles
+       (goto-char (point-min))
+       (search-forward "\n\n" nil t)
+       (delete-region (point) (point-max))
+       (if (not (equal (car ctl) "multipart/alternative"))
+           (while (setq handle (pop handles))
+             (gnus-insert-mime-button handle)
+             (insert "\n\n")
+             (when (and (mm-automatic-display-p
+                         (car (mm-handle-type handle)))
+                        (or (not (mm-handle-disposition handle))
+                            (equal (car (mm-handle-disposition handle))
+                                   "inline")))
+               (forward-line -2)
+               (mm-display-part handle t)
+               (goto-char (point-max))))
+         ;; Here we have multipart/alternative
+         (gnus-mime-display-alternative handles))))))
+
+(defun gnus-mime-display-alternative (handles &optional preferred)
+  (let* ((preferred (mm-preferred-alternative handles preferred))
+        (ihandles handles)
+        handle buffer-read-only)
+    (goto-char (point-min))
+    (search-forward "\n\n" nil t)
+    (delete-region (point) (point-max))
+    (mapcar 'mm-remove-part gnus-article-mime-handles)
+    (setq gnus-article-mime-handles handles)
+    (while (setq handle (pop handles))
+      (gnus-add-text-properties
+       (point)
+       (progn
+        (insert (format "[%c] %-18s"
+                        (if (equal handle preferred) ?* ? )
+                        (car (mm-handle-type handle))))
+        (point))
+       `(local-map ,gnus-mime-button-map
+                  keymap ,gnus-mime-button-map
+                  gnus-callback
+                  (lambda (handles)
+                    (gnus-mime-display-alternative
+                     ',ihandles ,(car (mm-handle-type handle))))
+                  gnus-data ,handle))
+      (insert "  "))
+    (insert "\n\n")
+    (when preferred
+      (mm-display-part preferred))))
+
 (defun gnus-article-wash-status ()
   "Return a string which display status of article washing."
   (save-excursion
@@ -2015,18 +2318,18 @@ If ALL-HEADERS is non-nil, no headers are hidden."
          (pem (gnus-article-hidden-text-p 'pem))
          (signature (gnus-article-hidden-text-p 'signature))
          (overstrike (gnus-article-hidden-text-p 'overstrike))
-         (emphasis (gnus-article-hidden-text-p 'emphasis))
-         (mime gnus-show-mime))
+         (emphasis (gnus-article-hidden-text-p 'emphasis)))
       (format "%c%c%c%c%c%c%c"
              (if cite ?c ? )
              (if (or headers boring) ?h ? )
              (if (or pgp pem) ?p ? )
              (if signature ?s ? )
              (if overstrike ?o ? )
-             (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)
@@ -2038,13 +2341,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.
@@ -2163,8 +2467,10 @@ Argument LINES specifies lines to be scrolled down."
   (interactive)
   (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-goto-subject gnus-current-article)
+    (gnus-summary-position-point)))
 
 (defun gnus-article-describe-briefly ()
   "Describe article mode commands briefly."
@@ -2178,7 +2484,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)
@@ -2189,7 +2495,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)))
 
@@ -2197,85 +2503,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.
@@ -2292,7 +2614,7 @@ 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)))
@@ -2303,7 +2625,7 @@ If given a prefix, show the hidden text instead."
                    (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)
@@ -2316,10 +2638,13 @@ If given a prefix, show the hidden text instead."
 
                (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))))))))
@@ -2329,7 +2654,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)))
@@ -2351,6 +2676,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
@@ -2364,6 +2691,7 @@ 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)
@@ -2374,24 +2702,30 @@ If given a prefix, show the hidden text instead."
           ;; 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))
-           (buffer-disable-undo (current-buffer))
+             (set-buffer gnus-original-article-buffer)
+           (set-buffer (gnus-get-buffer-create gnus-original-article-buffer))
+           (buffer-disable-undo)
            (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))))
+         (setq gnus-original-article (cons group article)))
+
+       ;; Decode charsets.
+       (run-hooks 'gnus-article-decode-hook)
+       ;; Mark article as decoded or not.
+       (setq gnus-article-decoded-p gnus-article-decode-hook))
 
       ;; Update sparse articles.
       (when (and do-update-line
@@ -2399,7 +2733,7 @@ If given a prefix, show the hidden text instead."
                     (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))
@@ -2435,7 +2769,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)
@@ -2444,7 +2777,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.
@@ -2454,27 +2787,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)
+    (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)))
@@ -2482,7 +2839,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))))
@@ -2542,21 +2899,23 @@ groups."
   :type 'regexp)
 
 (defcustom gnus-button-alist
-  `(("<\\(url: ?\\)?news:\\([^>\n\t ]*@[^>\n\t ]*\\)>" 0 t
+  `(("<\\(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
+    ("\\(\\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,
@@ -2584,10 +2943,11 @@ variable it the real callback function."
     ("^\\(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:
@@ -2625,6 +2985,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))))
 
@@ -2638,40 +2999,6 @@ call it with the value of the `gnus-data' text property."
     (when fun
       (funcall fun data))))
 
-(defun gnus-article-prev-button (n)
-  "Move point to N buttons backward.
-If N is negative, move forward instead."
-  (interactive "p")
-  (gnus-article-next-button (- n)))
-
-(defun gnus-article-next-button (n)
-  "Move point to N buttons forward.
-If N is negative, move backward instead."
-  (interactive "p")
-  (let ((function (if (< n 0) 'previous-single-property-change
-                   'next-single-property-change))
-       (inhibit-point-motion-hooks t)
-       (backward (< n 0))
-       (limit (if (< n 0) (point-min) (point-max))))
-    (setq n (abs n))
-    (while (and (not (= limit (point)))
-               (> n 0))
-      ;; Skip past the current button.
-      (when (get-text-property (point) 'gnus-callback)
-       (goto-char (funcall function (point) 'gnus-callback nil limit)))
-      ;; Go to the next (or previous) button.
-      (gnus-goto-char (funcall function (point) 'gnus-callback nil limit))
-      ;; Put point at the start of the button.
-      (when (and backward (not (get-text-property (point) 'gnus-callback)))
-       (goto-char (funcall function (point) 'gnus-callback nil limit)))
-      ;; Skip past intangible buttons.
-      (when (get-text-property (point) 'intangible)
-       (incf n))
-      (decf n))
-    (unless (zerop n)
-      (gnus-message 5 "No more buttons"))
-    n))
-
 (defun gnus-article-highlight (&optional force)
   "Highlight current article.
 This function calls `gnus-article-highlight-headers',
@@ -2854,10 +3181,17 @@ specified by `gnus-button-alist'."
    (nconc (and gnus-article-mouse-face
               (list gnus-mouse-face-prop gnus-article-mouse-face))
          (list 'gnus-callback fun)
-         (and data (list 'gnus-data data)))))
+         (and data (list 'gnus-data data))))
+  (widget-convert-button 'link from to :action 'gnus-widget-press-button
+                        :button-keymap gnus-widget-button-keymap))
 
 ;;; 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)
@@ -2924,17 +3258,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))
@@ -2986,7 +3312,7 @@ forbidden in URL encoding."
   ;; 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
@@ -3017,11 +3343,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.
 
@@ -3039,7 +3371,8 @@ forbidden in URL encoding."
     (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
@@ -3067,9 +3400,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."