2003-02-12 Michael Shields <shields@msrl.com>
[gnus] / lisp / gnus-art.el
index 1122853..cd23f29 100644 (file)
@@ -1,5 +1,5 @@
 ;;; gnus-art.el --- article mode commands for Gnus
-;; Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2002
+;; Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003
 ;;        Free Software Foundation, Inc.
 
 ;; Author: Lars Magne Ingebrigtsen <larsi@gnus.org>
   :group 'gnus-article)
 
 (defcustom gnus-ignored-headers
-  '("^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" "^X-Auth:" "^X-From-Line:"
-    "^X-Gnus-Article-Number:" "^X-Majordomo:" "^X-Url:" "^X-Sender:"
-    "^MBOX-Line" "^Priority:" "^X400-[-A-Za-z]+:"
-    "^Status:" "^X-Gnus-Mail-Source:" "^Cancel-Lock:"
-    "^X-FTN" "^X-EXP32-SerialNo:" "^Encoding:" "^Importance:"
-    "^Autoforwarded:" "^Original-Encoded-Information-Types:" "^X-Ya-Pop3:"
-    "^X-Face-Version:" "^X-Vms-To:" "^X-ML-NAME:" "^X-ML-COUNT:"
-    "^Mailing-List:" "^X-finfo:" "^X-md5sum:" "^X-md5sum-Origin:"
-    "^X-Sun-Charset:" "^X-Accept-Language:" "^X-Envelope-Sender:"
-    "^List-[A-Za-z]+:" "^X-Listprocessor-Version:"
-    "^X-Received:" "^X-Distribute:" "^X-Sequence:" "^X-Juno-Line-Breaks:"
-    "^X-Notes-Item:" "^X-MS-TNEF-Correlator:" "^x-uunet-gateway:"
-    "^X-Received:" "^Content-length:" "X-precedence:"
-    "^X-Authenticated-User:" "^X-Comment" "^X-Report:" "^X-Abuse-Info:"
-    "^X-HTTP-Proxy:" "^X-Mydeja-Info:" "^X-Copyright" "^X-No-Markup:"
-    "^X-Abuse-Info:" "^X-From_:" "^X-Accept-Language:" "^Errors-To:"
-    "^X-BeenThere:" "^X-Mailman-Version:" "^List-Help:" "^List-Post:"
-    "^List-Subscribe:" "^List-Id:" "^List-Unsubscribe:" "^List-Archive:"
-     "^X-Content-length:" "^X-Posting-Agent:" "^Original-Received:"
-     "^X-Request-PGP:" "^X-Fingerprint:" "^X-WRIEnvto:" "^X-WRIEnvfrom:"
-     "^X-Virus-Scanned:" "^X-Delivery-Agent:" "^Posted-Date:" "^X-Gateway:"
-     "^X-Local-Origin:" "^X-Local-Destination:" "^X-UserInfo1:"
-     "^X-Received-Date:" "^X-Hashcash:")
+  (mapcar
+   (lambda (header)
+     (concat "^" header ":"))
+   '("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" "X-Auth" "X-From-Line"
+     "X-Gnus-Article-Number" "X-Majordomo" "X-Url" "X-Sender"
+     "MBOX-Line" "Priority" "X400-[-A-Za-z]+"
+     "Status" "X-Gnus-Mail-Source" "Cancel-Lock"
+     "X-FTN" "X-EXP32-SerialNo" "Encoding" "Importance"
+     "Autoforwarded" "Original-Encoded-Information-Types" "X-Ya-Pop3"
+     "X-Face-Version" "X-Vms-To" "X-ML-NAME" "X-ML-COUNT"
+     "Mailing-List" "X-finfo" "X-md5sum" "X-md5sum-Origin"
+     "X-Sun-Charset" "X-Accept-Language" "X-Envelope-Sender"
+     "List-[A-Za-z]+" "X-Listprocessor-Version"
+     "X-Received" "X-Distribute" "X-Sequence" "X-Juno-Line-Breaks"
+     "X-Notes-Item" "X-MS-TNEF-Correlator" "x-uunet-gateway"
+     "X-Received" "Content-length" "X-precedence"
+     "X-Authenticated-User" "X-Comment" "X-Report" "X-Abuse-Info"
+     "X-HTTP-Proxy" "X-Mydeja-Info" "X-Copyright" "X-No-Markup"
+     "X-Abuse-Info" "X-From_" "X-Accept-Language" "Errors-To"
+     "X-BeenThere" "X-Mailman-Version" "List-Help" "List-Post"
+     "List-Subscribe" "List-Id" "List-Unsubscribe" "List-Archive"
+     "X-Content-length" "X-Posting-Agent" "Original-Received"
+     "X-Request-PGP" "X-Fingerprint" "X-WRIEnvto" "X-WRIEnvfrom"
+     "X-Virus-Scanned" "X-Delivery-Agent" "Posted-Date" "X-Gateway"
+     "X-Local-Origin" "X-Local-Destination" "X-UserInfo1"
+     "X-Received-Date" "X-Hashcash" "Face" "X-DMCA-Notifications"
+     "X-Abuse-and-DMCA-Info" "X-Postfilter"))
   "*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."
@@ -196,6 +200,15 @@ Possible values in this list are:
              (const :tag "Multiple To and/or Cc headers." many-to))
   :group 'gnus-article-hiding)
 
+(defcustom gnus-article-skip-boring nil
+  "Skip over text that is not worth reading.
+By default, if you set this t, then Gnus will display citations and
+signatures, but will never scroll down to show you a page consisting
+only of boring text.  Boring text is controlled by
+`gnus-article-boring-faces'."
+  :type 'boolean
+  :group 'gnus-article-hiding)
+
 (defcustom gnus-signature-separator '("^-- $" "^-- *$")
   "Regexp matching signature separator.
 This can also be a list of regexps.  In that case, it will be checked
@@ -540,10 +553,9 @@ The following additional specs are available:
   :type 'hook
   :group 'gnus-article-various)
 
-(defcustom gnus-article-hide-pgp-hook nil
-  "*A hook called after successfully hiding a PGP signature."
-  :type 'hook
-  :group 'gnus-article-various)
+(defvar gnus-article-hide-pgp-hook nil)
+(make-obsolete-variable 'gnus-article-hide-pgp-hook 
+                       "This variable is obsolete in Gnus 5.10.")
 
 (defcustom gnus-article-button-face 'bold
   "Face used for highlighting buttons in the article buffer.
@@ -710,7 +722,8 @@ displayed by the first non-nil matching CONTENT face."
 
 (defcustom gnus-unbuttonized-mime-types '(".*/.*")
   "List of MIME types that should not be given buttons when rendered inline.
-See also `gnus-buttonized-mime-types' which may override this variable."
+See also `gnus-buttonized-mime-types' which may override this variable.
+This variable is only used when `gnus-inhibit-mime-unbuttonizing' is nil."
   :version "21.1"
   :group 'gnus-article-mime
   :type '(repeat regexp))
@@ -719,7 +732,8 @@ See also `gnus-buttonized-mime-types' which may override this variable."
   "List of MIME types that should be given buttons when rendered inline.
 If set, this variable overrides `gnus-unbuttonized-mime-types'.
 To see e.g. security buttons you could set this to
-`(\"multipart/signed\")'."
+`(\"multipart/signed\")'.
+This variable is only used when `gnus-inhibit-mime-unbuttonizing' is nil."
   :version "21.1"
   :group 'gnus-article-mime
   :type '(repeat regexp))
@@ -786,7 +800,7 @@ This is meant for people who want to view first matched part.
 For `undisplayed-alternative' (default), the first undisplayed
 part or alternative part is used.  For `undisplayed', the first
 undisplayed part is used.  For a function, the first part which
-the function return `t' is used.  For `nil', the first part is
+the function return t is used.  For nil, the first part is
 used."
   :version "21.1"
   :group 'gnus-article-mime
@@ -960,13 +974,8 @@ See Info node `(gnus)Customizing Articles' for details."
   :link '(custom-manual "(gnus)Customizing Articles")
   :type gnus-article-treat-custom)
 
-(defcustom gnus-treat-strip-pgp t
-  "Strip PGP signatures.
-Valid values are nil, t, `head', `last', an integer or a predicate.
-See Info node `(gnus)Customizing Articles' for details."
-  :group 'gnus-article-treat
-  :link '(custom-manual "(gnus)Customizing Articles")
-  :type gnus-article-treat-custom)
+(make-obsolete-variable 'gnus-treat-strip-pgp 
+                       "This option is obsolete in Gnus 5.10.")
 
 (defcustom gnus-treat-strip-pem nil
   "Strip PEM signatures.
@@ -1146,16 +1155,23 @@ See Info node `(gnus)Customizing Articles' and Info node
   :type gnus-article-treat-head-custom)
 (put 'gnus-treat-display-xface 'highlight t)
 
-(defcustom gnus-treat-display-grey-xface
+(defcustom gnus-treat-display-face
   (and (not noninteractive)
-       (string-match "^0x" (shell-command-to-string "uncompface"))
-       t)
-  "Display grey X-Face headers.
-Valid values are nil, t."
+       (or (and (fboundp 'image-type-available-p)
+               (image-type-available-p 'png))
+          (and (featurep 'xemacs)
+               (featurep 'png)))
+       'head)
+  "Display Face headers.
+Valid values are nil, t, `head', `last', an integer or a predicate.
+See Info node `(gnus)Customizing Articles' and Info node
+`(gnus)X-Face' for details."
   :group 'gnus-article-treat
-  :version "21.3"
-  :type 'boolean)
-(put 'gnus-treat-display-grey-xface 'highlight t)
+  :version "21.1"
+  :link '(custom-manual "(gnus)Customizing Articles")
+  :link '(custom-manual "(gnus)X-Face")
+  :type gnus-article-treat-head-custom)
+(put 'gnus-treat-display-xface 'highlight t)
 
 (defcustom gnus-treat-display-smileys
   (if (or (and (featurep 'xemacs)
@@ -1241,6 +1257,14 @@ See Info node `(gnus)Customizing Articles' for details."
   :link '(custom-manual "(gnus)Customizing Articles")
   :type gnus-article-treat-custom)
 
+(defcustom gnus-treat-wash-html nil
+  "Format as HTML.
+Valid values are nil, t, `head', `last', an integer or a predicate.
+See Info node `(gnus)Customizing Articles' for details."
+  :group 'gnus-article-treat
+  :link '(custom-manual "(gnus)Customizing Articles")
+  :type gnus-article-treat-custom)
+
 (defcustom gnus-treat-fill-long-lines nil
   "Fill long lines.
 Valid values are nil, t, `head', `last', an integer or a predicate.
@@ -1321,12 +1345,12 @@ It is a string, such as \"PGP\". If nil, ask user."
     (gnus-treat-date-user-defined gnus-article-date-user)
     (gnus-treat-date-iso8601 gnus-article-date-iso8601)
     (gnus-treat-display-xface gnus-article-display-x-face)
+    (gnus-treat-display-face gnus-article-display-face)
     (gnus-treat-hide-headers gnus-article-maybe-hide-headers)
     (gnus-treat-hide-boring-headers gnus-article-hide-boring-headers)
     (gnus-treat-hide-signature gnus-article-hide-signature)
     (gnus-treat-strip-list-identifiers gnus-article-hide-list-identifiers)
     (gnus-treat-leading-whitespace gnus-article-remove-leading-whitespace)
-    (gnus-treat-strip-pgp gnus-article-hide-pgp)
     (gnus-treat-strip-pem gnus-article-hide-pem)
     (gnus-treat-from-picon gnus-treat-from-picon)
     (gnus-treat-mail-picon gnus-treat-mail-picon)
@@ -1346,6 +1370,7 @@ It is a string, such as \"PGP\". If nil, ask user."
     (gnus-treat-buttonize-head gnus-article-add-buttons-to-head)
     (gnus-treat-display-smileys gnus-treat-smiley)
     (gnus-treat-capitalize-sentences gnus-article-capitalize-sentences)
+    (gnus-treat-wash-html gnus-article-wash-html)
     (gnus-treat-emphasize gnus-article-emphasize)
     (gnus-treat-hide-citation gnus-article-hide-citation)
     (gnus-treat-hide-citation-maybe gnus-article-hide-citation-maybe)
@@ -1897,6 +1922,24 @@ unfolded."
         (forward-line 1)
         (point))))))
 
+(defun article-display-face ()
+  "Display any Face headers in the header."
+  (interactive)
+  (gnus-with-article-headers
+    (let ((face (message-fetch-field "face")))
+      (when face
+       (let ((png (gnus-convert-face-to-png face))
+             image)
+         (when png
+           (setq image (gnus-create-image png 'png t))
+           (gnus-article-goto-header "from")
+           (when (bobp)
+             (insert "From: [no `from' set]\n")
+             (forward-char -17))
+           (gnus-add-wash-type 'face)
+           (gnus-add-image 'face image)
+           (gnus-put-image image)))))))
+
 (defun article-display-x-face (&optional force)
   "Look for an X-Face header and display it if present."
   (interactive (list 'force))
@@ -1910,7 +1953,7 @@ unfolded."
          ;; instead.
          (gnus-delete-images 'xface)
        ;; Display X-Faces.
-       (let (x-faces from face grey)
+       (let (x-faces from face)
          (save-excursion
            (when (and wash-face-p
                       (progn
@@ -1924,67 +1967,39 @@ unfolded."
              (set-buffer gnus-original-article-buffer))
            (save-restriction
              (mail-narrow-to-head)
-             (if gnus-treat-display-grey-xface
-                 (progn
-                   (while (gnus-article-goto-header "X-Face\\(-[0-9]+\\)?")
-                     (if (match-beginning 2)
-                         (progn
-                           (setq grey t)
-                           (push (cons (- (string-to-number (match-string 2)))
-                                       (mail-header-field-value))
-                                 x-faces))
-                       (push (cons 0 (mail-header-field-value)) x-faces)))
-                   (dolist (x-face (prog1
-                                       (if grey
-                                           (sort x-faces 'car-less-than-car)
-                                         (nreverse x-faces))
-                                     (setq x-faces nil)))
-                     (push (cdr x-face) x-faces)))
-               (while (gnus-article-goto-header "X-Face")
-                 (push (mail-header-field-value) x-faces)))
+             (while (gnus-article-goto-header "X-Face")
+               (push (mail-header-field-value) x-faces))
              (setq from (message-fetch-field "from"))))
-         (if grey
-             (let ((xpm (gnus-convert-gray-x-face-to-xpm x-faces))
-                   image)
-               (when xpm
-                 (setq image (gnus-create-image xpm 'xpm t))
-                 (gnus-article-goto-header "from")
-                 (when (bobp)
-                   (insert "From: [no `from' set]\n")
-                   (forward-char -17))
-                 (gnus-add-wash-type 'xface)
-                 (gnus-add-image 'xface image)
-                 (gnus-put-image image)))
-           ;; Sending multiple EOFs to xv doesn't work, so we only do a
-           ;; single external face.
-           (when (stringp gnus-article-x-face-command)
-             (setq x-faces (list (car x-faces))))
-           (while (and (setq face (pop x-faces))
-                       gnus-article-x-face-command
-                       (or force
-                           ;; Check whether this face is censored.
-                           (not gnus-article-x-face-too-ugly)
-                           (and gnus-article-x-face-too-ugly from
-                                (not (string-match gnus-article-x-face-too-ugly
-                                                   from)))))
-             ;; We display the face.
-             (if (symbolp gnus-article-x-face-command)
-                 ;; The command is a lisp function, so we call it.
-                 (if (gnus-functionp gnus-article-x-face-command)
-                     (funcall gnus-article-x-face-command face)
-                   (error "%s is not a function" gnus-article-x-face-command))
-               ;; The command is a string, so we interpret the command
-               ;; as a, well, command, and fork it off.
-               (let ((process-connection-type nil))
-                 (process-kill-without-query
-                  (start-process
-                   "article-x-face" nil shell-file-name shell-command-switch
-                   gnus-article-x-face-command))
-                 (with-temp-buffer
-                   (insert face)
-                   (process-send-region "article-x-face"
-                                        (point-min) (point-max)))
-                 (process-send-eof "article-x-face"))))))))))
+         ;; Sending multiple EOFs to xv doesn't work, so we only do a
+         ;; single external face.
+         (when (stringp gnus-article-x-face-command)
+           (setq x-faces (list (car x-faces))))
+         (while (and (setq face (pop x-faces))
+                     gnus-article-x-face-command
+                     (or force
+                         ;; Check whether this face is censored.
+                         (not gnus-article-x-face-too-ugly)
+                         (and gnus-article-x-face-too-ugly from
+                              (not (string-match gnus-article-x-face-too-ugly
+                                                 from)))))
+           ;; We display the face.
+           (if (symbolp gnus-article-x-face-command)
+               ;; The command is a lisp function, so we call it.
+               (if (gnus-functionp gnus-article-x-face-command)
+                   (funcall gnus-article-x-face-command face)
+                 (error "%s is not a function" gnus-article-x-face-command))
+             ;; The command is a string, so we interpret the command
+             ;; as a, well, command, and fork it off.
+             (let ((process-connection-type nil))
+               (process-kill-without-query
+                (start-process
+                 "article-x-face" nil shell-file-name shell-command-switch
+                 gnus-article-x-face-command))
+               (with-temp-buffer
+                 (insert face)
+                 (process-send-region "article-x-face"
+                                      (point-min) (point-max)))
+               (process-send-eof "article-x-face")))))))))
 
 (defun article-decode-mime-words ()
   "Decode all MIME-encoded words in the article."
@@ -2171,24 +2186,24 @@ If READ-CHARSET, ask for a coding system."
 
 
 (defun article-wash-html (&optional read-charset)
-  "Format an html article.
+  "Format an HTML article.
 If READ-CHARSET, ask for a coding system."
   (interactive "P")
   (save-excursion
     (let ((buffer-read-only nil)
          charset)
-      (if (gnus-buffer-live-p gnus-original-article-buffer)
-         (with-current-buffer gnus-original-article-buffer
-           (let* ((ct (gnus-fetch-field "content-type"))
-                  (ctl (and ct
-                            (ignore-errors
-                              (mail-header-parse-content-type ct)))))
-             (setq charset (and ctl
-                                (mail-content-type-get ctl 'charset)))
-             (if (stringp charset)
-                 (setq charset (intern (downcase charset)))))))
-      (if read-charset
-         (setq charset (mm-read-coding-system "Charset: " charset)))
+      (when (gnus-buffer-live-p gnus-original-article-buffer)
+       (with-current-buffer gnus-original-article-buffer
+         (let* ((ct (gnus-fetch-field "content-type"))
+                (ctl (and ct
+                          (ignore-errors
+                            (mail-header-parse-content-type ct)))))
+           (setq charset (and ctl
+                              (mail-content-type-get ctl 'charset)))
+           (when (stringp charset)
+             (setq charset (intern (downcase charset)))))))
+      (when read-charset
+       (setq charset (mm-read-coding-system "Charset: " charset)))
       (unless charset
        (setq charset gnus-newsgroup-charset))
       (article-goto-body)
@@ -2197,8 +2212,8 @@ If READ-CHARSET, ask for a coding system."
          (narrow-to-region (point) (point-max))
          (let* ((func (or gnus-article-wash-function mm-text-html-renderer))
                 (entry (assq func mm-text-html-washer-alist)))
-           (if entry
-               (setq func (cdr entry)))
+           (when entry
+             (setq func (cdr entry)))
            (cond
             ((gnus-functionp func)
              (funcall func))
@@ -2256,42 +2271,6 @@ The `gnus-list-identifiers' variable specifies what to do."
                 "^Subject: +\\(\\(R[Ee]: +\\)+\\)R[Ee]: +" nil t)
            (delete-region (match-beginning 1) (match-end 1))))))))
 
-(defun article-hide-pgp ()
-  "Remove any PGP headers and signatures in the current article."
-  (interactive)
-  (save-excursion
-    (save-restriction
-      (let ((inhibit-point-motion-hooks t)
-           buffer-read-only beg end)
-       (article-goto-body)
-       ;; Hide the "header".
-       (when (re-search-forward "^-----BEGIN PGP SIGNED MESSAGE-----\n" nil t)
-         (gnus-add-wash-type 'pgp)
-         (delete-region (match-beginning 0) (match-end 0))
-         ;; Remove armor headers (rfc2440 6.2)
-         (delete-region (point) (or (re-search-forward "^[ \t]*\n" nil t)
-                                    (point)))
-         (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.
 If given a negative prefix, always show; if given a positive prefix,
@@ -3077,6 +3056,7 @@ Directory to save to is default to `gnus-article-save-directory'."
       (save-restriction
        (widen)
        (if (and (file-readable-p filename)
+                (file-regular-p filename)
                 (mail-file-babyl-p filename))
            (rmail-output-to-rmail-file filename t)
          (gnus-output-to-mail filename)))))
@@ -3331,13 +3311,13 @@ If variable `gnus-use-long-file-name' is non-nil, it is
      article-remove-cr
      article-remove-leading-whitespace
      article-display-x-face
+     article-display-face
      article-de-quoted-unreadable
      article-de-base64-unreadable
      article-decode-HZ
      article-wash-html
      article-unsplit-urls
      article-hide-list-identifiers
-     article-hide-pgp
      article-strip-banner
      article-babel
      article-hide-pem
@@ -3440,9 +3420,6 @@ If variable `gnus-use-long-file-name' is non-nil, it is
 
     (gnus-run-hooks 'gnus-article-menu-hook)))
 
-;; Fixme: do something for the Emacs tool bar in Article mode a la
-;; Summary.
-
 (defun gnus-article-mode ()
   "Major mode for displaying an article.
 
@@ -3465,7 +3442,9 @@ commands:
   (make-local-variable 'minor-mode-alist)
   (use-local-map gnus-article-mode-map)
   (when (gnus-visual-p 'article-menu 'menu)
-    (gnus-article-make-menu-bar))
+    (gnus-article-make-menu-bar)
+    (when gnus-summary-tool-bar-map
+      (set (make-local-variable 'tool-bar-map) gnus-summary-tool-bar-map)))
   (gnus-update-format-specifications nil 'article-mode)
   (set (make-local-variable 'page-delimiter) gnus-page-delimiter)
   (make-local-variable 'gnus-page-broken)
@@ -3526,6 +3505,8 @@ commands:
          (setq gnus-article-mime-handle-alist nil)
          (buffer-disable-undo)
          (setq buffer-read-only t)
+         ;; This list just keeps growing if we don't reset it.
+         (setq gnus-button-marker-list nil)
          (unless (eq major-mode 'gnus-article-mode)
            (gnus-article-mode))
          (current-buffer))
@@ -3898,8 +3879,40 @@ General format specifiers can also be used.  See Info node
            (mm-merge-handles gnus-article-mime-handles handle))
       (gnus-mm-display-part handle))))
 
+(eval-when-compile
+  (require 'jka-compr))
+
+;; jka-compr.el uses a "sh -c" to direct stderr to err-file, but these days
+;; emacs can do that itself.
+;;
+(defun gnus-mime-jka-compr-maybe-uncompress ()
+  "Uncompress the current buffer if `auto-compression-mode' is enabled.
+The uncompress method used is derived from `buffer-file-name'."
+  (when (and (fboundp 'jka-compr-installed-p)
+             (jka-compr-installed-p))
+    (let ((info (jka-compr-get-compression-info buffer-file-name)))
+      (when info
+        (let ((basename (file-name-nondirectory buffer-file-name))
+              (args     (jka-compr-info-uncompress-args    info))
+              (prog     (jka-compr-info-uncompress-program info))
+              (message  (jka-compr-info-uncompress-message info))
+              (err-file (jka-compr-make-temp-name)))
+          (if message
+              (message "%s %s..." message basename))
+          (unwind-protect
+              (unless (memq (apply 'call-process-region
+                                   (point-min) (point-max) 
+                                   prog
+                                   t (list t err-file) nil
+                                   args)
+                            jka-compr-acceptable-retval-list)
+                (jka-compr-error prog args basename message err-file))
+            (jka-compr-delete-temp-file err-file)))))))
+
 (defun gnus-mime-copy-part (&optional handle)
-  "Put the MIME part under point into a new buffer."
+  "Put the MIME part under point into a new buffer.
+If `auto-compression-mode' is enabled, compressed files like .gz and .bz2
+are decompressed."
   (interactive)
   (gnus-article-check-buffer)
   (let* ((handle (or handle (get-text-property (point) 'gnus-data)))
@@ -3919,6 +3932,7 @@ General format specifiers can also be used.  See Info node
       (unwind-protect
          (progn
            (setq buffer-file-name (expand-file-name base))
+           (gnus-mime-jka-compr-maybe-uncompress)
            (normal-mode))
        (setq buffer-file-name nil))
       (goto-char (point-min)))))
@@ -3930,13 +3944,12 @@ General format specifiers can also be used.  See Info node
   (let* ((handle (or handle (get-text-property (point) 'gnus-data)))
         (contents (and handle (mm-get-part handle)))
         (file (mm-make-temp-file (expand-file-name "mm." mm-tmp-directory)))
-        (printer (mailcap-mime-info (mm-handle-type handle) "print")))
+        (printer (mailcap-mime-info (mm-handle-media-type handle) "print")))
     (when contents
        (if printer
            (unwind-protect
                (progn
-                 (with-temp-file file
-                   (insert contents))
+                 (mm-save-part-to-file handle file)
                  (call-process shell-file-name nil
                                (generate-new-buffer " *mm*")
                                nil
@@ -4029,7 +4042,8 @@ If no internal viewer is available, use an external viewer."
         (mail-parse-charset gnus-newsgroup-charset)
         (mail-parse-ignored-charsets
          (save-excursion (set-buffer gnus-summary-buffer)
-                         gnus-newsgroup-ignored-charsets)))
+                         gnus-newsgroup-ignored-charsets))
+        buffer-read-only)
     (when handle
       (if (mm-handle-undisplayer handle)
          (mm-remove-part handle)
@@ -4259,9 +4273,10 @@ If no internal viewer is available, use an external viewer."
          ;; We have to do this since selecting the window
          ;; may change the point.  So we set the window point.
          (set-window-point window point)))
-      (let* ((handles (or ihandles (mm-dissect-buffer
-                                   nil gnus-article-loose-mime)
-                         (mm-uu-dissect)))
+      (let* ((handles (or ihandles
+                         (mm-dissect-buffer nil gnus-article-loose-mime)
+                         (and gnus-article-emulate-mime
+                              (mm-uu-dissect))))
             buffer-read-only handle name type b e display)
        (when (and (not ihandles)
                   (not gnus-displaying-mime))
@@ -4690,7 +4705,7 @@ Argument LINES specifies lines to be scrolled up."
   (if (save-excursion
        (end-of-line)
        (and (pos-visible-in-window-p)  ;Not continuation line.
-            (eobp)))
+            (>= (1+ (point)) (point-max)))) ;Allow for trailing newline.
       ;; Nothing in this page.
       (if (or (not gnus-page-broken)
              (save-excursion
@@ -4729,13 +4744,34 @@ Argument LINES specifies lines to be scrolled down."
             (goto-char (point-min))))
        (move-to-window-line 0)))))
 
+(defun gnus-article-only-boring-p ()
+  "Decide whether there is only boring text remaining in the article.
+Something \"interesting\" is a word of at least two letters that does
+not have a face in `gnus-article-boring-faces'."
+  (when (and gnus-article-skip-boring
+            gnus-article-boring-faces)
+    (save-excursion
+      (catch 'only-boring
+       (while (re-search-forward "\\b\\w\\w" nil t)
+         (forward-char -1)
+         (when (not (gnus-intersection
+                     (cons (plist-get (text-properties-at (point))
+                                      'face)
+                           (mapcar-extents
+                            '(lambda (extent)
+                               (extent-property extent 'face))
+                            nil (current-buffer) (point) (point)))
+                     gnus-article-boring-faces))
+           (throw 'only-boring nil)))
+       (throw 'only-boring t)))))
+
 (defun gnus-article-refer-article ()
   "Read article specified by message-id around point."
   (interactive)
   (let ((point (point)))
     (search-forward ">" nil t)         ;Move point to end of "<....>".
     (if (re-search-backward "\\(<[^<> \t\n]+>\\)" nil t)
-       (let ((message-id (match-string 1)))
+       (let ((message-id (gnus-replace-in-string (match-string 1) "<news:" "<" )))
          (goto-char point)
          (set-buffer gnus-summary-buffer)
          (gnus-summary-refer-article message-id))
@@ -4907,46 +4943,47 @@ Argument LINES specifies lines to be scrolled down."
 The text in the region will be yanked.  If the region isn't active,
 the entire article will be yanked."
   (interactive "P")
-  (let ((article (cdr gnus-article-current)) cont)
-    (if (not (mark t))
+  (let ((article (cdr gnus-article-current))
+       contents)
+    (if (not (gnus-mark-active-p))
        (with-current-buffer gnus-summary-buffer
          (gnus-summary-reply (list (list article)) wide))
-      (setq cont (buffer-substring (point) (mark t)))
+      (setq contents (buffer-substring (point) (mark t)))
       ;; Deactivate active regions.
       (when (and (boundp 'transient-mark-mode)
                 transient-mark-mode)
        (setq mark-active nil))
       (with-current-buffer gnus-summary-buffer
        (gnus-summary-reply
-        (list (list article cont)) wide)))))
+        (list (list article contents)) wide)))))
 
 (defun gnus-article-followup-with-original ()
   "Compose a followup to the current article.
 The text in the region will be yanked.  If the region isn't active,
 the entire article will be yanked."
   (interactive)
-  (let ((article (cdr gnus-article-current)) cont)
-      (if (not (mark t))
+  (let ((article (cdr gnus-article-current))
+       contents)
+      (if (not (gnus-mark-active-p))
          (with-current-buffer gnus-summary-buffer
            (gnus-summary-followup (list (list article))))
-       (setq cont (buffer-substring (point) (mark t)))
+       (setq contents (buffer-substring (point) (mark t)))
        ;; Deactivate active regions.
        (when (and (boundp 'transient-mark-mode)
                   transient-mark-mode)
          (setq mark-active nil))
        (with-current-buffer gnus-summary-buffer
          (gnus-summary-followup
-          (list (list article cont)))))))
+          (list (list article contents)))))))
 
 (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.
+This means that signatures, cited text and (some) headers will be
+hidden.
 If given a prefix, show the hidden text instead."
   (interactive (append (gnus-article-hidden-arg) (list 'force)))
   (gnus-article-hide-headers arg)
   (gnus-article-hide-list-identifiers arg)
-  (gnus-article-hide-pgp arg)
   (gnus-article-hide-citation-maybe arg force)
   (gnus-article-hide-signature arg))
 
@@ -5181,7 +5218,7 @@ If given a prefix, show the hidden text instead."
     "\C-c\C-f\C-k" message-goto-keywords
     "\C-c\C-f\C-u" message-goto-summary
     "\C-c\C-f\C-i" message-insert-or-toggle-importance
-    "\C-c\C-f\C-a" message-gen-unsubscribed-mft
+    "\C-c\C-f\C-a" message-generate-unsubscribed-mail-followup-to
     "\C-c\C-b" message-goto-body
     "\C-c\C-i" message-goto-signature
 
@@ -5340,8 +5377,8 @@ groups."
 
 (defcustom gnus-button-url-regexp
   (if (string-match "[[:digit:]]" "1") ;; support POSIX?
-      "\\b\\(\\(www\\.\\|\\(s?https?\\|ftp\\|file\\|gopher\\|news\\|telnet\\|wais\\|mailto\\|info\\):\\)\\(//[-a-z0-9_.]+:[0-9]*\\)?[-a-z0-9_=!?#$@~`%&*+|\\/:;.,[:word:]]+[-a-z0-9_=#$@~`%&*+|\\/[:word:]]\\)"
-    "\\b\\(\\(www\\.\\|\\(s?https?\\|ftp\\|file\\|gopher\\|news\\|telnet\\|wais\\|mailto\\|info\\):\\)\\(//[-a-z0-9_.]+:[0-9]*\\)?\\([-a-z0-9_=!?#$@~`%&*+|\\/:;.,]\\|\\w\\)+\\([-a-z0-9_=#$@~`%&*+|\\/]\\|\\w\\)\\)")
+      "\\b\\(\\(www\\.\\|\\(s?https?\\|ftp\\|file\\|gopher\\|news\\|telnet\\|wais\\|mailto\\|info\\):\\)\\(//[-a-z0-9_.]+:[0-9]*\\)?[-a-z0-9_=!?#$@~`%&*+\\/:;.,[:word:]]+[-a-z0-9_=#$@~`%&*+\\/[:word:]]\\)"
+    "\\b\\(\\(www\\.\\|\\(s?https?\\|ftp\\|file\\|gopher\\|news\\|telnet\\|wais\\|mailto\\|info\\):\\)\\(//[-a-z0-9_.]+:[0-9]*\\)?\\([-a-z0-9_=!?#$@~`%&*+\\/:;.,]\\|\\w\\)+\\([-a-z0-9_=#$@~`%&*+\\/]\\|\\w\\)\\)")
   "Regular expression that matches URLs."
   :group 'gnus-article-buttons
   :type 'regexp)
@@ -5459,40 +5496,43 @@ respectivly."
 (defvar gnus-button-handle-describe-prefix "^\\(C-h\\|<?[Ff]1>?\\)")
 
 (defun gnus-button-handle-describe-function (url)
-  "Call describe-function when pushing the corresponding URL button."
+  "Call `describe-function' when pushing the corresponding URL button."
   (describe-function
    (intern
     (gnus-replace-in-string url gnus-button-handle-describe-prefix ""))))
 
 (defun gnus-button-handle-describe-variable (url)
-  "Call describe-variable when pushing the corresponding URL button."
+  "Call `describe-variable' when pushing the corresponding URL button."
   (describe-variable
    (intern
     (gnus-replace-in-string url gnus-button-handle-describe-prefix ""))))
 
-;; FIXME: Is is possible to implement this?  Else it should be removed here
-;; and in `gnus-button-alist'.
 (defun gnus-button-handle-describe-key (url)
-  "Call describe-key when pushing the corresponding URL button."
-  (error "not implemented"))
+  "Call `describe-key' when pushing the corresponding URL button."
+  (let* ((key-string
+         (gnus-replace-in-string url gnus-button-handle-describe-prefix ""))
+        (keys (ignore-errors (eval `(kbd ,key-string)))))
+    (if keys
+       (describe-key keys)
+      (gnus-message 3 "Invalid key sequence in button: %s" key-string))))
 
 (defun gnus-button-handle-apropos (url)
-  "Call apropos when pushing the corresponding URL button."
+  "Call `apropos' when pushing the corresponding URL button."
   (apropos (gnus-replace-in-string url gnus-button-handle-describe-prefix "")))
 
 (defun gnus-button-handle-apropos-command (url)
-  "Call apropos when pushing the corresponding URL button."
+  "Call `apropos' when pushing the corresponding URL button."
   (apropos-command
    (gnus-replace-in-string url gnus-button-handle-describe-prefix "")))
 
 (defun gnus-button-handle-apropos-variable (url)
-  "Call apropos when pushing the corresponding URL button."
+  "Call `apropos' when pushing the corresponding URL button."
   (funcall
    (if (fboundp 'apropos-variable) 'apropos-variable 'apropos)
    (gnus-replace-in-string url gnus-button-handle-describe-prefix "")))
 
 (defun gnus-button-handle-apropos-documentation (url)
-  "Call apropos when pushing the corresponding URL button."
+  "Call `apropos' when pushing the corresponding URL button."
   (funcall
    (if (fboundp 'apropos-documentation) 'apropos-documentation 'apropos)
    (gnus-replace-in-string url gnus-button-handle-describe-prefix "")))
@@ -5584,9 +5624,10 @@ positives are possible."
      0 (>= gnus-button-emacs-level 1) gnus-button-handle-describe-function 2)
     ("\\b\\(C-h\\|<?[Ff]1>?\\)[ \t\n]+v[ \t\n]+\\([^ \t\n]+\\)[ \t\n]+RET"
      0 (>= gnus-button-emacs-level 1) gnus-button-handle-describe-variable 2)
-    ("\\b\\(C-h\\|<?[Ff]1>?\\)[ \t\n]+k[ \t\n]+\\([^ \t\n]+\\)[ \t\n]+" 0
-     ;; this regexp needs to be fixed!
-     (>= gnus-button-emacs-level 9) gnus-button-handle-describe-key 2)
+    ("`\\(\\b\\(C-h\\|<?[Ff]1>?\\)[ \t\n]+k[ \t\n]+\\([^']+\\)\\)'" 1
+     ;; Unlike the other regexps we really have to require quoting
+     ;; here to determine where it ends.
+     (>= gnus-button-emacs-level 1) gnus-button-handle-describe-key 3)
     ;; This is how URLs _should_ be embedded in text...
     ("<URL: *\\([^<>]*\\)>" 1 t gnus-button-embedded-url 1)
     ;; Raw URLs.
@@ -5691,8 +5732,8 @@ call it with the value of the `gnus-data' text property."
 If the text at point has a `gnus-callback' property,
 call it with the value of the `gnus-data' text property."
   (interactive)
-  (let* ((data (get-text-property (point) 'gnus-data))
-        (fun (get-text-property (point) 'gnus-callback)))
+  (let ((data (get-text-property (point) 'gnus-data))
+       (fun (get-text-property (point) 'gnus-callback)))
     (when fun
       (funcall fun data))))
 
@@ -6218,11 +6259,11 @@ For example:
        (highlightp (gnus-visual-p 'article-highlight 'highlight))
        val elem)
     (gnus-run-hooks 'gnus-part-display-hook)
-    (while (setq elem (pop alist))
+    (dolist (elem alist)
       (setq val
            (save-excursion
-             (if (gnus-buffer-live-p gnus-summary-buffer)
-                 (set-buffer gnus-summary-buffer))
+             (when (gnus-buffer-live-p gnus-summary-buffer)
+               (set-buffer gnus-summary-buffer))
              (symbol-value (car elem))))
       (when (and (or (consp val)
                     treated-type)
@@ -6244,6 +6285,8 @@ For example:
   (cond
    ((null val)
     nil)
+   (condition
+    (eq condition val))
    ((and (listp val)
         (stringp (car val)))
     (apply 'gnus-or (mapcar `(lambda (s)
@@ -6262,8 +6305,6 @@ For example:
        (equal (car val) type))
        (t
        (error "%S is not a valid predicate" pred)))))
-   (condition
-    (eq condition val))
    ((eq val t)
     t)
    ((eq val 'head)
@@ -6409,42 +6450,45 @@ For example:
 
 (defun gnus-mime-security-show-details (handle)
   (let ((details (mm-handle-multipart-ctl-parameter handle 'gnus-details)))
-    (if details
-       (if gnus-mime-security-show-details-inline
-           (let ((gnus-mime-security-button-pressed t)
-                 (gnus-mime-security-button-line-format
-                  (get-text-property (point) 'gnus-line-format))
+    (if (not details)
+       (gnus-message 5 "No details.")
+      (if gnus-mime-security-show-details-inline
+         (let ((gnus-mime-security-button-pressed
+                (not (get-text-property (point) 'gnus-mime-details)))
+               (gnus-mime-security-button-line-format
+                (get-text-property (point) 'gnus-line-format))
                buffer-read-only)
-             (forward-char -1)
-             (while (eq (get-text-property (point) 'gnus-line-format)
-                        gnus-mime-security-button-line-format)
-               (forward-char -1))
-             (forward-char)
-             (save-restriction
-               (narrow-to-region (point) (point))
-               (gnus-insert-mime-security-button handle))
-             (delete-region (point)
-                            (or (text-property-not-all
-                                 (point) (point-max)
-                                 'gnus-line-format
-                                 gnus-mime-security-button-line-format)
-                                (point-max))))
-         (if (gnus-buffer-live-p gnus-mime-security-details-buffer)
-             (with-current-buffer gnus-mime-security-details-buffer
-               (erase-buffer)
-               t)
-           (setq gnus-mime-security-details-buffer
-                 (gnus-get-buffer-create "*MIME Security Details*")))
-         (with-current-buffer gnus-mime-security-details-buffer
-           (insert details)
-           (goto-char (point-min)))
-         (pop-to-buffer gnus-mime-security-details-buffer))
-      (gnus-message 5 "No details."))))
+           (forward-char -1)
+           (while (eq (get-text-property (point) 'gnus-line-format)
+                      gnus-mime-security-button-line-format)
+             (forward-char -1))
+           (forward-char)
+           (save-restriction
+             (narrow-to-region (point) (point))
+             (gnus-insert-mime-security-button handle))
+           (delete-region (point)
+                          (or (text-property-not-all
+                               (point) (point-max)
+                               'gnus-line-format
+                               gnus-mime-security-button-line-format)
+                              (point-max))))
+       ;; Not inlined.
+       (if (gnus-buffer-live-p gnus-mime-security-details-buffer)
+           (with-current-buffer gnus-mime-security-details-buffer
+             (erase-buffer)
+             t)
+         (setq gnus-mime-security-details-buffer
+               (gnus-get-buffer-create "*MIME Security Details*")))
+       (with-current-buffer gnus-mime-security-details-buffer
+         (insert details)
+         (goto-char (point-min)))
+       (pop-to-buffer gnus-mime-security-details-buffer)))))
 
 (defun gnus-mime-security-press-button (handle)
-  (if (mm-handle-multipart-ctl-parameter handle 'gnus-info)
-      (gnus-mime-security-show-details handle)
-    (gnus-mime-security-verify-or-decrypt handle)))
+  (save-excursion
+    (if (mm-handle-multipart-ctl-parameter handle 'gnus-info)
+       (gnus-mime-security-show-details handle)
+      (gnus-mime-security-verify-or-decrypt handle))))
 
 (defun gnus-insert-mime-security-button (handle &optional displayed)
   (let* ((protocol (mm-handle-multipart-ctl-parameter handle 'protocol))
@@ -6465,7 +6509,8 @@ For example:
         b e)
     (setq gnus-tmp-details
          (if gnus-tmp-details
-             (concat "\n" gnus-tmp-details) ""))
+             (concat "\n" gnus-tmp-details)
+           ""))
     (setq gnus-tmp-pressed-details
          (if gnus-mime-security-button-pressed gnus-tmp-details ""))
     (unless (bolp)
@@ -6477,6 +6522,7 @@ For example:
      `(,@(gnus-local-map-property gnus-mime-security-button-map)
         gnus-callback gnus-mime-security-press-button
         gnus-line-format ,gnus-mime-security-button-line-format
+        gnus-mime-details ,gnus-mime-security-button-pressed
         article-type annotation
         gnus-data ,handle))
     (setq e (point))
@@ -6489,8 +6535,8 @@ For example:
      (lambda (widget/window &optional overlay pos)
        ;; Needed to properly clear the message due to a bug in
        ;; wid-edit (XEmacs only).
-       (if (boundp 'help-echo-owns-message)
-          (setq help-echo-owns-message t))
+       (when (boundp 'help-echo-owns-message)
+        (setq help-echo-owns-message t))
        (format
        "%S: show detail"
        (aref gnus-mouse-2 0))))))