Merge from gnus--rel--5.10
[gnus] / lisp / gnus-sum.el
index 2e0af70..11e84c2 100644 (file)
@@ -15,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
@@ -114,7 +114,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)
@@ -1852,6 +1852,7 @@ increase the score of each group you read."
   "/" gnus-summary-limit-to-subject
   "n" gnus-summary-limit-to-articles
   "b" gnus-summary-limit-to-bodies
+  "h" gnus-summary-limit-to-headers
   "w" gnus-summary-pop-limit
   "s" gnus-summary-limit-to-subject
   "a" gnus-summary-limit-to-author
@@ -3442,16 +3443,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) ?\s))
+      (setq n (1- n)))))
 
 (defun gnus-update-summary-mark-positions ()
   "Compute where the summary marks are to go."
@@ -3557,6 +3559,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))
@@ -4077,7 +4082,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
@@ -4581,7 +4586,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)
@@ -4649,23 +4654,46 @@ If LINE, insert the rebuilt thread starting on line LINE."
              (1+ (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."
@@ -5113,6 +5141,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)
@@ -5328,13 +5360,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)
@@ -5832,8 +5864,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))
@@ -6045,7 +6078,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
@@ -6053,7 +6086,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
@@ -6564,9 +6597,10 @@ displayed, no centering will be performed."
        ;; possible valid number, or the second line from the top,
        ;; whichever is the least.
        (let ((top-pos (save-excursion (forward-line (- top)) (point))))
+         (message "%s" top-pos)
          (if (> bottom top-pos)
              ;; Keep the second line from the top visible
-             (set-window-start window top-pos t)
+             (set-window-start window top-pos)
            ;; Try to keep the bottom line visible; if it's partially
            ;; obscured, either scroll one more line to make it fully
            ;; visible, or revert to using TOP-POS.
@@ -7907,6 +7941,27 @@ To and Cc headers are checked.  You need to include them in
             (gnus-summary-limit articles))
       (gnus-summary-position-point))))
 
+(defun gnus-summary-limit-strange-charsets-predicate (header)
+  (let ((string (concat (mail-header-subject header)
+                       (mail-header-from header)))
+       charset found)
+    (dotimes (i (1- (length string)))
+      (setq charset (format "%s" (char-charset (aref string (1+ i)))))
+      (when (string-match "unicode\\|big\\|japanese" charset)
+       (setq found t)))
+    found))
+
+(defun gnus-summary-limit-to-predicate (predicate)
+  "Limit to articles where PREDICATE returns non-nil.
+PREDICATE will be called with the header structures of the
+articles."
+  (let ((articles nil)
+       (case-fold-search t))
+    (dolist (header gnus-newsgroup-headers)
+      (when (funcall predicate header)
+       (push (mail-header-number header) articles)))
+    (gnus-summary-limit (nreverse articles))))
+
 (defun gnus-summary-limit-to-age (age &optional younger-p)
   "Limit the summary buffer to articles that are older than (or equal) AGE days.
 If YOUNGER-P (the prefix) is non-nil, limit the summary buffer to
@@ -8012,7 +8067,13 @@ If ALL is non-nil, limit strictly to unread articles."
           gnus-duplicate-mark gnus-souped-mark)
      'reverse)))
 
-(defun gnus-summary-limit-to-bodies (match &optional reverse)
+(defun gnus-summary-limit-to-headers (match &optional reverse)
+  "Limit the summary buffer to articles that have headers that match MATCH.
+If REVERSE (the prefix), limit to articles that don't match."
+  (interactive "sMatch headers (regexp): \nP")
+  (gnus-summary-limit-to-bodies match reverse t))
+
+(defun gnus-summary-limit-to-bodies (match &optional reverse headersp)
   "Limit the summary buffer to articles that have bodies that match MATCH.
 If REVERSE (the prefix), limit to articles that don't match."
   (interactive "sMatch body (regexp): \nP")
@@ -8033,7 +8094,9 @@ If REVERSE (the prefix), limit to articles that don't match."
        (set-buffer gnus-article-buffer)
        (article-goto-body)
        (let* ((case-fold-search t)
-              (found (re-search-forward match nil t)))
+              (found (if headersp
+                         (re-search-backward match nil t)
+                       (re-search-forward match nil t))))
          (when (or (and found
                         (not reverse))
                    (and (not found)
@@ -8346,7 +8409,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
@@ -9338,16 +9401,16 @@ ACTION can be either `move' (the default), `crosspost' or `copy'."
                (to-method (gnus-find-method-for-group
                            to-newsgroup))
                (move-is-internal (gnus-method-equal from-method to-method)))
-        (gnus-request-move-article
-         article                       ; Article to move
-         gnus-newsgroup-name           ; From newsgroup
-         (nth 1 (gnus-find-method-for-group
-                 gnus-newsgroup-name)) ; Server
-         (list 'gnus-request-accept-article
-               to-newsgroup (list 'quote select-method)
-               (not articles) t)       ; Accept form
-         (not articles)                ; Only save nov last time
-         move-is-internal)))           ; is this move internal?
+          (gnus-request-move-article
+           article                     ; Article to move
+           gnus-newsgroup-name         ; From newsgroup
+           (nth 1 (gnus-find-method-for-group
+                   gnus-newsgroup-name)) ; Server
+           (list 'gnus-request-accept-article
+                 to-newsgroup (list 'quote select-method)
+                 (not articles) t)     ; Accept form
+           (not articles)              ; Only save nov last time
+           move-is-internal)))         ; is this move internal?
        ;; Copy the article.
        ((eq action 'copy)
         (save-excursion
@@ -9380,7 +9443,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))))
@@ -9388,7 +9451,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)
@@ -9484,7 +9547,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
@@ -9497,7 +9560,7 @@ ACTION can be either `move' (the default), `crosspost' or `copy'."
 
        ;;;!!!Why is this necessary?
        (set-buffer gnus-summary-buffer)
-       
+
        (gnus-summary-goto-subject article)
        (when (eq action 'move)
          (gnus-summary-mark-article article gnus-canceled-mark))))
@@ -9509,7 +9572,7 @@ ACTION can be either `move' (the default), `crosspost' or `copy'."
       (set-buffer gnus-group-buffer)
       (let ((gnus-group-marked to-groups))
        (gnus-group-get-new-news-this-group nil t)))
-    
+
     (gnus-kill-buffer copy-buf)
     (gnus-summary-position-point)
     (gnus-set-mode-line 'summary)))
@@ -10697,8 +10760,8 @@ 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-sorted-intersection gnus-newsgroup-unreads
+                                                gnus-newsgroup-downloadable)
                        gnus-newsgroup-unfetched)))
            ;; We actually mark all articles as canceled, which we
            ;; have to do when using auto-expiry or adaptive scoring.