(shr-tag-li): Get <li> indentation right.
[gnus] / lisp / gnus-art.el
index 826d5dc..d96df61 100644 (file)
@@ -1,7 +1,7 @@
 ;;; gnus-art.el --- article mode commands for Gnus
 
 ;; Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004,
-;;   2005, 2006, 2007, 2008, 2009 Free Software Foundation, Inc.
+;;   2005, 2006, 2007, 2008, 2009, 2010 Free Software Foundation, Inc.
 
 ;; Author: Lars Magne Ingebrigtsen <larsi@gnus.org>
 ;; Keywords: news
 (defvar w3m-minor-mode-map)
 
 (require 'gnus)
-;; Avoid the "Recursive load suspected" error in Emacs 21.1.
-(eval-and-compile
-  (let ((recursive-load-depth-limit 100))
-    (require 'gnus-sum)))
+(require 'gnus-sum)
 (require 'gnus-spec)
 (require 'gnus-int)
 (require 'gnus-win)
@@ -728,7 +725,7 @@ Each element is a regular expression."
   :group 'gnus-article-various)
 
 (make-obsolete-variable 'gnus-article-hide-pgp-hook nil
-                       "Gnus 5.10 (Emacs-22.1)")
+                       "Gnus 5.10 (Emacs 22.1)")
 
 (defface gnus-button
   '((t (:weight bold)))
@@ -1415,15 +1412,19 @@ predicate.  See Info node `(gnus)Customizing Articles'."
   :type gnus-article-treat-custom)
 
 (make-obsolete-variable 'gnus-treat-display-xface
-                       'gnus-treat-display-x-face "22.1")
+                       'gnus-treat-display-x-face "Emacs 22.1")
 
 (defcustom gnus-treat-display-x-face
   (and (not noninteractive)
        (gnus-image-type-available-p 'xbm)
        (if (featurep 'xemacs)
           (featurep 'xface)
-        (and (string-match "^0x" (shell-command-to-string "uncompface"))
-             (executable-find "icontopbm")))
+        (condition-case nil
+             (and (string-match "^0x" (shell-command-to-string "uncompface"))
+                  (executable-find "icontopbm"))
+           ;; shell-command-to-string may signal an error, e.g. if
+           ;; shell-file-name is not found.
+           (error nil)))
        'head)
   "Display X-Face headers.
 Valid values are nil and `head'.
@@ -1528,10 +1529,38 @@ node `(gnus)Picons' for details."
   :type gnus-article-treat-head-custom)
 (put 'gnus-treat-newsgroups-picon 'highlight t)
 
+(defcustom gnus-treat-from-gravatar nil
+  "Display gravatars in the From header.
+Valid values are nil, t, `head', `first', `last', an integer or a
+predicate.  See Info node `(gnus)Customizing Articles' and Info
+node `(gnus)Gravatars' for details."
+  :version "24.1"
+  :group 'gnus-article-treat
+  :group 'gnus-gravatar
+  :link '(custom-manual "(gnus)Customizing Articles")
+  :link '(custom-manual "(gnus)Gravatars")
+  :type gnus-article-treat-head-custom)
+(put 'gnus-treat-from-gravatar 'highlight t)
+
+(defcustom gnus-treat-mail-gravatar nil
+  "Display gravatars in To and Cc headers.
+Valid values are nil, t, `head', `first', `last', an integer or a
+predicate.  See Info node `(gnus)Customizing Articles' and Info
+node `(gnus)Gravatars' for details."
+  :version "24.1"
+  :group 'gnus-article-treat
+  :group 'gnus-gravatar
+  :link '(custom-manual "(gnus)Customizing Articles")
+  :link '(custom-manual "(gnus)Gravatars")
+  :type gnus-article-treat-head-custom)
+(put 'gnus-treat-mail-gravatar 'highlight t)
+
 (defcustom gnus-treat-body-boundary
   (if (or gnus-treat-newsgroups-picon
          gnus-treat-mail-picon
-         gnus-treat-from-picon)
+         gnus-treat-from-picon
+          gnus-treat-from-gravatar
+          gnus-treat-mail-gravatar)
       ;; If there's much decoration, the user might prefer a boundery.
       'head
     nil)
@@ -1569,24 +1598,6 @@ predicate.  See Info node `(gnus)Customizing Articles'."
   :link '(custom-manual "(gnus)Customizing Articles")
   :type gnus-article-treat-custom)
 
-(defcustom gnus-treat-play-sounds nil
-  "Play sounds.
-Valid values are nil, t, `head', `first', `last', an integer or a
-predicate.  See Info node `(gnus)Customizing Articles'."
-  :version "21.1"
-  :group 'gnus-article-treat
-  :link '(custom-manual "(gnus)Customizing Articles")
-  :type gnus-article-treat-custom)
-
-(defcustom gnus-treat-translate nil
-  "Translate articles from one language to another.
-Valid values are nil, t, `head', `first', `last', an integer or a
-predicate.  See Info node `(gnus)Customizing Articles'."
-  :version "21.1"
-  :group 'gnus-article-treat
-  :link '(custom-manual "(gnus)Customizing Articles")
-  :type gnus-article-treat-custom)
-
 (defcustom gnus-treat-x-pgp-sig nil
   "Verify X-PGP-Sig.
 To automatically treat X-PGP-Sig, set it to head.
@@ -1664,10 +1675,12 @@ This requires GNU Libidn, and by default only enabled if it is found."
     (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-pem gnus-article-hide-pem)
     (gnus-treat-from-picon gnus-treat-from-picon)
     (gnus-treat-mail-picon gnus-treat-mail-picon)
     (gnus-treat-newsgroups-picon gnus-treat-newsgroups-picon)
+    (gnus-treat-strip-pem gnus-article-hide-pem)
+    (gnus-treat-from-gravatar gnus-treat-from-gravatar)
+    (gnus-treat-mail-gravatar gnus-treat-mail-gravatar)
     (gnus-treat-highlight-headers gnus-article-highlight-headers)
     (gnus-treat-highlight-signature gnus-article-highlight-signature)
     (gnus-treat-strip-trailing-blank-lines
@@ -1689,8 +1702,7 @@ This requires GNU Libidn, and by default only enabled if it is found."
     (gnus-treat-hide-citation gnus-article-hide-citation)
     (gnus-treat-hide-citation-maybe gnus-article-hide-citation-maybe)
     (gnus-treat-highlight-citation gnus-article-highlight-citation)
-    (gnus-treat-body-boundary gnus-article-treat-body-boundary)
-    (gnus-treat-play-sounds gnus-earcon-display)))
+    (gnus-treat-body-boundary gnus-article-treat-body-boundary)))
 
 (defvar gnus-article-mime-handle-alist nil)
 (defvar article-lapsed-timer nil)
@@ -2273,9 +2285,9 @@ long lines if and only if arg is positive."
        (insert "X-Boundary: ")
        (gnus-add-text-properties start (point) '(invisible t intangible t))
        (insert (let (str)
-                 (while (>= (1- (window-width)) (length str))
+                 (while (>= (window-width) (length str))
                    (setq str (concat str gnus-body-boundary-delimiter)))
-                 (substring str 0 (1- (window-width))))
+                 (substring str 0 (window-width)))
                "\n")
        (gnus-put-text-property start (point) 'gnus-decoration 'header)))))
 
@@ -2802,31 +2814,66 @@ summary buffer."
 (defun gnus-article-browse-delete-temp-files (&optional how)
   "Delete temp-files created by `gnus-article-browse-html-parts'."
   (when (and gnus-article-browse-html-temp-list
-            (or how
-                (setq how gnus-article-browse-delete-temp)))
-    (when (and (eq how 'ask)
-              (gnus-y-or-n-p (format
-                              "Delete all %s temporary HTML file(s)? "
-                              (length gnus-article-browse-html-temp-list)))
-              (setq how t)))
+            (progn
+              (or how (setq how gnus-article-browse-delete-temp))
+              (if (eq how 'ask)
+                  (let ((files (length gnus-article-browse-html-temp-list)))
+                    (gnus-y-or-n-p (format
+                                    "Delete all %s temporary HTML file%s? "
+                                    files
+                                    (if (> files 1) "s" ""))))
+                how)))
     (dolist (file gnus-article-browse-html-temp-list)
-      (when (and (file-exists-p file)
-                (or (eq how t)
-                    ;; `how' is neither `nil', `ask' nor `t' (i.e. `file'):
-                    (gnus-y-or-n-p
-                     (format "Delete temporary HTML file `%s'? " file))))
-       (delete-file file)))
+      (cond ((file-directory-p file)
+            (when (or (not (eq how 'file))
+                      (gnus-y-or-n-p
+                       (format
+                        "Delete temporary HTML file(s) in directory `%s'? "
+                        (file-name-as-directory file))))
+              (gnus-delete-directory file)))
+           ((file-exists-p file)
+            (when (or (not (eq how 'file))
+                      (gnus-y-or-n-p
+                       (format "Delete temporary HTML file `%s'? " file)))
+              (delete-file file)))))
     ;; Also remove file from the list when not deleted or if file doesn't
     ;; exist anymore.
     (setq gnus-article-browse-html-temp-list nil))
   gnus-article-browse-html-temp-list)
 
+(defun gnus-article-browse-html-save-cid-content (cid handles directory)
+  "Find CID content in HANDLES and save it in a file in DIRECTORY.
+Return file name."
+  (save-match-data
+    (let (file type)
+      (catch 'found
+       (dolist (handle handles)
+         (cond
+          ((not (listp handle)))
+          ((equal (mm-handle-media-supertype handle) "multipart")
+           (when (setq file (gnus-article-browse-html-save-cid-content
+                             cid handle directory))
+             (throw 'found file)))
+          ((equal (concat "<" cid ">") (mm-handle-id handle))
+           (setq file
+                 (expand-file-name
+                  (or (mail-content-type-get
+                       (mm-handle-disposition handle) 'filename)
+                      (mail-content-type-get
+                       (setq type (mm-handle-type handle)) 'name)
+                      (concat
+                       (make-temp-name "cid")
+                       (car (rassoc (car type) mailcap-mime-extensions))))
+                  directory))
+           (mm-save-part-to-file handle file)
+           (throw 'found file))))))))
+
 (defun gnus-article-browse-html-parts (list &optional header)
   "View all \"text/html\" parts from LIST.
 Recurse into multiparts.  The optional HEADER that should be a decoded
 message header will be added to the bodies of the \"text/html\" parts."
   ;; Internal function used by `gnus-article-browse-html-article'.
-  (let (type file charset tmp-file showed)
+  (let (type file charset content cid-dir tmp-file showed)
     ;; Find and show the html-parts.
     (dolist (handle list)
       ;; If HTML, show it:
@@ -2849,16 +2896,42 @@ message header will be added to the bodies of the \"text/html\" parts."
                        (setq handle (mm-handle-cache handle)
                              type (mm-handle-type handle))
                        (equal (car type) "text/html"))))
-            (when (or (setq charset (mail-content-type-get type 'charset))
-                      header
-                      (not file))
+            (setq charset (mail-content-type-get type 'charset)
+                  content (mm-get-part handle))
+            (with-temp-buffer
+              (if (eq charset 'gnus-decoded)
+                  (mm-enable-multibyte)
+                (mm-disable-multibyte))
+              (insert content)
+              ;; resolve cid contents
+              (let ((case-fold-search t)
+                    cid-file)
+                (goto-char (point-min))
+                (while (re-search-forward "\
+<img[\t\n ]+\\(?:[^\t\n >]+[\t\n ]+\\)*src=\"\\(cid:\\([^\"]+\\)\\)\""
+                                          nil t)
+                  (unless cid-dir
+                    (setq cid-dir (mm-make-temp-file "cid" t))
+                    (add-to-list 'gnus-article-browse-html-temp-list cid-dir))
+                  (setq file nil
+                        content nil)
+                  (when (setq cid-file
+                              (gnus-article-browse-html-save-cid-content
+                               (match-string 2)
+                               (with-current-buffer gnus-article-buffer
+                                 gnus-article-mime-handles)
+                               cid-dir))
+                    (replace-match (concat "file://" cid-file)
+                                   nil nil nil 1))))
+              (unless content (setq content (buffer-string))))
+            (when (or charset header (not file))
               (setq tmp-file (mm-make-temp-file
                               ;; Do we need to care for 8.3 filenames?
                               "mm-" nil ".html")))
             ;; Add a meta html tag to specify charset and a header.
             (cond
              (header
-              (let (title eheader body hcharset coding)
+              (let (title eheader body hcharset coding force-charset)
                 (with-temp-buffer
                   (mm-enable-multibyte)
                   (setq case-fold-search t)
@@ -2881,8 +2954,8 @@ message header will be added to the bodies of the \"text/html\" parts."
                                                              charset)
                             title (when title
                                     (mm-encode-coding-string title charset))
-                            body (mm-encode-coding-string (mm-get-part handle)
-                                                          charset))
+                            body (mm-encode-coding-string content charset)
+                            force-charset t)
                     (setq hcharset (mm-find-mime-charset-region (point-min)
                                                                 (point-max)))
                     (cond ((= (length hcharset) 1)
@@ -2903,7 +2976,7 @@ message header will be added to the bodies of the \"text/html\" parts."
                                         title (when title
                                                 (mm-encode-coding-string
                                                  title coding))
-                                        body (mm-get-part handle))
+                                        body content)
                                 (setq charset 'utf-8
                                       eheader (mm-encode-coding-string
                                                (buffer-string) charset)
@@ -2912,22 +2985,23 @@ message header will be added to the bodies of the \"text/html\" parts."
                                                title charset))
                                       body (mm-encode-coding-string
                                             (mm-decode-coding-string
-                                             (mm-get-part handle) body)
-                                            charset))))
+                                             content body)
+                                            charset)
+                                      force-charset t)))
                           (setq charset hcharset
                                 eheader (mm-encode-coding-string
                                          (buffer-string) coding)
                                 title (when title
                                         (mm-encode-coding-string
                                          title coding))
-                                body (mm-get-part handle)))
+                                body content))
                       (setq eheader (mm-string-as-unibyte (buffer-string))
-                            body (mm-get-part handle))))
+                            body content)))
                   (erase-buffer)
                   (mm-disable-multibyte)
                   (insert body)
                   (when charset
-                    (mm-add-meta-html-tag handle charset))
+                    (mm-add-meta-html-tag handle charset force-charset))
                   (when title
                     (goto-char (point-min))
                     (unless (search-forward "<title>" nil t)
@@ -2944,10 +3018,9 @@ message header will be added to the bodies of the \"text/html\" parts."
              (charset
               (mm-with-unibyte-buffer
                 (insert (if (eq charset 'gnus-decoded)
-                            (mm-encode-coding-string
-                             (mm-get-part handle)
-                             (setq charset 'utf-8))
-                          (mm-get-part handle)))
+                            (mm-encode-coding-string content
+                                                     (setq charset 'utf-8))
+                          content))
                 (if (or (mm-add-meta-html-tag handle charset)
                         (not file))
                     (mm-write-region (point-min) (point-max)
@@ -2994,17 +3067,23 @@ message header will be added to the bodies of the \"text/html\" parts."
 
 (defun gnus-article-browse-html-article (&optional arg)
   "View \"text/html\" parts of the current article with a WWW browser.
+Inline images embedded in a message using the cid scheme, as they are
+generally considered to be safe, will be processed properly.
 The message header is added to the beginning of every html part unless
 the prefix argument ARG is given.
 
-Warning: Spammers use links to images in HTML articles to verify
-whether you have read the message.  As
+Warning: Spammers use links to images (using the http scheme) in HTML
+articles to verify whether you have read the message.  As
 `gnus-article-browse-html-article' passes the HTML content to the
 browser without eliminating these \"web bugs\" you should only
 use it for mails from trusted senders.
 
 If you always want to display HTML parts in the browser, set
-`mm-text-html-renderer' to nil."
+`mm-text-html-renderer' to nil.
+
+This command creates temporary files to pass HTML contents including
+images if any to the browser, and deletes them when exiting the group
+\(if you want)."
   ;; Cf. `mm-w3m-safe-url-regexp'
   (interactive "P")
   (if arg
@@ -3879,7 +3958,7 @@ Directory to save to is default to `gnus-article-save-directory'."
                  "Save %s in rmail file" filename
                  gnus-rmail-save-name gnus-newsgroup-name
                  gnus-current-headers 'gnus-newsgroup-last-rmail))
-  (gnus-eval-in-buffer-window gnus-save-article-buffer
+  (with-current-buffer gnus-save-article-buffer
     (save-excursion
       (save-restriction
        (widen)
@@ -3897,7 +3976,7 @@ Directory to save to is default to `gnus-article-save-directory'."
                  "Save %s in Unix mail file" filename
                  gnus-mail-save-name gnus-newsgroup-name
                  gnus-current-headers 'gnus-newsgroup-last-mail))
-  (gnus-eval-in-buffer-window gnus-save-article-buffer
+  (with-current-buffer gnus-save-article-buffer
     (save-excursion
       (save-restriction
        (widen)
@@ -3918,7 +3997,7 @@ Directory to save to is default to `gnus-article-save-directory'."
                  "Save %s in file" filename
                  gnus-file-save-name gnus-newsgroup-name
                  gnus-current-headers 'gnus-newsgroup-last-file))
-  (gnus-eval-in-buffer-window gnus-save-article-buffer
+  (with-current-buffer gnus-save-article-buffer
     (save-excursion
       (save-restriction
        (widen)
@@ -3950,7 +4029,7 @@ The directory to save in defaults to `gnus-article-save-directory'."
                  "Save %s body in file" filename
                  gnus-file-save-name gnus-newsgroup-name
                  gnus-current-headers 'gnus-newsgroup-last-file))
-  (gnus-eval-in-buffer-window gnus-save-article-buffer
+  (with-current-buffer gnus-save-article-buffer
     (save-excursion
       (save-restriction
        (widen)
@@ -4029,7 +4108,7 @@ and the raw article including all headers will be piped."
       (if default
          (setq command default)
        (error "A command is required")))
-    (gnus-eval-in-buffer-window save-buffer
+    (with-current-buffer save-buffer
       (save-restriction
        (widen)
        (shell-command-on-region (point-min) (point-max) command nil)))
@@ -4188,6 +4267,8 @@ If variable `gnus-use-long-file-name' is non-nil, it is
                  (put-text-property (match-end 0) (point-max)
                                     'face eface)))))))))
 
+(autoload 'canlock-verify "canlock" nil t) ;; for XEmacs.
+
 (defun article-verify-cancel-lock ()
   "Verify Cancel-Lock header."
   (interactive)
@@ -4341,6 +4422,8 @@ If variable `gnus-use-long-file-name' is non-nil, it is
 
     (gnus-run-hooks 'gnus-article-menu-hook)))
 
+(defvar bookmark-make-record-function)
+
 (defun gnus-article-mode ()
   "Major mode for displaying an article.
 
@@ -4379,6 +4462,8 @@ commands:
   (make-local-variable 'gnus-article-image-alist)
   (make-local-variable 'gnus-article-charset)
   (make-local-variable 'gnus-article-ignored-charsets)
+  (set (make-local-variable 'bookmark-make-record-function)
+       'gnus-summary-bookmark-make-record)
   ;; Prevent Emacs 22 from displaying non-break space with `nobreak-space'
   ;; face.
   (set (make-local-variable 'nobreak-char-display) nil)
@@ -4746,6 +4831,22 @@ General format specifiers can also be used.  See Info node
                (vector (caddr c) (car c) :active t))
              gnus-mime-button-commands)))
 
+(defvar gnus-url-button-commands
+  '((gnus-article-copy-string "u" "Copy URL to kill ring")))
+
+(defvar gnus-url-button-map
+  (let ((map (make-sparse-keymap)))
+    (dolist (c gnus-url-button-commands)
+      (define-key map (cadr c) (car c)))
+    map))
+
+(easy-menu-define
+  gnus-url-button-menu gnus-url-button-map "URL button menu."
+  `("Url Button"
+    ,@(mapcar (lambda (c)
+               (vector (caddr c) (car c) :active t))
+             gnus-url-button-commands)))
+
 (defmacro gnus-bind-safe-url-regexp (&rest body)
   "Bind `mm-w3m-safe-url-regexp' according to `gnus-safe-html-newsgroups'."
   `(let ((mm-w3m-safe-url-regexp
@@ -4755,7 +4856,11 @@ General format specifiers can also be used.  See Info node
                           (with-current-buffer gnus-article-current-summary
                             gnus-newsgroup-name)
                         gnus-newsgroup-name)))
-           (if (cond ((stringp gnus-safe-html-newsgroups)
+           (if (cond ((not group)
+                      ;; Maybe we're in a mml-preview buffer
+                      ;; and no group is selected.
+                      t)
+                     ((stringp gnus-safe-html-newsgroups)
                       (string-match gnus-safe-html-newsgroups group))
                      ((consp gnus-safe-html-newsgroups)
                       (member group gnus-safe-html-newsgroups)))
@@ -4797,10 +4902,7 @@ General format specifiers can also be used.  See Info node
   ;; FIXME: why is it necessary?
   (sit-for 0)
   (let ((parts (length gnus-article-mime-handle-alist)))
-    (or n (setq n
-               (string-to-number
-                (read-string ;; Emacs 21 doesn't have `read-number'.
-                 (format "Jump to part (2..%s): " parts)))))
+    (or n (setq n (read-number (format "Jump to part (2..%s): " parts))))
     (unless (and (integerp n) (<= n parts) (>= n 1))
       (setq n
            (progn
@@ -4819,6 +4921,10 @@ General format specifiers can also be used.  See Info node
          (t
           (gnus-article-goto-part n)))))
 
+(defvar gnus-mime-buttonized-part-id nil
+  "ID of a mime part that should be buttonized.
+`gnus-mime-save-part-and-strip' and `gnus-mime-delete-part' bind it.")
+
 (eval-when-compile
   (defsubst gnus-article-edit-part (handles &optional current-id)
     "Edit an article in order to delete a mime part.
@@ -4861,10 +4967,15 @@ and `gnus-mime-delete-part', and not provided at run-time normally."
         ,(gnus-group-read-only-p)
         ,gnus-summary-buffer no-highlight))
      t)
-    (gnus-article-edit-done)
+    ;; Force buttonizing this part.
+    (let ((gnus-mime-buttonized-part-id current-id))
+      (gnus-article-edit-done))
     (gnus-configure-windows 'article)
     (when (and current-id (integerp gnus-auto-select-part))
-      (gnus-article-jump-to-part (+ current-id gnus-auto-select-part)))))
+      (gnus-article-jump-to-part
+       (min (max (+ current-id gnus-auto-select-part) 1)
+           (with-current-buffer gnus-article-buffer
+             (length gnus-article-mime-handle-alist)))))))
 
 (defun gnus-mime-replace-part (file)
   "Replace MIME part under point with an external body."
@@ -4945,7 +5056,7 @@ Deleting parts may malfunction or destroy the article; continue? "))
       (unless data
        (error "No MIME part under point"))
       (with-current-buffer (mm-handle-buffer data)
-       (let ((bsize (format "%s" (buffer-size))))
+       (let ((bsize (buffer-size)))
          (erase-buffer)
          (insert
           (concat
@@ -4954,7 +5065,10 @@ Deleting parts may malfunction or destroy the article; continue? "))
            "|\n"
            "| Type:           " type "\n"
            "| Filename:       " filename "\n"
-           "| Size (encoded): " bsize " Byte\n"
+           "| Size (encoded): " (format "%s byte%s\n"
+                                        bsize (if (= bsize 1)
+                                                  ""
+                                                "s"))
            (when description
              (concat    "| Description:    " description "\n"))
            "`----\n"))
@@ -4974,13 +5088,14 @@ Deleting parts may malfunction or destroy the article; continue? "))
     (when data
       (mm-save-part data))))
 
-(defun gnus-mime-pipe-part ()
-  "Pipe the MIME part under point to a process."
+(defun gnus-mime-pipe-part (&optional cmd)
+  "Pipe the MIME part under point to a process.
+Use CMD as the process."
   (interactive)
   (gnus-article-check-buffer)
   (let ((data (get-text-property (point) 'gnus-data)))
     (when data
-      (mm-pipe-part data))))
+      (mm-pipe-part data cmd))))
 
 (defun gnus-mime-view-part ()
   "Interactively choose a viewing method for the MIME part under point."
@@ -5016,11 +5131,10 @@ available media-types."
   (unless mime-type
     (setq mime-type
          (let ((default (gnus-mime-view-part-as-type-internal)))
-           (completing-read
-            (format "View as MIME type (default %s): "
-                    (car default))
-            (mapcar #'list (mailcap-mime-types))
-            pred nil nil nil
+           (gnus-completing-read
+            "View as MIME type"
+            (remove-if-not pred (mailcap-mime-types))
+            nil nil nil
             (car default)))))
   (gnus-article-check-buffer)
   (let ((handle (get-text-property (point) 'gnus-data)))
@@ -5289,7 +5403,7 @@ If no internal viewer is available, use an external viewer."
 (defun gnus-mime-action-on-part (&optional action)
   "Do something with the MIME attachment at \(point\)."
   (interactive
-   (list (completing-read "Action: " gnus-mime-action-alist nil t)))
+   (list (gnus-completing-read "Action" (mapcar 'car gnus-mime-action-alist) t)))
   (gnus-article-check-buffer)
   (let ((action-pair (assoc action gnus-mime-action-alist)))
     (if action-pair
@@ -5458,7 +5572,9 @@ N is the numerical prefix."
     1))
 
 (defun gnus-article-view-part (&optional n)
-  "View MIME part N, which is the numerical prefix."
+  "View MIME part N, which is the numerical prefix.
+If the part is already shown, hide the part.  If N is nil, view
+all parts."
   (interactive "P")
   (with-current-buffer gnus-article-buffer
     (or (numberp n) (setq n (gnus-article-mime-match-handle-first
@@ -5572,7 +5688,7 @@ N is the numerical prefix."
      :action 'gnus-widget-press-button
      :button-keymap gnus-mime-button-map
      :help-echo
-     (lambda (widget/window &optional overlay pos)
+     (lambda (widget)
        ;; Needed to properly clear the message due to a bug in
        ;; wid-edit (XEmacs only).
        (if (boundp 'help-echo-owns-message)
@@ -5580,14 +5696,7 @@ N is the numerical prefix."
        (format
        "%S: %s the MIME part; %S: more options"
        (aref gnus-mouse-2 0)
-       ;; XEmacs will get a single widget arg; Emacs 21 will get
-       ;; window, overlay, position.
-       (if (mm-handle-displayed-p
-            (if overlay
-                (with-current-buffer (gnus-overlay-buffer overlay)
-                  (widget-get (widget-at (gnus-overlay-start overlay))
-                              :mime-handle))
-              (widget-get widget/window :mime-handle)))
+       (if (mm-handle-displayed-p (widget-get widget :mime-handle))
            "hide" "show")
        (aref gnus-down-mouse-3 0))))))
 
@@ -5766,7 +5875,8 @@ If displaying \"text/html\" is discouraged \(see
                   ((or (bobp) (eq (char-before (1- (point))) ?\n)) 0)
                   (t 1))))
          (when (or (not display)
-                   (not (gnus-unbuttonized-mime-type-p type)))
+                   (not (gnus-unbuttonized-mime-type-p type))
+                   (eq id gnus-mime-buttonized-part-id))
            (gnus-insert-mime-button
             handle id (list (or display (and not-attachment text))))
            (gnus-article-insert-newline)
@@ -6191,29 +6301,24 @@ Argument LINES specifies lines to be scrolled up."
     (gnus-article-next-page-1 lines)
     nil))
 
-(defmacro gnus-article-beginning-of-window ()
+(defun gnus-article-beginning-of-window ()
   "Move point to the beginning of the window.
 In Emacs, the point is placed at the line number which `scroll-margin'
 specifies."
   (if (featurep 'xemacs)
-      '(move-to-window-line 0)
-    '(move-to-window-line
-      (min (max 0 scroll-margin)
-          (max 1 (- (window-height)
-                    (if mode-line-format 1 0)
-                    (if header-line-format 1 0)
-                    2))))))
+      (move-to-window-line 0)
+    ;; There is an obscure bug in Emacs that makes it impossible to
+    ;; scroll past big pictures in the article buffer.  Try to fix
+    ;; this by adding a sanity check by counting the lines visible.
+    (when (> (count-lines (window-start) (window-end)) 30)
+      (move-to-window-line
+       (min (max 0 scroll-margin)
+           (max 1 (- (window-height)
+                     (if mode-line-format 1 0)
+                     (if header-line-format 1 0)
+                     2)))))))
 
 (defun gnus-article-next-page-1 (lines)
-  (unless (featurep 'xemacs)
-    ;; Protect against the bug that Emacs 21.x hangs up when scrolling up for
-    ;; too many number of lines if `scroll-margin' is set as two or greater.
-    (when (and (numberp lines)
-              (> lines 0)
-              (> scroll-margin 0))
-      (setq lines (min lines
-                      (max 0 (- (count-lines (window-start) (point-max))
-                                scroll-margin))))))
   (condition-case ()
       (let ((scroll-in-place nil))
        (scroll-up lines))
@@ -6292,7 +6397,7 @@ not have a face in `gnus-article-boring-faces'."
 (defun gnus-article-describe-briefly ()
   "Describe article mode commands briefly."
   (interactive)
-  (gnus-message 6 (substitute-command-keys "\\<gnus-article-mode-map>\\[gnus-article-goto-next-page]:Next page  \\[gnus-article-goto-prev-page]:Prev page  \\[gnus-article-show-summary]:Show summary  \\[gnus-info-find-node]:Run Info  \\[gnus-article-describe-briefly]:This help")))
+  (gnus-message 6 "%s" (substitute-command-keys "\\<gnus-article-mode-map>\\[gnus-article-goto-next-page]:Next page     \\[gnus-article-goto-prev-page]:Prev page  \\[gnus-article-show-summary]:Show summary  \\[gnus-info-find-node]:Run Info  \\[gnus-article-describe-briefly]:This help")))
 
 (defun gnus-article-check-buffer ()
   "Beep if not in an article buffer."
@@ -6465,6 +6570,11 @@ KEY is a string or a vector."
 ;;`gnus-agent-mode' in gnus-agent.el will define it.
 (defvar gnus-agent-summary-mode)
 (defvar gnus-draft-mode)
+;; Calling help-buffer will autoload help-mode.
+(defvar help-xref-stack-item)
+;; Emacs 22 doesn't load it in the batch mode.
+(eval-when-compile
+  (autoload 'help-buffer "help-mode"))
 
 (defun gnus-article-describe-bindings (&optional prefix)
   "Show a list of all defined keys, and their definitions.
@@ -6475,10 +6585,17 @@ then we display only bindings that start with that prefix."
   (let ((keymap (copy-keymap gnus-article-mode-map))
        (map (copy-keymap gnus-article-send-map))
        (sumkeys (where-is-internal 'gnus-article-read-summary-keys))
-       agent draft)
+       parent agent draft)
     (define-key keymap "S" map)
     (define-key map [t] nil)
     (with-current-buffer gnus-article-current-summary
+      (set-keymap-parent
+       keymap
+       (if (setq parent (keymap-parent gnus-article-mode-map))
+          (prog1
+              (setq parent (copy-keymap parent))
+            (set-keymap-parent parent (current-local-map)))
+        (current-local-map)))
       (set-keymap-parent map (key-binding "S"))
       (let (key def gnus-pick-mode)
        (while sumkeys
@@ -6508,9 +6625,7 @@ then we display only bindings that start with that prefix."
                    (with-current-buffer ,(current-buffer)
                      (gnus-article-describe-bindings prefix)))
                  ,prefix)))
-      (with-current-buffer (if (fboundp 'help-buffer)
-                              (let (help-xref-following) (help-buffer))
-                            "*Help*") ;; Emacs 21
+      (with-current-buffer (let (help-xref-following) (help-buffer))
        (setq help-xref-stack-item item)))))
 
 (defun gnus-article-reply-with-original (&optional wide)
@@ -6907,9 +7022,7 @@ groups."
       (gnus-backlog-remove-article
        (car gnus-article-current) (cdr gnus-article-current)))
     ;; Flush original article as well.
-    (when (get-buffer gnus-original-article-buffer)
-      (with-current-buffer gnus-original-article-buffer
-       (setq gnus-original-article nil)))
+    (gnus-flush-original-article-buffer)
     (when gnus-use-cache
       (gnus-cache-update-article
        (car gnus-article-current) (cdr gnus-article-current)))
@@ -6923,6 +7036,11 @@ groups."
     (set-window-point (get-buffer-window buf) (point)))
   (gnus-summary-show-article))
 
+(defun gnus-flush-original-article-buffer ()
+  (when (get-buffer gnus-original-article-buffer)
+    (with-current-buffer gnus-original-article-buffer
+      (setq gnus-original-article nil))))
+
 (defun gnus-article-edit-exit ()
   "Exit the article editing without updating."
   (interactive)
@@ -7011,46 +7129,6 @@ man page."
                 (function :tag "Other"))
   :group 'gnus-article-buttons)
 
-(defcustom gnus-ctan-url "http://tug.ctan.org/tex-archive/"
-  "Top directory of a CTAN \(Comprehensive TeX Archive Network\) archive.
-If the default site is too slow, try to find a CTAN mirror, see
-<URL:http://tug.ctan.org/tex-archive/CTAN.sites?action=/index.html>.  See also
-the variable `gnus-button-handle-ctan'."
-  :version "22.1"
-  :group 'gnus-article-buttons
-  :link '(custom-manual "(gnus)Group Parameters")
-  :type '(choice (const "http://www.tex.ac.uk/tex-archive/")
-                (const "http://tug.ctan.org/tex-archive/")
-                (const "http://www.dante.de/CTAN/")
-                (string :tag "Other")))
-
-(defcustom gnus-button-ctan-handler 'browse-url
-  "Function to use for displaying CTAN links.
-The function must take one argument, the string naming the URL."
-  :version "22.1"
-  :type '(choice (function-item :tag "Browse Url" browse-url)
-                (function :tag "Other"))
-  :group 'gnus-article-buttons)
-
-(defcustom gnus-button-handle-ctan-bogus-regexp "^/?tex-archive/\\|^/"
-  "Bogus strings removed from CTAN URLs."
-  :version "22.1"
-  :group 'gnus-article-buttons
-  :type '(choice (const "^/?tex-archive/\\|/")
-                (regexp :tag "Other")))
-
-(defcustom gnus-button-ctan-directory-regexp
-  (regexp-opt
-   (list "archive-tools" "biblio" "bibliography" "digests" "documentation"
-        "dviware" "fonts" "graphics" "help" "indexing" "info" "language"
-        "languages" "macros" "nonfree" "obsolete" "support" "systems"
-        "tds" "tools" "usergrps" "web") t)
-  "Regular expression for ctan directories.
-It should match all directories in the top level of `gnus-ctan-url'."
-  :version "22.1"
-  :group 'gnus-article-buttons
-  :type 'regexp)
-
 (defcustom gnus-button-mid-or-mail-regexp
   (concat "\\b\\(<?" gnus-button-valid-localpart-regexp "@"
          gnus-button-valid-fqdn-regexp
@@ -7308,26 +7386,6 @@ Calls `describe-variable' or `describe-function'."
        (gnus-message 1 "Cannot locale library `%s'." url)
       (find-file-read-only file))))
 
-(defun gnus-button-handle-ctan (url)
-  "Call `browse-url' when pushing a CTAN URL button."
-  (funcall
-   gnus-button-ctan-handler
-   (concat
-    gnus-ctan-url
-    (gnus-replace-in-string url gnus-button-handle-ctan-bogus-regexp ""))))
-
-(defcustom gnus-button-tex-level 5
-  "*Integer that says how many TeX-related buttons Gnus will show.
-The higher the number, the more buttons will appear and the more false
-positives are possible.  Note that you can set this variable local to
-specific groups.  Setting it higher in TeX groups is probably a good idea.
-See Info node `(gnus)Group Parameters' and the variable `gnus-parameters' on
-how to set variables in specific groups."
-  :version "22.1"
-  :group 'gnus-article-buttons
-  :link '(custom-manual "(gnus)Group Parameters")
-  :type 'integer)
-
 (defcustom gnus-button-man-level 5
   "*Integer that says how many man-related buttons Gnus will show.
 The higher the number, the more buttons will appear and the more false
@@ -7394,20 +7452,6 @@ positives are possible."
      0 (>= gnus-button-message-level 0) gnus-url-mailto 1)
     ("\\bmailto:\\([^ \n\t]+\\)"
      0 (>= gnus-button-message-level 0) gnus-url-mailto 1)
-    ;; CTAN
-    ((concat "\\bCTAN:[ \t\n]?[^>)!;:,'\n\t ]*\\("
-            gnus-button-ctan-directory-regexp
-            "[^][>)!;:,'\n\t ]+\\)")
-     0 (>= gnus-button-tex-level 1) gnus-button-handle-ctan 1)
-    ((concat "\\btex-archive/\\("
-            gnus-button-ctan-directory-regexp
-            "/[-_.a-z0-9/]+[-_./a-z0-9]+[/a-z0-9]\\)")
-     1 (>= gnus-button-tex-level 6) gnus-button-handle-ctan 1)
-    ((concat
-      "\\b\\("
-      gnus-button-ctan-directory-regexp
-      "/[-_.a-z0-9]+/[-_./a-z0-9]+[/a-z0-9]\\)")
-     1 (>= gnus-button-tex-level 8) gnus-button-handle-ctan 1)
     ;; Info Konqueror style <info:/foo/bar baz>.
     ;; Must come before " Gnus home-grown style".
     ("\\binfo://?\\([^'\">\n\t]+\\)"
@@ -7706,7 +7750,11 @@ specified by `gnus-button-alist'."
              (unless (and (eq (car entry) 'gnus-button-url-regexp)
                           (gnus-article-extend-url-button from start end))
                (gnus-article-add-button start end
-                                        'gnus-button-push from)))))))))
+                                        'gnus-button-push from)
+               (gnus-put-text-property
+                start end
+                'gnus-string (buffer-substring-no-properties
+                              start end))))))))))
 
 (defun gnus-article-extend-url-button (beg start end)
   "Extend url button if url is folded into two or more lines.
@@ -7798,7 +7846,7 @@ url is put as the `gnus-button-url' overlay property on the button."
 
 ;;; External functions:
 
-(defun gnus-article-add-button (from to fun &optional data)
+(defun gnus-article-add-button (from to fun &optional data text)
   "Create a button between FROM and TO with callback FUN and data DATA."
   (when gnus-article-button-face
     (gnus-overlay-put (gnus-make-overlay from to nil t)
@@ -7810,8 +7858,21 @@ url is put as the `gnus-button-url' overlay property on the button."
          (list 'gnus-callback fun)
          (and data (list 'gnus-data data))))
   (widget-convert-button 'link from to :action 'gnus-widget-press-button
+                        :help-echo (or text "Follow the link")
+                        :keymap gnus-url-button-map
                         :button-keymap gnus-widget-button-keymap))
 
+(defun gnus-article-copy-string ()
+  "Copy the string in the button to the kill ring."
+  (interactive)
+  (gnus-article-check-buffer)
+  (let ((data (get-text-property (point) 'gnus-string)))
+    (when data
+      (with-temp-buffer
+       (insert data)
+       (copy-region-as-kill (point-min) (point-max))
+       (message "Copied %s" data)))))
+
 ;;; Internal functions:
 
 (defun gnus-article-set-globals ()
@@ -8110,9 +8171,6 @@ url is put as the `gnus-button-url' overlay property on the button."
 
 (defvar gnus-next-page-map
   (let ((map (make-sparse-keymap)))
-    (unless (>= emacs-major-version 21)
-      ;; XEmacs doesn't care.
-      (set-keymap-parent map gnus-article-mode-map))
     (define-key map gnus-mouse-2 'gnus-button-next-page)
     (define-key map "\r" 'gnus-button-next-page)
     map))
@@ -8308,15 +8366,15 @@ For example:
   (interactive
    (list
     (or gnus-article-encrypt-protocol
-       (completing-read "Encrypt protocol: "
-                        gnus-article-encrypt-protocol-alist
-                        nil t))
+       (gnus-completing-read "Encrypt protocol"
+                              (mapcar 'car gnus-article-encrypt-protocol-alist)
+                              t))
     current-prefix-arg))
   ;; User might hit `K E' instead of `K e', so prompt once.
   (when (and gnus-article-encrypt-protocol
             gnus-novice-user)
     (unless (gnus-y-or-n-p "Really encrypt article(s)? ")
-      (error "Encrypt aborted.")))
+      (error "Encrypt aborted")))
   (let ((func (cdr (assoc protocol gnus-article-encrypt-protocol-alist))))
     (unless func
       (error "Can't find the encrypt protocol %s" protocol))
@@ -8372,9 +8430,7 @@ For example:
          (when gnus-keep-backlog
            (gnus-backlog-remove-article
             (car gnus-article-current) (cdr gnus-article-current)))
-          (when (get-buffer gnus-original-article-buffer)
-            (with-current-buffer gnus-original-article-buffer
-             (setq gnus-original-article nil)))
+         (gnus-flush-original-article-buffer)
          (when gnus-use-cache
            (gnus-cache-update-article
             (car gnus-article-current) (cdr gnus-article-current))))))))
@@ -8562,7 +8618,7 @@ For example:
      :action 'gnus-widget-press-button
      :button-keymap gnus-mime-security-button-map
      :help-echo
-     (lambda (widget/window &optional overlay pos)
+     (lambda (widget)
        ;; Needed to properly clear the message due to a bug in
        ;; wid-edit (XEmacs only).
        (when (boundp 'help-echo-owns-message)
@@ -8624,5 +8680,4 @@ For example:
 
 (run-hooks 'gnus-art-load-hook)
 
-;; arch-tag: 2654516f-6279-48f9-a83b-05c1fa450c33
 ;;; gnus-art.el ends here