* gnus-art.el (gnus-mime-save-part-and-strip): Clarify prompt.
[gnus] / lisp / gnus-art.el
index f73cd80..74821df 100644 (file)
@@ -1,6 +1,7 @@
 ;;; gnus-art.el --- article mode commands for Gnus
-;; Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005
-;;        Free Software Foundation, Inc.
+
+;; Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004,
+;;   2005 Free Software Foundation, Inc.
 
 ;; Author: Lars Magne Ingebrigtsen <larsi@gnus.org>
 ;; Keywords: news
@@ -19,8 +20,8 @@
 
 ;; You should have received a copy of the GNU General Public License
 ;; along with GNU Emacs; see the file COPYING.  If not, write to the
-;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
-;; Boston, MA 02111-1307, USA.
+;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+;; Boston, MA 02110-1301, USA.
 
 ;;; Commentary:
 
@@ -933,6 +934,19 @@ used."
   :type '(repeat (cons (string :tag "name")
                       (function))))
 
+(defcustom gnus-auto-select-part 1
+  "Advance to next MIME part when deleting or stripping parts.
+
+When 0, point will be placed on the same part as before.  When
+positive (negative), move point forward (backwards) this many
+parts.  When nil, redisplay article."
+  :version "23.0" ;; No Gnus
+  :group 'gnus-article-mime
+  :type '(choice (const nil :tag "Redisplay article.")
+                (const 1 :tag "Next part.")
+                (const 0 :tag "Current part.")
+                integer))
+
 ;;;
 ;;; The treatment variables
 ;;;
@@ -1510,10 +1524,10 @@ This requires GNU Libidn, and by default only enabled if it is found."
     (gnus-treat-date-ut gnus-article-date-ut)
     (gnus-treat-date-local gnus-article-date-local)
     (gnus-treat-date-english gnus-article-date-english)
-    (gnus-treat-date-lapsed gnus-article-date-lapsed)
     (gnus-treat-date-original gnus-article-date-original)
     (gnus-treat-date-user-defined gnus-article-date-user)
     (gnus-treat-date-iso8601 gnus-article-date-iso8601)
+    (gnus-treat-date-lapsed gnus-article-date-lapsed)
     (gnus-treat-display-x-face gnus-article-display-x-face)
     (gnus-treat-display-face gnus-article-display-face)
     (gnus-treat-hide-headers gnus-article-maybe-hide-headers)
@@ -2162,33 +2176,33 @@ unfolded."
       ;; read-only.
       (if (and wash-face-p (memq 'face gnus-article-wash-types))
          (gnus-delete-images 'face)
-       (let (face faces)
-         (save-excursion
+       (let (face faces from)
+         (save-current-buffer
            (when (and wash-face-p
-                      (progn
-                        (goto-char (point-min))
-                        (not (re-search-forward "^Face:[\t ]*" nil t)))
-                      (gnus-buffer-live-p gnus-original-article-buffer))
+                      (gnus-buffer-live-p gnus-original-article-buffer)
+                      (not (re-search-forward "^Face:[\t ]*" nil t)))
              (set-buffer gnus-original-article-buffer))
            (save-restriction
              (mail-narrow-to-head)
              (while (gnus-article-goto-header "Face")
-               (setq faces (nconc faces (list (mail-header-field-value)))))))
-         (dolist (face faces)
-           (let ((png (gnus-convert-face-to-png face))
-                 image)
-             (when png
-               (setq image
-                     (apply 'gnus-create-image png 'png t
-                            (cdr (assq 'png gnus-face-properties-alist))))
-               (gnus-article-goto-header "from")
-               (when (bobp)
-                 (insert "From: [no `from' set]\n")
-                 (forward-char -17))
-               (gnus-add-wash-type 'face)
-               (gnus-add-image 'face image)
-               (gnus-put-image image nil 'face))))))
-      )))
+               (push (mail-header-field-value) faces))))
+         (when faces
+           (goto-char (point-min))
+           (let ((from (gnus-article-goto-header "from"))
+                 png image)
+             (unless from
+               (insert "From:")
+               (setq from (point))
+               (insert "[no `from' set]\n"))
+             (while faces
+               (when (setq png (gnus-convert-face-to-png (pop faces)))
+                 (setq image
+                       (apply 'gnus-create-image png 'png t
+                              (cdr (assq 'png gnus-face-properties-alist))))
+                 (goto-char from)
+                 (gnus-add-wash-type 'face)
+                 (gnus-add-image 'face image)
+                 (gnus-put-image image nil 'face))))))))))
 
 (defun article-display-x-face (&optional force)
   "Look for an X-Face header and display it if present."
@@ -2205,13 +2219,10 @@ unfolded."
          (gnus-delete-images 'xface)
        ;; Display X-Faces.
        (let (x-faces from face)
-         (save-excursion
+         (save-current-buffer
            (when (and wash-face-p
-                      (progn
-                        (goto-char (point-min))
-                        (not (re-search-forward
-                              "^X-Face\\(-[0-9]+\\)?:[\t ]*" nil t)))
-                      (gnus-buffer-live-p gnus-original-article-buffer))
+                      (gnus-buffer-live-p gnus-original-article-buffer)
+                      (not (re-search-forward "^X-Face:[\t ]*" nil t)))
              ;; If type `W f', use gnus-original-article-buffer,
              ;; otherwise use the current buffer because displaying
              ;; RFC822 parts calls this function too.
@@ -2225,35 +2236,36 @@ unfolded."
          ;; single external face.
          (when (stringp gnus-article-x-face-command)
            (setq x-faces (list (car x-faces))))
-         (while (and (setq face (pop x-faces))
-                     gnus-article-x-face-command
-                     (or force
-                         ;; Check whether this face is censored.
-                         (not gnus-article-x-face-too-ugly)
-                         (and gnus-article-x-face-too-ugly from
-                              (not (string-match gnus-article-x-face-too-ugly
-                                                 from)))))
-           ;; We display the face.
-           (cond ((stringp gnus-article-x-face-command)
-                  ;; The command is a string, so we interpret the command
-                  ;; as a, well, command, and fork it off.
-                  (let ((process-connection-type nil))
-                    (gnus-set-process-query-on-exit-flag
-                     (start-process
-                      "article-x-face" nil shell-file-name
-                      shell-command-switch gnus-article-x-face-command)
-                     nil)
-                    (with-temp-buffer
-                      (insert face)
-                      (process-send-region "article-x-face"
-                                           (point-min) (point-max)))
-                    (process-send-eof "article-x-face")))
-                 ((functionp gnus-article-x-face-command)
-                  ;; The command is a lisp function, so we call it.
-                  (funcall gnus-article-x-face-command face))
-                 (t
-                  (error "%s is not a function"
-                         gnus-article-x-face-command)))))))))
+         (when (and x-faces
+                    gnus-article-x-face-command
+                    (or force
+                        ;; Check whether this face is censored.
+                        (not gnus-article-x-face-too-ugly)
+                        (and from
+                             (not (string-match gnus-article-x-face-too-ugly
+                                                from)))))
+           (while (setq face (pop x-faces))
+             ;; We display the face.
+             (cond ((stringp gnus-article-x-face-command)
+                    ;; The command is a string, so we interpret the command
+                    ;; as a, well, command, and fork it off.
+                    (let ((process-connection-type nil))
+                      (gnus-set-process-query-on-exit-flag
+                       (start-process
+                        "article-x-face" nil shell-file-name
+                        shell-command-switch gnus-article-x-face-command)
+                       nil)
+                      (with-temp-buffer
+                        (insert face)
+                        (process-send-region "article-x-face"
+                                             (point-min) (point-max)))
+                      (process-send-eof "article-x-face")))
+                   ((functionp gnus-article-x-face-command)
+                    ;; The command is a lisp function, so we call it.
+                    (funcall gnus-article-x-face-command face))
+                   (t
+                    (error "%s is not a function"
+                           gnus-article-x-face-command))))))))))
 
 (defun article-decode-mime-words ()
   "Decode all MIME-encoded words in the article."
@@ -4094,8 +4106,26 @@ General format specifiers can also be used.  See Info node
          (delete-region (point) (point-max))
          (mm-display-parts handles))))))
 
+(defun gnus-article-jump-to-part (n)
+  "Jump to MIME part N."
+  (interactive "P")
+  (pop-to-buffer gnus-article-buffer)
+  (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)))))
+    (unless (and (integerp n) (<= n parts) (>= n 1))
+      (setq n
+           (progn
+             (gnus-message 7 "Invalid part `%s', using %s instead."
+                           n parts)
+             parts)))
+    (gnus-message 9 "Jumping to part %s." n)
+    (gnus-article-goto-part n)))
+
 (eval-when-compile
-  (defsubst gnus-article-edit-part (handles)
+  (defsubst gnus-article-edit-part (handles &optional current-id)
     "Edit an article in order to delete a mime part.
 This function is exclusively used by `gnus-mime-save-part-and-strip'
 and `gnus-mime-delete-part', and not provided at run-time normally."
@@ -4110,7 +4140,7 @@ and `gnus-mime-delete-part', and not provided at run-time normally."
                   ',gnus-newsgroup-ignored-charsets))
              (mbl mml-buffer-list))
          (setq mml-buffer-list nil)
-         (insert-buffer gnus-original-article-buffer)
+         (insert-buffer-substring gnus-original-article-buffer)
          (mime-to-mml ',handles)
          (setq gnus-article-mime-handles nil)
          (let ((mbl1 mml-buffer-list))
@@ -4134,10 +4164,14 @@ and `gnus-mime-delete-part', and not provided at run-time normally."
        (gnus-summary-edit-article-done
         ,(or (mail-header-references gnus-current-headers) "")
         ,(gnus-group-read-only-p)
-        ,gnus-summary-buffer no-highlight)))
+        ,gnus-summary-buffer no-highlight))
+     t)
     (gnus-article-edit-done)
     (gnus-summary-expand-window)
-    (gnus-summary-show-article)))
+    (gnus-summary-show-article)
+    (when (and current-id (integerp gnus-auto-select-part))
+      (gnus-article-jump-to-part
+       (+ current-id gnus-auto-select-part)))))
 
 (defun gnus-mime-save-part-and-strip ()
   "Save the MIME part under point then replace it with an external body."
@@ -4148,29 +4182,28 @@ and `gnus-mime-delete-part', and not provided at run-time normally."
   (when (mm-complicated-handles gnus-article-mime-handles)
     (error "\
 The current article has a complicated MIME structure, giving up..."))
-  (when (gnus-yes-or-no-p "\
-Deleting parts may malfunction or destroy the article; continue? ")
-    (let* ((data (get-text-property (point) 'gnus-data))
-          file param
-          (handles gnus-article-mime-handles))
-      (setq file (and data (mm-save-part data)))
-      (when file
-       (with-current-buffer (mm-handle-buffer data)
-         (erase-buffer)
-         (insert "Content-Type: " (mm-handle-media-type data))
-         (mml-insert-parameter-string (cdr (mm-handle-type data))
-                                      '(charset))
-         (insert "\n")
-         (insert "Content-ID: " (message-make-message-id) "\n")
-         (insert "Content-Transfer-Encoding: binary\n")
-         (insert "\n"))
-       (setcdr data
-               (cdr (mm-make-handle nil
-                                    `("message/external-body"
-                                      (access-type . "LOCAL-FILE")
-                                      (name . ,file)))))
-       (set-buffer gnus-summary-buffer)
-       (gnus-article-edit-part handles)))))
+  (let* ((data (get-text-property (point) 'gnus-data))
+        (id (get-text-property (point) 'gnus-part))
+        file param
+        (handles gnus-article-mime-handles))
+    (setq file (and data (mm-save-part data "Delete MIME part and save to: ")))
+    (when file
+      (with-current-buffer (mm-handle-buffer data)
+       (erase-buffer)
+       (insert "Content-Type: " (mm-handle-media-type data))
+       (mml-insert-parameter-string (cdr (mm-handle-type data))
+                                    '(charset))
+       (insert "\n")
+       (insert "Content-ID: " (message-make-message-id) "\n")
+       (insert "Content-Transfer-Encoding: binary\n")
+       (insert "\n"))
+      (setcdr data
+             (cdr (mm-make-handle nil
+                                  `("message/external-body"
+                                    (access-type . "LOCAL-FILE")
+                                    (name . ,file)))))
+      ;; (set-buffer gnus-summary-buffer)
+      (gnus-article-edit-part handles id))))
 
 (defun gnus-mime-delete-part ()
   "Delete the MIME part under point.
@@ -4182,9 +4215,11 @@ Replace it with some information about the removed part."
   (when (mm-complicated-handles gnus-article-mime-handles)
     (error "\
 The current article has a complicated MIME structure, giving up..."))
-  (when (gnus-yes-or-no-p "\
-Deleting parts may malfunction or destroy the article; continue? ")
+  (when (or gnus-expert-user
+           (gnus-yes-or-no-p "\
+Deleting parts may malfunction or destroy the article; continue? "))
     (let* ((data (get-text-property (point) 'gnus-data))
+          (id (get-text-property (point) 'gnus-part))
           (handles gnus-article-mime-handles)
           (none "(none)")
           (description
@@ -4215,8 +4250,8 @@ Deleting parts may malfunction or destroy the article; continue? ")
                        nil `("text/plain") nil nil
                        (list "attachment")
                        (format "Deleted attachment (%s bytes)" bsize))))))
-      (set-buffer gnus-summary-buffer)
-      (gnus-article-edit-part handles))))
+      ;; (set-buffer gnus-summary-buffer)
+      (gnus-article-edit-part handles id))))
 
 (defun gnus-mime-save-part ()
   "Save the MIME part under point."
@@ -4413,7 +4448,7 @@ Compressed files like .gz and .bz2 are decompressed."
                 (and charset
                      (setq coding-system
                            (mm-charset-to-coding-system charset))
-                     (not (eq charset 'ascii))))
+                     (not (eq coding-system 'ascii))))
             (mm-decode-coding-string contents coding-system)
           (mm-string-to-multibyte contents)))
        (goto-char b)))))
@@ -4480,13 +4515,15 @@ If no internal viewer is available, use an external viewer."
     (if action-pair
        (funcall (cdr action-pair)))))
 
-(defun gnus-article-part-wrapper (n function)
+(defun gnus-article-part-wrapper (n function &optional no-handle)
   (with-current-buffer gnus-article-buffer
     (when (> n (length gnus-article-mime-handle-alist))
       (error "No such part"))
     (gnus-article-goto-part n)
-    (let ((handle (cdr (assq n gnus-article-mime-handle-alist))))
-      (funcall function handle))))
+    (if no-handle
+       (funcall function)
+      (let ((handle (cdr (assq n gnus-article-mime-handle-alist))))
+       (funcall function handle)))))
 
 (defun gnus-article-pipe-part (n)
   "Pipe MIME part N, which is the numerical prefix."
@@ -4524,6 +4561,18 @@ N is the numerical prefix."
   (interactive "p")
   (gnus-article-part-wrapper n 'gnus-mime-inline-part))
 
+(defun gnus-article-save-part-and-strip (n)
+  "Save MIME part N and replace it with an external body.
+N is the numerical prefix."
+  (interactive "p")
+  (gnus-article-part-wrapper n 'gnus-mime-save-part-and-strip t))
+
+(defun gnus-article-delete-part (n)
+  "Delete MIME part N and add some information about the removed part.
+N is the numerical prefix."
+  (interactive "p")
+  (gnus-article-part-wrapper n 'gnus-mime-delete-part t))
+
 (defun gnus-article-mime-match-handle-first (condition)
   (if condition
       (let (n)
@@ -5188,14 +5237,36 @@ Argument LINES specifies lines to be scrolled up."
     (gnus-article-next-page-1 lines)
     nil))
 
+(defmacro 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)))))))
+
 (defun gnus-article-next-page-1 (lines)
-  (let ((scroll-in-place nil))
-    (condition-case ()
-       (scroll-up lines)
-      (end-of-buffer
-       ;; Long lines may cause an end-of-buffer error.
-       (goto-char (point-max)))))
-  (move-to-window-line 0))
+  (when (and (not (featurep 'xemacs))
+            (numberp lines)
+            (> lines 0)
+            (numberp (symbol-value 'scroll-margin))
+            (> (symbol-value 'scroll-margin) 0))
+    ;; 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.
+    (setq lines (min lines
+                    (max 0 (- (count-lines (window-start) (point-max))
+                              (symbol-value 'scroll-margin))))))
+  (condition-case ()
+      (let ((scroll-in-place nil))
+       (scroll-up lines))
+    (end-of-buffer
+     ;; Long lines may cause an end-of-buffer error.
+     (goto-char (point-max))))
+  (gnus-article-beginning-of-window))
 
 (defun gnus-article-prev-page (&optional lines)
   "Show previous page of current article.
@@ -5209,13 +5280,13 @@ Argument LINES specifies lines to be scrolled down."
        (gnus-narrow-to-page -1)        ;Go to previous page.
        (goto-char (point-max))
        (recenter -1))
-    (let ((scroll-in-place nil))
-      (prog1
-         (condition-case ()
-             (scroll-down lines)
-           (beginning-of-buffer
-            (goto-char (point-min))))
-       (move-to-window-line 0)))))
+    (prog1
+       (condition-case ()
+           (let ((scroll-in-place nil))
+             (scroll-down lines))
+         (beginning-of-buffer
+          (goto-char (point-min))))
+      (gnus-article-beginning-of-window))))
 
 (defun gnus-article-only-boring-p ()
   "Decide whether there is only boring text remaining in the article.
@@ -5763,7 +5834,7 @@ groups."
        ,(or (mail-header-references gnus-current-headers) "")
        ,(gnus-group-read-only-p) ,gnus-summary-buffer no-highlight))))
 
-(defun gnus-article-edit-article (start-func exit-func)
+(defun gnus-article-edit-article (start-func exit-func &optional quiet)
   "Start editing the contents of the current article buffer."
   (let ((winconf (current-window-configuration)))
     (set-buffer gnus-article-buffer)
@@ -5776,7 +5847,8 @@ groups."
     (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")))
+    (unless quiet
+      (gnus-message 6 "C-c C-c to end edits"))))
 
 (defun gnus-article-edit-done (&optional arg)
   "Update the article edits and exit."
@@ -5821,7 +5893,7 @@ groups."
          (window-start (window-start)))
       (erase-buffer)
       (if (gnus-buffer-live-p gnus-original-article-buffer)
-         (insert-buffer gnus-original-article-buffer))
+         (insert-buffer-substring gnus-original-article-buffer))
       (let ((winconf gnus-prev-winconf))
        (kill-all-local-variables)
        (gnus-article-mode)
@@ -5865,6 +5937,14 @@ groups."
   :group 'gnus-article-buttons
   :type 'regexp)
 
+;; Regexp suggested by Felix Wiemann in <87oeuomcz9.fsf@news2.ososo.de>
+(defcustom gnus-button-valid-localpart-regexp
+  "[a-z0-9$%(*-=?[_][^<>\")!;:,{}\n\t ]*"
+  "Regular expression that matches a localpart of mail addresses or MIDs."
+  :version "22.1"
+  :group 'gnus-article-buttons
+  :type 'regexp)
+
 (defcustom gnus-button-man-handler 'manual-entry
   "Function to use for displaying man pages.
 The function must take at least one argument with a string naming the
@@ -5904,12 +5984,11 @@ The function must take one argument, the string naming the URL."
                 (regexp :tag "Other")))
 
 (defcustom gnus-button-ctan-directory-regexp
-  (concat
-   "\\(?:"
-   "biblio\\|digests\\|dviware\\|fonts\\|graphics\\|help\\|"
-   "indexing\\|info\\|language\\|macros\\|support\\|systems\\|"
-   "tds\\|tools\\|usergrps\\|web\\|nonfree\\|obsolete"
-   "\\)")
+  (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"
@@ -5917,8 +5996,7 @@ It should match all directories in the top level of `gnus-ctan-url'."
   :type 'regexp)
 
 (defcustom gnus-button-mid-or-mail-regexp
-  (concat "\\b\\(<?[a-z0-9$%(*-=?[_][^<>\")!;:,{}\n\t ]*@"
-         ;; Felix Wiemann in <87oeuomcz9.fsf@news2.ososo.de>
+  (concat "\\b\\(<?" gnus-button-valid-localpart-regexp "@"
          gnus-button-valid-fqdn-regexp
          ">?\\)\\b")
   "Regular expression that matches a message ID or a mail address."
@@ -6233,8 +6311,9 @@ positives are possible."
 (defcustom gnus-button-alist
   '(("<\\(url:[>\n\t ]*?\\)?\\(nntp\\|news\\):[>\n\t ]*\\([^>\n\t ]*@[^>\n\t ]*\\)>"
      0 (>= gnus-button-message-level 0) gnus-button-handle-news 3)
-    ("\\b\\(nntp\\|news\\):\\([^>\n\t ]*@[^>)!;:,\n\t ]*\\)" 0 t
-     gnus-button-handle-news 2)
+    ((concat "\\b\\(nntp\\|news\\):\\("
+            gnus-button-valid-localpart-regexp "@[a-z0-9.-]+[a-z]\\)")
+     0 t gnus-button-handle-news 2)
     ("\\(\\b<\\(url:[>\n\t ]*\\)?\\(nntp\\|news\\):[>\n\t ]*\\(//\\)?\\([^>\n\t ]*\\)>\\)"
      1 (>= gnus-button-message-level 0) gnus-button-fetch-group 5)
     ("\\b\\(nntp\\|news\\):\\(//\\)?\\([^'\">\n\t ]+\\)"