]> code.delx.au - gnu-emacs/blobdiff - lisp/gnus/gnus-sum.el
Merge from emacs--rel--22
[gnu-emacs] / lisp / gnus / gnus-sum.el
index a61f3df6293b20dc2f3848684161b70d241538cc..56c5fffb7e5c18d900b0e8a58a924c0e3f098213 100644 (file)
@@ -1,6 +1,7 @@
 ;;; gnus-sum.el --- summary 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, 2006, 2007 Free Software Foundation, Inc.
 
 ;; Author: Lars Magne Ingebrigtsen <larsi@gnus.org>
 ;; Keywords: news
@@ -14,7 +15,7 @@
 
 ;; GNU Emacs is distributed in the hope that it will be useful,
 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
-;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.         See the
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 ;; GNU General Public License for more details.
 
 ;; You should have received a copy of the GNU General Public License
@@ -28,7 +29,7 @@
 
 (eval-when-compile
   (require 'cl)
-  (defvar tool-bar-map))
+  (defvar tool-bar-mode))
 
 (require 'gnus)
 (require 'gnus-group)
@@ -37,6 +38,7 @@
 (require 'gnus-int)
 (require 'gnus-undo)
 (require 'gnus-util)
+(require 'gmm-utils)
 (require 'mm-decode)
 (require 'nnoo)
 
@@ -108,7 +110,7 @@ have all the sub-threads as children.
 If this variable is `adopt', Gnus will make one of the \"children\"
 the parent and mark all the step-children as such.
 If this variable is `empty', the \"children\" are printed with empty
-subject fields.         (Or rather, they will be printed with a string
+subject fields.  (Or rather, they will be printed with a string
 given by the `gnus-summary-same-subject' variable.)"
   :group 'gnus-thread
   :type '(choice (const :tag "off" nil)
@@ -256,8 +258,7 @@ simplification is selected."
   "*If non-nil, hide all threads initially.
 This can be a predicate specifier which says which threads to hide.
 If threads are hidden, you have to run the command
-`gnus-summary-show-thread' by hand or use `gnus-select-article-hook'
-to expose hidden threads."
+`gnus-summary-show-thread' by hand or select an article."
   :group 'gnus-thread
   :type '(radio (sexp :format "Non-nil\n"
                      :match (lambda (widget value)
@@ -303,12 +304,20 @@ equal will be included."
   :type 'boolean)
 
 (defcustom gnus-auto-select-first t
-  "*If non-nil, select the article under point.
-Which article this is is controlled by the `gnus-auto-select-subject'
-variable.
+  "If non-nil, select an article on group entry.
+An article is selected automatically when entering a group
+e.g. with \\<gnus-group-mode-map>\\[gnus-group-read-group], or via `gnus-summary-next-page' or
+`gnus-summary-catchup-and-goto-next-group'.
+
+Which article is selected is controlled by the variable
+`gnus-auto-select-subject'.
 
 If you want to prevent automatic selection of articles in some
 newsgroups, set the variable to nil in `gnus-select-group-hook'."
+  ;; Commands include...
+  ;; \\<gnus-group-mode-map>\\[gnus-group-read-group]
+  ;; \\<gnus-summary-mode-map>\\[gnus-summary-next-page]
+  ;; \\<gnus-summary-mode-map>\\[gnus-summary-catchup-and-goto-next-group]
   :group 'gnus-group-select
   :type '(choice (const :tag "none" nil)
                 (sexp :menu-tag "first" t)))
@@ -964,14 +973,14 @@ automatically when it is selected."
      . gnus-summary-normal-read))
   "*Controls the highlighting of summary buffer lines.
 
-A list of (FORM . FACE) pairs.  When deciding how a particular
+A list of (FORM . FACE) pairs.  When deciding how a particular
 summary line should be displayed, each form is evaluated.  The content
 of the face field after the first true form is used.  You can change
 how those summary lines are displayed, by editing the face field.
 
 You can use the following variables in the FORM field.
 
-score:        The article's score
+score:        The article's score.
 default:      The default article score.
 default-high: The default score for high scored articles.
 default-low:  The default score for low scored articles.
@@ -991,7 +1000,11 @@ which it may alter in any way."
   :group 'gnus-summary)
 
 (defvar gnus-decode-encoded-word-function 'mail-decode-encoded-word-string
-  "Variable that says which function should be used to decode a string with encoded words.")
+  "Function used to decode a string with encoded words.")
+
+(defvar gnus-decode-encoded-address-function
+  'mail-decode-encoded-address-string
+  "Function used to decode addresses with encoded words.")
 
 (defcustom gnus-extra-headers '(To Newsgroups)
   "*Extra headers to parse."
@@ -1000,7 +1013,9 @@ which it may alter in any way."
   :type '(repeat symbol))
 
 (defcustom gnus-ignored-from-addresses
-  (and user-mail-address (regexp-quote user-mail-address))
+  (and user-mail-address
+       (not (string= user-mail-address ""))
+       (regexp-quote user-mail-address))
   "*Regexp of From headers that may be suppressed in favor of To headers."
   :version "21.1"
   :group 'gnus-summary
@@ -1246,6 +1261,7 @@ the type of the variable (string, integer, character, etc).")
 (defvar gnus-newsgroup-last-mail nil)
 (defvar gnus-newsgroup-last-folder nil)
 (defvar gnus-newsgroup-last-file nil)
+(defvar gnus-newsgroup-last-directory nil)
 (defvar gnus-newsgroup-auto-expire nil)
 (defvar gnus-newsgroup-active nil)
 
@@ -1361,6 +1377,7 @@ This list will always be a subset of gnus-newsgroup-undownloaded.")
     gnus-newsgroup-begin gnus-newsgroup-end
     gnus-newsgroup-last-rmail gnus-newsgroup-last-mail
     gnus-newsgroup-last-folder gnus-newsgroup-last-file
+    gnus-newsgroup-last-directory
     gnus-newsgroup-auto-expire gnus-newsgroup-unreads
     gnus-newsgroup-unselected gnus-newsgroup-marked
     gnus-newsgroup-spam-marked
@@ -1988,6 +2005,7 @@ increase the score of each group you read."
   "r" gnus-summary-save-article-rmail
   "f" gnus-summary-save-article-file
   "b" gnus-summary-save-article-body-file
+  "B" gnus-summary-write-article-body-file
   "h" gnus-summary-save-article-folder
   "v" gnus-summary-save-article-vm
   "p" gnus-summary-pipe-output
@@ -2428,7 +2446,7 @@ gnus-summary-show-article-from-menu-as-charset-%s" cs))))
         ["Unread" gnus-summary-limit-to-unread t]
         ["Unseen" gnus-summary-limit-to-unseen t]
         ["Non-dormant" gnus-summary-limit-exclude-dormant t]
-        ["Next articles" gnus-summary-limit-to-articles t]
+        ["Next or process marked articles" gnus-summary-limit-to-articles t]
         ["Pop limit" gnus-summary-pop-limit t]
         ["Show dormant" gnus-summary-limit-include-dormant t]
         ["Hide childless dormant"
@@ -2544,47 +2562,161 @@ gnus-summary-show-article-from-menu-as-charset-%s" cs))))
 
 (defvar gnus-summary-tool-bar-map nil)
 
-;; Emacs 21 tool bar.  Should be no-op otherwise.
-(defun gnus-summary-make-tool-bar ()
-  (if (and (fboundp 'tool-bar-add-item-from-menu)
-          (default-value 'tool-bar-mode)
-          (not gnus-summary-tool-bar-map))
-      (setq gnus-summary-tool-bar-map
-           (let ((tool-bar-map (make-sparse-keymap))
-                 (load-path (mm-image-load-path)))
-             (tool-bar-add-item-from-menu
-              'gnus-summary-prev-unread "prev-ur" gnus-summary-mode-map)
-             (tool-bar-add-item-from-menu
-              'gnus-summary-next-unread "next-ur" gnus-summary-mode-map)
-             (tool-bar-add-item-from-menu
-              'gnus-summary-post-news "post" gnus-summary-mode-map)
-             (tool-bar-add-item-from-menu
-              'gnus-summary-followup-with-original "fuwo" gnus-summary-mode-map)
-             (tool-bar-add-item-from-menu
-              'gnus-summary-followup "followup" gnus-summary-mode-map)
-             (tool-bar-add-item-from-menu
-              'gnus-summary-reply-with-original "reply-wo" gnus-summary-mode-map)
-             (tool-bar-add-item-from-menu
-              'gnus-summary-reply "reply" gnus-summary-mode-map)
-             (tool-bar-add-item-from-menu
-              'gnus-summary-caesar-message "rot13" gnus-summary-mode-map)
-             (tool-bar-add-item-from-menu
-              'gnus-uu-decode-uu "uu-decode" gnus-summary-mode-map)
-             (tool-bar-add-item-from-menu
-              'gnus-summary-save-article-file "save-aif" gnus-summary-mode-map)
-             (tool-bar-add-item-from-menu
-              'gnus-summary-save-article "save-art" gnus-summary-mode-map)
-             (tool-bar-add-item-from-menu
-              'gnus-uu-post-news "uu-post" gnus-summary-mode-map)
-             (tool-bar-add-item-from-menu
-              'gnus-summary-catchup "catchup" gnus-summary-mode-map)
-             (tool-bar-add-item-from-menu
-              'gnus-summary-catchup-and-exit "cu-exit" gnus-summary-mode-map)
-             (tool-bar-add-item-from-menu
-              'gnus-summary-exit "exit-summ" gnus-summary-mode-map)
-             tool-bar-map)))
-  (if gnus-summary-tool-bar-map
-      (set (make-local-variable 'tool-bar-map) gnus-summary-tool-bar-map)))
+;; Note: The :set function in the `gnus-summary-tool-bar*' variables will only
+;; affect _new_ message buffers.  We might add a function that walks thru all
+;; summary-mode buffers and force the update.
+(defun gnus-summary-tool-bar-update (&optional symbol value)
+  "Update summary mode toolbar.
+Setter function for custom variables."
+  (setq-default gnus-summary-tool-bar-map nil)
+  (when symbol
+    ;; When used as ":set" function:
+    (set-default symbol value))
+  (when (gnus-buffer-live-p gnus-summary-buffer)
+    (with-current-buffer gnus-summary-buffer
+      (gnus-summary-make-tool-bar))))
+
+(defcustom gnus-summary-tool-bar (if (eq gmm-tool-bar-style 'gnome)
+                                    'gnus-summary-tool-bar-gnome
+                                  'gnus-summary-tool-bar-retro)
+  "Specifies the Gnus summary tool bar.
+
+It can be either a list or a symbol refering to a list.  See
+`gmm-tool-bar-from-list' for the format of the list.  The
+default key map is `gnus-summary-mode-map'.
+
+Pre-defined symbols include `gnus-summary-tool-bar-gnome' and
+`gnus-summary-tool-bar-retro'."
+  :type '(choice (const :tag "GNOME style" gnus-summary-tool-bar-gnome)
+                (const :tag "Retro look"  gnus-summary-tool-bar-retro)
+                (repeat :tag "User defined list" gmm-tool-bar-item)
+                (symbol))
+  :version "22.1" ;; Gnus 5.10.9
+  :initialize 'custom-initialize-default
+  :set 'gnus-summary-tool-bar-update
+  :group 'gnus-summary)
+
+(defcustom gnus-summary-tool-bar-gnome
+  '((gnus-summary-post-news "mail/compose" nil)
+    (gnus-summary-insert-new-articles "mail/inbox" nil
+                                     :visible (or (not gnus-agent)
+                                                  gnus-plugged))
+    (gnus-summary-reply-with-original "mail/reply")
+    (gnus-summary-reply "mail/reply" nil :visible nil)
+    (gnus-summary-followup-with-original "mail/reply-all")
+    (gnus-summary-followup "mail/reply-all" nil :visible nil)
+    (gnus-summary-mail-forward "mail/forward")
+    (gnus-summary-save-article "mail/save")
+    (gnus-summary-search-article-forward "search" nil :visible nil)
+    (gnus-summary-print-article "print")
+    (gnus-summary-tick-article-forward "flag-followup" nil :visible nil)
+    ;; Some new commands that may need more suitable icons:
+    (gnus-summary-save-newsrc "save" nil :visible nil)
+    ;; (gnus-summary-show-article "stock_message-display" nil :visible nil)
+    (gnus-summary-prev-article "left-arrow")
+    (gnus-summary-next-article "right-arrow")
+    (gnus-summary-next-page "next-page")
+    ;; (gnus-summary-enter-digest-group "right_arrow" nil :visible nil)
+    ;;
+    ;; Maybe some sort-by-... could be added:
+    ;; (gnus-summary-sort-by-author "sort-a-z" nil :visible nil)
+    ;; (gnus-summary-sort-by-date "sort-1-9" nil :visible nil)
+    (gnus-summary-mark-as-expirable
+     "delete" nil
+     :visible (gnus-check-backend-function 'request-expire-articles
+                                          gnus-newsgroup-name))
+    (gnus-summary-mark-as-spam
+     "mail/spam" t
+     :visible (and (fboundp 'spam-group-ham-contents-p)
+                  (spam-group-ham-contents-p gnus-newsgroup-name))
+     :help "Mark as spam")
+    (gnus-summary-mark-as-read-forward
+     "mail/not-spam" nil
+     :visible (and (fboundp 'spam-group-spam-contents-p)
+                  (spam-group-spam-contents-p gnus-newsgroup-name)))
+    ;;
+    (gnus-summary-exit "exit")
+    (gmm-customize-mode "preferences" t :help "Edit mode preferences")
+    (gnus-info-find-node "help"))
+  "List of functions for the summary tool bar (GNOME style).
+
+See `gmm-tool-bar-from-list' for the format of the list."
+  :type '(repeat gmm-tool-bar-item)
+  :version "22.1" ;; Gnus 5.10.9
+  :initialize 'custom-initialize-default
+  :set 'gnus-summary-tool-bar-update
+  :group 'gnus-summary)
+
+(defcustom gnus-summary-tool-bar-retro
+  '((gnus-summary-prev-unread-article "gnus/prev-ur")
+    (gnus-summary-next-unread-article "gnus/next-ur")
+    (gnus-summary-post-news "gnus/post")
+    (gnus-summary-followup-with-original "gnus/fuwo")
+    (gnus-summary-followup "gnus/followup")
+    (gnus-summary-reply-with-original "gnus/reply-wo")
+    (gnus-summary-reply "gnus/reply")
+    (gnus-summary-caesar-message "gnus/rot13")
+    (gnus-uu-decode-uu "gnus/uu-decode")
+    (gnus-summary-save-article-file "gnus/save-aif")
+    (gnus-summary-save-article "gnus/save-art")
+    (gnus-uu-post-news "gnus/uu-post")
+    (gnus-summary-catchup "gnus/catchup")
+    (gnus-summary-catchup-and-exit "gnus/cu-exit")
+    (gnus-summary-exit "gnus/exit-summ")
+    ;; Some new command that may need more suitable icons:
+    (gnus-summary-print-article "gnus/print" nil :visible nil)
+    (gnus-summary-mark-as-expirable "gnus/close" nil :visible nil)
+    (gnus-summary-save-newsrc "gnus/save" nil :visible nil)
+    ;; (gnus-summary-enter-digest-group "gnus/right_arrow" nil :visible nil)
+    (gnus-summary-search-article-forward "gnus/search" nil :visible nil)
+    ;; (gnus-summary-insert-new-articles "gnus/paste" nil :visible nil)
+    ;; (gnus-summary-toggle-threads "gnus/open" nil :visible nil)
+    ;;
+    (gnus-info-find-node "gnus/help" nil :visible nil))
+  "List of functions for the summary tool bar (retro look).
+
+See `gmm-tool-bar-from-list' for the format of the list."
+  :type '(repeat gmm-tool-bar-item)
+  :version "22.1" ;; Gnus 5.10.9
+  :initialize 'custom-initialize-default
+  :set 'gnus-summary-tool-bar-update
+  :group 'gnus-summary)
+
+(defcustom gnus-summary-tool-bar-zap-list t
+  "List of icon items from the global tool bar.
+These items are not displayed in the Gnus summary mode tool bar.
+
+See `gmm-tool-bar-from-list' for the format of the list."
+  :type 'gmm-tool-bar-zap-list
+  :version "22.1" ;; Gnus 5.10.9
+  :initialize 'custom-initialize-default
+  :set 'gnus-summary-tool-bar-update
+  :group 'gnus-summary)
+
+(defvar image-load-path)
+
+(defun gnus-summary-make-tool-bar (&optional force)
+  "Make a summary mode tool bar from `gnus-summary-tool-bar'.
+When FORCE, rebuild the tool bar."
+  (when (and (not (featurep 'xemacs))
+            (boundp 'tool-bar-mode)
+            tool-bar-mode
+            (or (not gnus-summary-tool-bar-map) force))
+    (let* ((load-path
+           (gmm-image-load-path-for-library "gnus"
+                                            "mail/save.xpm"
+                                            nil t))
+           (image-load-path (cons (car load-path)
+                                  (when (boundp 'image-load-path)
+                                    image-load-path)))
+          (map (gmm-tool-bar-from-list gnus-summary-tool-bar
+                                       gnus-summary-tool-bar-zap-list
+                                       'gnus-summary-mode-map)))
+      (when map
+       ;; Need to set `gnus-summary-tool-bar-map' because `gnus-article-mode'
+       ;; uses it's value.
+       (setq gnus-summary-tool-bar-map map))))
+  (set (make-local-variable 'tool-bar-map) gnus-summary-tool-bar-map))
 
 (defun gnus-score-set-default (var value)
   "A version of set that updates the GNU Emacs menu-bar."
@@ -3096,8 +3228,11 @@ display only a single character."
     (aset table ?\r nil)
     ;; We keep TAB as well.
     (aset table ?\t nil)
-    ;; We nix out any glyphs over 126 that are not set already.
-    (let ((i 256))
+    ;; We nix out any glyphs 127 through 255, or 127 through 159 in
+    ;; Emacs 23 (unicode), that are not set already.
+    (let ((i (if (ignore-errors (= (make-char 'latin-iso8859-1 160) 160))
+                160
+              256)))
       (while (>= (setq i (1- i)) 127)
        ;; Only modify if the entry is nil.
        (unless (aref table i)
@@ -3216,16 +3351,17 @@ buffer that was in action when the last article was fetched."
       t
     (not (cdr (gnus-data-find-list article)))))
 
-(defun gnus-make-thread-indent-array ()
-  (let ((n 200))
-    (unless (and gnus-thread-indent-array
-                (= gnus-thread-indent-level gnus-thread-indent-array-level))
-      (setq gnus-thread-indent-array (make-vector 201 "")
-           gnus-thread-indent-array-level gnus-thread-indent-level)
-      (while (>= n 0)
-       (aset gnus-thread-indent-array n
-             (make-string (* n gnus-thread-indent-level) ? ))
-       (setq n (1- n))))))
+(defun gnus-make-thread-indent-array (&optional n)
+  (when (or n
+           (progn (setq n 200) nil)
+           (null gnus-thread-indent-array)
+           (/= gnus-thread-indent-level gnus-thread-indent-array-level))
+    (setq gnus-thread-indent-array (make-vector (1+ n) "")
+         gnus-thread-indent-array-level gnus-thread-indent-level)
+    (while (>= n 0)
+      (aset gnus-thread-indent-array n
+           (make-string (* n gnus-thread-indent-level) ? ))
+      (setq n (1- n)))))
 
 (defun gnus-update-summary-mark-positions ()
   "Compute where the summary marks are to go."
@@ -3313,7 +3449,7 @@ buffer that was in action when the last article was fetched."
              (concat "-> "
                      (inline
                        (gnus-summary-extract-address-component
-                        (funcall gnus-decode-encoded-word-function to)))))
+                        (funcall gnus-decode-encoded-address-function to)))))
             ((setq newsgroups (cdr (assq 'Newsgroups extra-headers)))
              (concat "=> " newsgroups)))))
      (inline (gnus-summary-extract-address-component gnus-tmp-from)))))
@@ -3324,6 +3460,9 @@ buffer that was in action when the last article was fetched."
                                 gnus-tmp-expirable gnus-tmp-subject-or-nil
                                 &optional gnus-tmp-dummy gnus-tmp-score
                                 gnus-tmp-process)
+  (if (>= gnus-tmp-level (length gnus-thread-indent-array))
+      (gnus-make-thread-indent-array (max (* 2 (length gnus-thread-indent-array))
+                                         gnus-tmp-level)))
   (let* ((gnus-tmp-indentation (aref gnus-thread-indent-array gnus-tmp-level))
         (gnus-tmp-lines (mail-header-lines gnus-tmp-header))
         (gnus-tmp-score (or gnus-tmp-score gnus-summary-default-score 0))
@@ -3589,16 +3728,10 @@ If NO-DISPLAY, don't generate a summary buffer."
       (when gnus-build-sparse-threads
        (gnus-build-sparse-threads))
       ;; Find the initial limit.
-      (if gnus-show-threads
-         (if show-all
-             (let ((gnus-newsgroup-dormant nil))
-               (gnus-summary-initial-limit show-all))
+      (if show-all
+         (let ((gnus-newsgroup-dormant nil))
            (gnus-summary-initial-limit show-all))
-       ;; When unthreaded, all articles are always shown.
-       (setq gnus-newsgroup-limit
-             (mapcar
-              (lambda (header) (mail-header-number header))
-              gnus-newsgroup-headers)))
+       (gnus-summary-initial-limit show-all))
       ;; Generate the summary buffer.
       (unless no-display
        (gnus-summary-prepare))
@@ -3854,7 +3987,7 @@ If NO-DISPLAY, don't generate a summary buffer."
     infloop))
 
 (defun gnus-make-threads ()
-  "Go through the dependency hashtb and find the roots.         Return all threads."
+  "Go through the dependency hashtb and find the roots.  Return all threads."
   (let (threads)
     (while (catch 'infloop
             (mapatoms
@@ -4065,7 +4198,7 @@ Returns HEADER if it was entered in the DEPENDENCIES.  Returns nil otherwise."
                   (error x))
                 (condition-case ()     ; from
                     (gnus-remove-odd-characters
-                     (funcall gnus-decode-encoded-word-function
+                     (funcall gnus-decode-encoded-address-function
                               (setq x (nnheader-nov-field))))
                   (error x))
                 (nnheader-nov-field)   ; date
@@ -4360,7 +4493,7 @@ If LINE, insert the rebuilt thread starting on line LINE."
     ;; First go up in this thread until we find the root.
     (setq last-id (gnus-root-id id)
          headers (message-flatten-list (gnus-id-to-thread last-id)))
-    ;; We have now found the real root of this thread. It might have
+    ;; We have now found the real root of this thread.  It might have
     ;; been gathered into some loose thread, so we have to search
     ;; through the threads to find the thread we wanted.
     (let ((threads gnus-newsgroup-threads)
@@ -4428,23 +4561,46 @@ If LINE, insert the rebuilt thread starting on line LINE."
              (1+ (gnus-point-at-eol))
            (gnus-delete-line)))))))
 
-(defun gnus-sort-threads-1 (threads func)
+(defun gnus-sort-threads-recursive (threads func)
   (sort (mapcar (lambda (thread)
                  (cons (car thread)
                        (and (cdr thread)
-                            (gnus-sort-threads-1 (cdr thread) func))))
+                            (gnus-sort-threads-recursive (cdr thread) func))))
                threads) func))
 
+(defun gnus-sort-threads-loop (threads func)
+  (let* ((superthread (cons nil threads))
+        (stack (list (cons superthread threads)))
+        remaining-threads thread)
+    (while stack
+      (setq remaining-threads (cdr (car stack)))
+      (if remaining-threads
+         (progn (setq thread (car remaining-threads))
+                (setcdr (car stack) (cdr remaining-threads))
+                (if (cdr thread)
+                    (push (cons thread (cdr thread)) stack)))
+       (setq thread (caar stack))
+       (setcdr thread (sort (cdr thread) func))
+       (pop stack)))
+    (cdr superthread)))
+
 (defun gnus-sort-threads (threads)
   "Sort THREADS."
   (if (not gnus-thread-sort-functions)
       threads
     (gnus-message 8 "Sorting threads...")
-    (let ((max-lisp-eval-depth 5000))
-      (prog1 (gnus-sort-threads-1
-        threads
-        (gnus-make-sort-function gnus-thread-sort-functions))
-        (gnus-message 8 "Sorting threads...done")))))
+    (prog1
+       (condition-case nil
+           (let ((max-lisp-eval-depth (max max-lisp-eval-depth 5000)))
+             (gnus-sort-threads-recursive
+              threads (gnus-make-sort-function gnus-thread-sort-functions)))
+         ;; Even after binding max-lisp-eval-depth, the recursive
+         ;; sorter might fail for very long threads.  In that case,
+         ;; try using a (less well-tested) non-recursive sorter.
+         (error (gnus-sort-threads-loop
+                 threads (gnus-make-sort-function
+                          gnus-thread-sort-functions))))
+      (gnus-message 8 "Sorting threads...done"))))
 
 (defun gnus-sort-articles (articles)
   "Sort ARTICLES."
@@ -4869,6 +5025,10 @@ or a straight list of headers."
                      gnus-tmp-closing-bracket ?\>)
              (setq gnus-tmp-opening-bracket ?\[
                    gnus-tmp-closing-bracket ?\]))
+           (if (>= gnus-tmp-level (length gnus-thread-indent-array))
+               (gnus-make-thread-indent-array
+                (max (* 2 (length gnus-thread-indent-array))
+                     gnus-tmp-level)))
            (setq
             gnus-tmp-indentation
             (aref gnus-thread-indent-array gnus-tmp-level)
@@ -5087,13 +5247,13 @@ If SELECT-ARTICLES, only select those articles from GROUP."
          (when (equal major-mode 'gnus-summary-mode)
            (gnus-kill-buffer (current-buffer)))
          (error "Couldn't activate group %s: %s"
-                group (gnus-status-message group))))
+                (gnus-group-decoded-name group) (gnus-status-message group))))
 
     (unless (gnus-request-group group t)
       (when (equal major-mode 'gnus-summary-mode)
        (gnus-kill-buffer (current-buffer)))
       (error "Couldn't request group %s: %s"
-            group (gnus-status-message group)))
+            (gnus-group-decoded-name group) (gnus-status-message group)))
 
     (when gnus-agent
       (gnus-agent-possibly-alter-active group (gnus-active group) info)
@@ -5299,8 +5459,7 @@ If SELECT-ARTICLES, only select those articles from GROUP."
 
 (defun gnus-articles-to-read (group &optional read-all)
   "Find out what articles the user wants to read."
-  (let* ((display (gnus-group-find-parameter group 'display))
-        (articles
+  (let* ((articles
          ;; Select all articles if `read-all' is non-nil, or if there
          ;; are no unread articles.
          (if (or read-all
@@ -5591,8 +5750,9 @@ If WHERE is `summary', the summary mode line format will be used."
        (let* ((mformat (symbol-value
                         (intern
                          (format "gnus-%s-mode-line-format-spec" where))))
-              (gnus-tmp-group-name (gnus-group-decoded-name
-                                    gnus-newsgroup-name))
+              (gnus-tmp-group-name (gnus-mode-string-quote
+                                    (gnus-group-decoded-name
+                                     gnus-newsgroup-name)))
               (gnus-tmp-article-number (or gnus-current-article 0))
               (gnus-tmp-unread gnus-newsgroup-unreads)
               (gnus-tmp-unread-and-unticked (length gnus-newsgroup-unreads))
@@ -5806,7 +5966,7 @@ The resulting hash table is returned, or nil if no Xrefs were found."
       (let ((case-fold-search t)
            in-reply-to header p lines chars)
        (goto-char (point-min))
-       ;; Search to the beginning of the next header.  Error messages
+       ;; Search to the beginning of the next header.  Error messages
        ;; do not begin with 2 or 3.
        (while (re-search-forward "^[23][0-9]+ " nil t)
          (setq id nil
@@ -5814,7 +5974,7 @@ The resulting hash table is returned, or nil if no Xrefs were found."
          ;; This implementation of this function, with nine
          ;; search-forwards instead of the one re-search-forward and
          ;; a case (which basically was the old function) is actually
-         ;; about twice as fast, even though it looks messier.  You
+         ;; about twice as fast, even though it looks messier.  You
          ;; can't have everything, I guess.  Speed and elegance
          ;; doesn't always go hand in hand.
          (setq
@@ -5840,7 +6000,7 @@ The resulting hash table is returned, or nil if no Xrefs were found."
            (progn
              (goto-char p)
              (if (search-forward "\nfrom:" nil t)
-                 (funcall gnus-decode-encoded-word-function
+                 (funcall gnus-decode-encoded-address-function
                           (nnheader-header-value))
                "(nobody)"))
            ;; Date.
@@ -5957,7 +6117,8 @@ Return a list of headers that match SEQUENCE (see
        (allp (cond
               ((eq gnus-read-all-available-headers t)
                t)
-              ((stringp gnus-read-all-available-headers)
+              ((and (stringp gnus-read-all-available-headers)
+                    group)
                (string-match gnus-read-all-available-headers group))
               (t
                nil)))
@@ -6112,7 +6273,7 @@ current article will be taken into consideration."
       (let ((max (max (point) (mark)))
            articles article)
        (save-excursion
-         (goto-char (min (min (point) (mark))))
+         (goto-char (min (point) (mark)))
          (while
              (and
               (push (setq article (gnus-summary-article-number)) articles)
@@ -6450,10 +6611,12 @@ displayed, no centering will be performed."
          (setq nlast (if (atom (cadr read)) (cadr read) (caadr read)))
          (setq read (cdr read)))))
     ;; And add the last unread articles.
-    (cond ((< first last)
-           (push (cons first last) unread))
-          ((= first last)
-           (push first unread)))
+    (cond ((not (and first last))
+          nil)
+         ((< first last)
+          (push (cons first last) unread))
+         ((= first last)
+          (push first unread)))
     ;; Return the sequence of unread articles.
     (delq 0 (nreverse unread))))
 
@@ -7178,6 +7341,9 @@ If UNREAD, only unread articles are selected.
 If SUBJECT, only articles with SUBJECT are selected.
 If BACKWARD, the previous article is selected instead of the next."
   (interactive "P")
+  ;; Make sure we are in the summary buffer.
+  (unless (eq major-mode 'gnus-summary-mode)
+    (set-buffer gnus-summary-buffer))
   (cond
    ;; Is there such an article?
    ((and (gnus-summary-search-forward unread subject backward)
@@ -7696,8 +7862,8 @@ articles that are younger than AGE days."
           (gnus-completing-read-with-default
            (symbol-name (car gnus-extra-headers))
            (if current-prefix-arg
-               "Exclude extra header:"
-             "Limit extra header:")
+               "Exclude extra header"
+             "Limit extra header")
            (mapcar (lambda (x)
                      (cons (symbol-name x) x))
                    gnus-extra-headers)
@@ -7832,7 +7998,7 @@ article."
   (interactive)
   (let ((gnus-verbose (max 6 gnus-verbose)))
     (if (not gnus-newsgroup-dormant)
-       (gnus-message 3 "No cached articles for this group")
+       (gnus-message 3 "No dormant articles for this group")
       (gnus-summary-goto-subjects gnus-newsgroup-dormant))))
 
 (defun gnus-summary-limit-include-dormant ()
@@ -8041,7 +8207,7 @@ fetch-old-headers verbiage, and so on."
   ;; will really go down to a leaf article first, before slowly
   ;; working its way up towards the root.
   (when thread
-    (let* ((max-lisp-eval-depth 5000)
+    (let* ((max-lisp-eval-depth (max 5000 max-lisp-eval-depth))
           (children
           (if (cdr thread)
               (apply '+ (mapcar 'gnus-summary-limit-children
@@ -8330,10 +8496,11 @@ to guess what the document format is."
        ;; the parent article.
        (when (setq to-address (or (gnus-fetch-field "reply-to")
                                   (gnus-fetch-field "from")))
-         (setq params (append
-                       (list (cons 'to-address
-                                   (funcall gnus-decode-encoded-word-function
-                                            to-address))))))
+         (setq params
+               (append
+                (list (cons 'to-address
+                            (funcall gnus-decode-encoded-address-function
+                                     to-address))))))
        (setq dig (nnheader-set-temp-buffer " *gnus digest buffer*"))
        (insert-buffer-substring gnus-original-article-buffer)
        ;; Remove lines that may lead nndoc to misinterpret the
@@ -9029,7 +9196,7 @@ ACTION can be either `move' (the default), `crosspost' or `copy'."
             (gnus-request-article-this-buffer article gnus-newsgroup-name)
             (when (consp (setq art-group
                                (gnus-request-accept-article
-                                to-newsgroup select-method (not articles))))
+                                to-newsgroup select-method (not articles) t)))
               (setq new-xref (concat new-xref " " (car art-group)
                                      ":"
                                      (number-to-string (cdr art-group))))
@@ -9037,7 +9204,7 @@ ACTION can be either `move' (the default), `crosspost' or `copy'."
               ;; it and replace the new article.
               (nnheader-replace-header "Xref" new-xref)
               (gnus-request-replace-article
-               (cdr art-group) to-newsgroup (current-buffer))
+               (cdr art-group) to-newsgroup (current-buffer) t)
               art-group))))))
       (cond
        ((not art-group)
@@ -9135,7 +9302,7 @@ ACTION can be either `move' (the default), `crosspost' or `copy'."
              (gnus-request-article-this-buffer article gnus-newsgroup-name)
              (nnheader-replace-header "Xref" new-xref)
              (gnus-request-replace-article
-              article gnus-newsgroup-name (current-buffer))))
+              article gnus-newsgroup-name (current-buffer) t)))
 
          ;; run the move/copy/crosspost/respool hook
          (run-hook-with-args 'gnus-summary-article-move-hook
@@ -9218,7 +9385,7 @@ latter case, they will be copied into the relevant groups."
                                        gnus-newsgroup-name)))))
                (method
                 (gnus-completing-read-with-default
-                 methname "What backend do you want to use when respooling?"
+                 methname "Backend to use when respooling"
                  methods nil t nil 'gnus-mail-method-history))
                ms)
           (cond
@@ -9384,14 +9551,14 @@ deleted forever, right now."
   (interactive)
   (or gnus-expert-user
       (gnus-yes-or-no-p
-       "Are you really, really, really sure you want to delete all these messages? ")
+       "Are you really, really sure you want to delete all expirable messages? ")
       (error "Phew!"))
   (gnus-summary-expire-articles t))
 
 ;; Suggested by Jack Vinson <vinson@unagi.cis.upenn.edu>.
 (defun gnus-summary-delete-article (&optional n)
   "Delete the N next (mail) articles.
-This command actually deletes articles.         This is not a marking
+This command actually deletes articles.  This is not a marking
 command.  The article will disappear forever from your life, never to
 return.
 
@@ -9561,7 +9728,8 @@ groups."
            (save-excursion
              (save-restriction
                (message-narrow-to-head)
-               (let ((head (buffer-string))
+               (let ((head (buffer-substring-no-properties
+                            (point-min) (point-max)))
                      header)
                  (with-temp-buffer
                    (insert (format "211 %d Article retrieved.\n"
@@ -10344,9 +10512,10 @@ The number of articles marked as read is returned."
                        gnus-newsgroup-dormant nil))
                (setq gnus-newsgroup-unreads
                      (gnus-sorted-nunion
-                       (gnus-intersection gnus-newsgroup-unreads
-                                          gnus-newsgroup-downloadable)
-                       gnus-newsgroup-unfetched)))
+                       (gnus-sorted-intersection gnus-newsgroup-unreads
+                                                gnus-newsgroup-downloadable)
+                      (gnus-sorted-difference gnus-newsgroup-unfetched
+                                              gnus-newsgroup-cached))))
            ;; We actually mark all articles as canceled, which we
            ;; have to do when using auto-expiry or adaptive scoring.
            (gnus-summary-show-all-threads)
@@ -10611,7 +10780,9 @@ Returns nil if no thread was there to be shown."
 (defun gnus-map-articles (predicate articles)
   "Map PREDICATE over ARTICLES and return non-nil if any predicate is non-nil."
   (apply 'gnus-or (mapcar predicate
-                         (mapcar 'gnus-summary-article-header articles))))
+                         (mapcar (lambda (number)
+                                   (gnus-summary-article-header number))
+                                 articles))))
 
 (defun gnus-summary-hide-all-threads (&optional predicate)
   "Hide all thread subtrees.
@@ -10867,12 +11038,26 @@ If N is a positive number, save the N next articles.
 If N is a negative number, save the N previous articles.
 If N is nil and any articles have been marked with the process mark,
 save those articles instead.
-The variable `gnus-default-article-saver' specifies the saver function."
+The variable `gnus-default-article-saver' specifies the saver function.
+
+If the optional second argument NOT-SAVED is non-nil, articles saved
+will not be marked as saved."
   (interactive "P")
+  (require 'gnus-art)
   (let* ((articles (gnus-summary-work-articles n))
         (save-buffer (save-excursion
                        (nnheader-set-temp-buffer " *Gnus Save*")))
         (num (length articles))
+        ;; Whether to save decoded articles or raw articles.
+        (decode (when gnus-article-save-coding-system
+                  (get gnus-default-article-saver :decode)))
+        ;; When saving many articles in a single file, use the other
+        ;; function to save articles other than the first one.
+        (saver2 (get gnus-default-article-saver :function))
+        (gnus-prompt-before-saving (if saver2
+                                       t
+                                     gnus-prompt-before-saving))
+        (gnus-default-article-saver gnus-default-article-saver)
         header file)
     (dolist (article articles)
       (setq header (gnus-summary-article-header article))
@@ -10883,17 +11068,25 @@ The variable `gnus-default-article-saver' specifies the saver function."
            (gnus-message 1 "Article %d is unsaveable" article))
        ;; This is a real article.
        (save-window-excursion
-         (let ((gnus-display-mime-function nil)
-               (gnus-article-prepare-hook nil))
-           (gnus-summary-select-article t nil nil article)))
+         (let ((gnus-display-mime-function (when decode
+                                             gnus-display-mime-function))
+               (gnus-article-prepare-hook (when decode
+                                            gnus-article-prepare-hook)))
+           (gnus-summary-select-article t nil nil article)
+           (gnus-summary-goto-subject article)))
        (save-excursion
          (set-buffer save-buffer)
          (erase-buffer)
-         (insert-buffer-substring gnus-original-article-buffer))
+         (insert-buffer-substring (if decode
+                                      gnus-article-buffer
+                                    gnus-original-article-buffer)))
        (setq file (gnus-article-save save-buffer file num))
        (gnus-summary-remove-process-mark article)
        (unless not-saved
-         (gnus-summary-set-saved-mark article))))
+         (gnus-summary-set-saved-mark article)))
+      (when saver2
+       (setq gnus-default-article-saver saver2
+             saver2 nil)))
     (gnus-kill-buffer save-buffer)
     (gnus-summary-position-point)
     (gnus-set-mode-line 'summary)
@@ -10917,7 +11110,7 @@ If HEADERS (the symbolic prefix), include the headers, too."
       (gnus-configure-windows 'pipe))))
 
 (defun gnus-summary-save-article-mail (&optional arg)
-  "Append the current article to an mail file.
+  "Append the current article to a Unix mail box file.
 If N is a positive number, save the N next articles.
 If N is a negative number, save the N previous articles.
 If N is nil and any articles have been marked with the process mark,
@@ -10971,6 +11164,17 @@ save those articles instead."
   (let ((gnus-default-article-saver 'gnus-summary-save-body-in-file))
     (gnus-summary-save-article arg)))
 
+(defun gnus-summary-write-article-body-file (&optional arg)
+  "Write the current article body to a file, deleting the previous file.
+If N is a positive number, save the N next articles.
+If N is a negative number, save the N previous articles.
+If N is nil and any articles have been marked with the process mark,
+save those articles instead."
+  (interactive "P")
+  (require 'gnus-art)
+  (let ((gnus-default-article-saver 'gnus-summary-write-body-to-file))
+    (gnus-summary-save-article arg)))
+
 (defun gnus-summary-muttprint (&optional arg)
   "Print the current article using Muttprint.
 If N is a positive number, save the N next articles.
@@ -11043,7 +11247,7 @@ save those articles instead."
   (let* ((split-name (gnus-get-split-value gnus-move-split-methods))
         (minibuffer-confirm-incomplete nil) ; XEmacs
         (prom
-         (format "%s %s to:"
+         (format "%s %s to"
                  prompt
                  (if (> (length articles) 1)
                      (format "these %d articles" (length articles))