Synch with Oort Gnus.
[elisp/gnus.git-] / lisp / gnus-sum.el
index b061c2a..8e05e70 100644 (file)
@@ -120,6 +120,11 @@ given by the `gnus-summary-same-subject' variable.)"
                 (const adopt)
                 (const empty)))
 
+(defcustom gnus-summary-make-false-root-always t
+  "Always make a false dummy root."
+  :group 'gnus-thread
+  :type 'boolean)
+
 (defcustom gnus-summary-gather-exclude-subject "^ *$\\|^(none)$"
   "*A regexp to match subjects to be excluded from loose thread gathering.
 As loose thread gathering is done on subjects only, that means that
@@ -609,7 +614,7 @@ list of parameters to that command."
   :type 'boolean)
 
 (defcustom gnus-summary-dummy-line-format
-  "  %(:                          :%) %S\n"
+  "   %(:                             :%) %S\n"
   "*The format specification for the dummy roots in the summary buffer.
 It works along the same lines as a normal formatting string,
 with some simple extensions.
@@ -883,11 +888,11 @@ automatically when it is selected."
   '(((eq mark gnus-canceled-mark)
      . gnus-summary-cancelled-face)
     ((and uncached (> score default-high))
-     . gnus-summary-high-uncached-face)
+     . gnus-summary-high-undownloaded-face)
     ((and uncached (< score default-low))
-     . gnus-summary-low-uncached-face)
+     . gnus-summary-low-undownloaded-face)
     (uncached
-     . gnus-summary-normal-uncached-face)
+     . gnus-summary-normal-undownloaded-face)
     ((and (> score default-high)
          (or (eq mark gnus-dormant-mark)
              (eq mark gnus-ticked-mark)))
@@ -1375,7 +1380,12 @@ buffers. For example:
 ")
 
 ;; Byte-compiler warning.
-(eval-when-compile (defvar gnus-article-mode-map))
+;(eval-when-compile (defvar gnus-article-mode-map))
+(eval-when-compile
+  (let ((features (cons 'gnus-sum features)))
+    (require 'gnus)
+    (require 'gnus-agent)
+    (require 'gnus-art)))
 
 ;; Subject simplification.
 
@@ -1728,7 +1738,8 @@ increase the score of each group you read."
 
   (gnus-define-keys (gnus-summary-buffer-map "Y" gnus-summary-mode-map)
     "g" gnus-summary-prepare
-    "c" gnus-summary-insert-cached-articles)
+    "c" gnus-summary-insert-cached-articles
+    "d" gnus-summary-insert-dormant-articles)
 
   (gnus-define-keys (gnus-summary-exit-map "Z" gnus-summary-mode-map)
     "c" gnus-summary-catchup-and-exit
@@ -2171,6 +2182,7 @@ gnus-summary-show-article-from-menu-as-charset-%s" cs))))
     (easy-menu-define
      gnus-summary-thread-menu gnus-summary-mode-map ""
      '("Threads"
+       ["Find all messages in thread" gnus-summary-refer-thread t]
        ["Toggle threading" gnus-summary-toggle-threads t]
        ["Hide threads" gnus-summary-hide-all-threads t]
        ["Show threads" gnus-summary-show-all-threads t]
@@ -2276,7 +2288,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]
-       ["Articles" gnus-summary-limit-to-articles t]
+       ["Next 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"
@@ -2353,6 +2365,7 @@ gnus-summary-show-article-from-menu-as-charset-%s" cs))))
        ("Regeneration"
        ["Regenerate" gnus-summary-prepare t]
        ["Insert cached articles" gnus-summary-insert-cached-articles t]
+       ["Insert dormant articles" gnus-summary-insert-dormant-articles t]
        ["Toggle threading" gnus-summary-toggle-threads t])
        ["See old articles" gnus-summary-insert-old-articles t]
        ["See new articles" gnus-summary-insert-new-articles t]
@@ -2850,6 +2863,7 @@ article number."
 This is all marks except unread, ticked, dormant, and expirable."
   (not (or (= mark gnus-unread-mark)
           (= mark gnus-ticked-mark)
+          (= mark gnus-spam-mark)
           (= mark gnus-dormant-mark)
           (= mark gnus-expirable-mark))))
 
@@ -3086,7 +3100,7 @@ buffer that was in action when the last article was fetched."
     (let ((gnus-replied-mark 129)
          (gnus-score-below-mark 130)
          (gnus-score-over-mark 130)
-         (gnus-downloaded-mark 131)
+         (gnus-undownloaded-mark 131)
          (spec gnus-summary-line-format-spec)
          gnus-visual pos)
       (save-excursion
@@ -3097,7 +3111,7 @@ buffer that was in action when the last article was fetched."
           (make-full-mail-header 0 "" "nobody"
                                  "05 Apr 2001 23:33:09 +0400"
                                  "" "" 0 0 "" nil)
-          0 nil nil 128 t nil "" nil 1)
+          0 nil t 128 t nil "" nil 1)
          (goto-char (point-min))
          (setq pos (list (cons 'unread (and (search-forward "\200" nil t)
                                             (- (point) (point-min) 1)))))
@@ -3508,6 +3522,8 @@ If SHOW-ALL is non-nil, already read articles are also listed."
        ;; Mark this buffer as "prepared".
        (setq gnus-newsgroup-prepared t)
        (gnus-run-hooks 'gnus-summary-prepared-hook)
+       (unless (gnus-ephemeral-group-p group)
+         (gnus-group-update-group group))
        t)))))
 
 (defun gnus-summary-auto-select-subject ()
@@ -3609,7 +3625,16 @@ If SHOW-ALL is non-nil, already read articles are also listed."
                (setcdr prev (cdr threads))
                (setq threads prev))
            ;; Enter this thread into the hash table.
-           (gnus-sethash subject threads hashtb)))
+           (gnus-sethash subject
+                         (if gnus-summary-make-false-root-always
+                             (progn
+                               ;; If you want a dummy root above all
+                               ;; threads...
+                               (setcar threads (list whole-subject
+                                                     (car threads)))
+                               threads)
+                           threads)
+                         hashtb)))
        (setq prev threads)
        (setq threads (cdr threads)))
       result)))
@@ -4415,19 +4440,19 @@ Unscored articles will be counted as having a score of zero."
 (defun gnus-thread-latest-date (thread)
   "Return the highest article date in THREAD."
   (let ((previous-time 0))
-    (apply 'max (mapcar
-                (lambda (header)
-                  (setq previous-time
-                        (time-to-seconds
-                         (mail-header-parse-date
-                          (condition-case ()
-                              (mail-header-date header)
-                            (error previous-time))))))
-                (sort
-                 (message-flatten-list thread)
-                 (lambda (h1 h2)
-                   (< (mail-header-number h1)
-                      (mail-header-number h2))))))))
+    (apply 'max
+          (mapcar
+           (lambda (header)
+             (setq previous-time
+                   (time-to-seconds
+                    (condition-case ()
+                        (mail-header-parse-date (mail-header-date header))
+                      (error previous-time)))))
+           (sort
+            (message-flatten-list thread)
+            (lambda (h1 h2)
+              (< (mail-header-number h1)
+                 (mail-header-number h2))))))))
 
 (defun gnus-thread-total-score-1 (root)
   ;; This function find the total score of the thread below ROOT.
@@ -4496,6 +4521,8 @@ or a straight list of headers."
   (let ((gnus-tmp-level 0)
        (default-score (or gnus-summary-default-score 0))
        (gnus-visual-p (gnus-visual-p 'summary-highlight 'highlight))
+       (building-line-count gnus-summary-display-while-building)
+       (building-count (integerp gnus-summary-display-while-building))
        thread number subject stack state gnus-tmp-gathered beg-match
        new-roots gnus-tmp-new-adopts thread-end simp-subject
        gnus-tmp-header gnus-tmp-unread gnus-tmp-downloaded
@@ -4515,6 +4542,8 @@ or a straight list of headers."
 
       ;; Do the threaded display.
 
+      (if gnus-summary-display-while-building
+         (switch-to-buffer (buffer-name)))
       (while (or threads stack gnus-tmp-new-adopts new-roots)
 
        (if (and (= gnus-tmp-level 0)
@@ -4764,6 +4793,17 @@ or a straight list of headers."
        (push (if (nth 1 thread) 1 0) tree-stack)
        (incf gnus-tmp-level)
        (setq threads (if thread-end nil (cdar thread)))
+       (if gnus-summary-display-while-building
+           (if building-count
+               (progn
+                 ;; use a set frequency
+                 (setq building-line-count (1- building-line-count))
+                 (when (= building-line-count 0)
+                   (sit-for 0)
+                   (setq building-line-count
+                         gnus-summary-display-while-building)))
+             ;; always
+             (sit-for 0)))
        (unless threads
          (setq gnus-tmp-level 0)))))
   (gnus-message 7 "Generating summary...done"))
@@ -5079,7 +5119,8 @@ 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* ((articles
+  (let* ((display (gnus-group-find-parameter group 'display))
+        (articles
          ;; Select all articles if `read-all' is non-nil, or if there
          ;; are no unread articles.
          (if (or read-all
@@ -5539,7 +5580,8 @@ The resulting hash table is returned, or nil if no Xrefs were found."
        ;; Update the number of unread articles.
        (setcar entry num)
        ;; Update the group buffer.
-       (gnus-group-update-group group t)))))
+       (unless (gnus-ephemeral-group-p group)
+         (gnus-group-update-group group t))))))
 
 (defvar gnus-newsgroup-none-id 0)
 
@@ -6292,6 +6334,7 @@ If FORCE (the prefix), also save the .newsrc file(s)."
   (gnus-async-halt-prefetch)
   (let* ((group gnus-newsgroup-name)
         (quit-config (gnus-group-quit-config gnus-newsgroup-name))
+        (gnus-group-is-exiting-p t)
         (mode major-mode)
         (group-point nil)
         (buf (current-buffer)))
@@ -6387,6 +6430,7 @@ If FORCE (the prefix), also save the .newsrc file(s)."
   "Quit reading current newsgroup without updating read article info."
   (interactive)
   (let* ((group gnus-newsgroup-name)
+        (gnus-group-is-exiting-p t)
         (quit-config (gnus-group-quit-config group)))
     (when (or no-questions
              gnus-expert-user
@@ -6423,6 +6467,8 @@ If FORCE (the prefix), also save the .newsrc file(s)."
       (gnus-configure-windows 'group 'force)
       ;; Clear the current group name.
       (setq gnus-newsgroup-name nil)
+      (unless (gnus-ephemeral-group-p group)
+       (gnus-group-update-group group))
       (when (equal (gnus-group-group-name) group)
        (gnus-group-next-unread-group 1))
       (when quit-config
@@ -6672,7 +6718,8 @@ Returns the article selected or nil if there are no unread articles."
        (let ((data gnus-newsgroup-data))
          (while (and data
                      (and (not (and undownloaded
-                                    (memq (car data) gnus-newsgroup-undownloaded)))
+                                    (memq (car data)
+                                          gnus-newsgroup-undownloaded)))
                           (if unseen
                               (or (not (memq
                                         (gnus-data-number (car data))
@@ -6724,6 +6771,14 @@ If optional argument UNREAD is non-nil, only unread article is selected."
   (interactive "p")
   (gnus-summary-next-subject (- n) t))
 
+(defun gnus-summary-goto-subjects (articles)
+  "Insert the subject header for ARTICLES in the current buffer."
+  (save-excursion
+    (dolist (article articles)
+      (gnus-summary-goto-subject article t)))
+  (gnus-summary-limit (append articles gnus-newsgroup-limit))
+  (gnus-summary-position-point))
+  
 (defun gnus-summary-goto-subject (article &optional force silent)
   "Go the subject line of ARTICLE.
 If FORCE, also allow jumping to articles not currently shown."
@@ -6910,7 +6965,7 @@ If BACKWARD, the previous article is selected instead of the next."
   (let ((keystrokes '((?\C-n (gnus-group-next-unread-group 1))
                      (?\C-p (gnus-group-prev-unread-group 1))))
        (cursor-in-echo-area t)
-       keve key group ended)
+       keve key group ended prompt)
     (save-excursion
       (set-buffer gnus-group-buffer)
       (goto-char start)
@@ -6919,19 +6974,20 @@ If BACKWARD, the previous article is selected instead of the next."
                (gnus-summary-best-group gnus-newsgroup-name)
              (gnus-summary-search-group backward gnus-keep-same-level))))
     (while (not ended)
-      (gnus-message
-       5 "No more%s articles%s" (if unread " unread" "")
-       (if (and group
-               (not (gnus-ephemeral-group-p gnus-newsgroup-name)))
-          (format " (Type %s for %s [%s])"
-                  (single-key-description cmd) group
-                  (car (gnus-gethash group gnus-newsrc-hashtb)))
-        (format " (Type %s to exit %s)"
-                (single-key-description cmd)
-                gnus-newsgroup-name)))
+      (setq prompt
+           (format
+            "No more%s articles%s " (if unread " unread" "")
+            (if (and group
+                     (not (gnus-ephemeral-group-p gnus-newsgroup-name)))
+                (format " (Type %s for %s [%s])"
+                        (single-key-description cmd) group
+                        (car (gnus-gethash group gnus-newsrc-hashtb)))
+              (format " (Type %s to exit %s)"
+                      (single-key-description cmd)
+                      gnus-newsgroup-name))))
       ;; Confirm auto selection.
-      (setq key (car (setq keve (gnus-read-event-char))))
-      (setq ended t)
+      (setq key (car (setq keve (gnus-read-event-char prompt)))
+           ended t)
       (cond
        ((assq key keystrokes)
        (let ((obuf (current-buffer)))
@@ -6974,14 +7030,16 @@ If UNREAD is non-nil, only unread articles are selected."
    (and gnus-auto-select-same
        (gnus-summary-article-subject))))
 
-(defun gnus-summary-next-page (&optional lines circular)
+(defun gnus-summary-next-page (&optional lines circular stop)
   "Show next page of the selected article.
 If at the end of the current article, select the next article.
 LINES says how many lines should be scrolled up.
 
 If CIRCULAR is non-nil, go to the start of the article instead of
 selecting the next article when reaching the end of the current
-article."
+article.
+
+If STOP is non-nil, just stop when reaching the end of the message."
   (interactive "P")
   (setq gnus-summary-buffer (current-buffer))
   (gnus-set-global-variables)
@@ -7007,7 +7065,9 @@ article."
          (gnus-eval-in-buffer-window gnus-article-buffer
            (setq endp (gnus-article-next-page lines)))
          (when endp
-           (cond (circular
+           (cond (stop
+                  (gnus-message 3 "End of message"))
+                 (circular
                   (gnus-summary-beginning-of-article))
                  (lines
                   (gnus-message 3 "End of message"))
@@ -7315,8 +7375,9 @@ articles that are younger than AGE days."
         days)
      (while (not days-got)
        (setq days (if younger
-                     (read-string "Limit to articles within (in days): ")
-                   (read-string "Limit to articles older than (in days): ")))
+                     (read-string "Limit to articles younger than (in days, older when negative): ")
+                   (read-string
+                    "Limit to articles older than (in days, younger when negative): ")))
        (when (> (length days) 0)
         (setq days (read days)))
        (if (numberp days)
@@ -7487,6 +7548,14 @@ article."
        (gnus-summary-limit (nconc articles gnus-newsgroup-limit))
       (gnus-summary-position-point))))
 
+(defun gnus-summary-insert-dormant-articles ()
+  "Insert all the dormat articles for this group into the current buffer."
+  (interactive)
+  (let ((gnus-verbose (max 6 gnus-verbose)))
+    (if (not gnus-newsgroup-dormant)
+       (gnus-message 3 "No cached articles for this group")
+      (gnus-summary-goto-subjects gnus-newsgroup-dormant))))
+
 (defun gnus-summary-limit-include-dormant ()
   "Display all the hidden articles that are marked as dormant.
 Note that this command only works on a subset of the articles currently
@@ -8846,9 +8915,8 @@ ACTION can be either `move' (the default), `crosspost' or `copy'."
                       to-group (cdar marks) (list to-article) info)))
                  (setq marks (cdr marks)))
 
-               (gnus-request-set-mark to-group (list (list (list to-article)
-                                                           'add
-                                                           to-marks))))
+               (gnus-request-set-mark
+                to-group (list (list (list to-article) 'add to-marks))))
 
              (gnus-dribble-enter
               (concat "(gnus-group-set-info '"
@@ -8904,6 +8972,15 @@ If nil, use to the current newsgroup method."
   :type 'symbol
   :group 'gnus-summary-mail)
 
+(defcustom gnus-summary-display-while-building nil
+  "If not-nil, show and update the summary buffer as it's being built.
+If the value is t, update the buffer after every line is inserted.  If
+the value is an integer (N), update the display every N lines."
+  :group 'gnus-thread
+  :type '(choice (const :tag "off" nil)
+                number
+                (const :tag "frequently" t)))
+
 (defun gnus-summary-respool-article (&optional n method)
   "Respool the current article.
 The article will be squeezed through the mail spooling process again,
@@ -9448,10 +9525,7 @@ ARTICLE can also be a list of articles."
            (not (equal gnus-newsgroup-name (car gnus-article-current))))
     (error "No current article selected"))
   ;; Remove old bookmark, if one exists.
-  (let ((old (assq article gnus-newsgroup-bookmarks)))
-    (when old
-      (setq gnus-newsgroup-bookmarks
-           (delq old gnus-newsgroup-bookmarks))))
+  (gnus-pull article gnus-newsgroup-bookmarks)
   ;; Set the new bookmark, which is on the form
   ;; (article-number . line-number-in-body).
   (push
@@ -9461,8 +9535,7 @@ ARTICLE can also be a list of articles."
           (count-lines
            (min (point)
                 (save-excursion
-                  (goto-char (point-min))
-                  (search-forward "\n\n" nil t)
+                  (article-goto-body)
                   (point)))
            (point))))
    gnus-newsgroup-bookmarks)
@@ -9472,13 +9545,10 @@ ARTICLE can also be a list of articles."
   "Remove the bookmark from the current article."
   (interactive (list (gnus-summary-article-number)))
   ;; Remove old bookmark, if one exists.
-  (let ((old (assq article gnus-newsgroup-bookmarks)))
-    (if old
-       (progn
-         (setq gnus-newsgroup-bookmarks
-               (delq old gnus-newsgroup-bookmarks))
-         (gnus-message 6 "Removed bookmark."))
-      (gnus-message 6 "No bookmark in current article."))))
+  (if (not (assq article gnus-newsgroup-bookmarks))
+      (gnus-message 6 "No bookmark in current article.")
+    (gnus-pull article gnus-newsgroup-bookmarks)
+    (gnus-message 6 "Removed bookmark.")))
 
 ;; Suggested by Daniel Quinlan <quinlan@best.com>.
 (defun gnus-summary-mark-as-dormant (n)
@@ -9524,7 +9594,7 @@ If NO-EXPIRE, auto-expiry will be inhibited."
        (gnus-summary-goto-unread
         (and gnus-summary-goto-unread
              (not (eq gnus-summary-goto-unread 'never))
-             (not (memq mark (list gnus-unread-mark
+             (not (memq mark (list gnus-unread-mark gnus-spam-mark
                                    gnus-ticked-mark gnus-dormant-mark)))))
        (n (abs n))
        (mark (or mark gnus-del-mark)))
@@ -10532,20 +10602,22 @@ The variable `gnus-default-article-saver' specifies the saver function."
     (gnus-set-mode-line 'summary)
     n))
 
-(defun gnus-summary-pipe-output (&optional arg)
+(defun gnus-summary-pipe-output (&optional arg headers)
   "Pipe the current article to a subprocess.
 If N is a positive number, pipe the N next articles.
 If N is a negative number, pipe the N previous articles.
 If N is nil and any articles have been marked with the process mark,
-pipe those articles instead."
-  (interactive "P")
+pipe those articles instead.
+If HEADERS (the symbolic prefix), include the headers, too."
+  (interactive (gnus-interactive "P\ny"))
   (require 'gnus-art)
-  (let ((gnus-default-article-saver 'gnus-summary-save-in-pipe))
+  (let ((gnus-default-article-saver 'gnus-summary-save-in-pipe)
+       (gnus-save-all-headers (or headers gnus-save-all-headers)))
     (gnus-summary-save-article arg t))
   (let ((buffer (get-buffer "*Shell Command Output*")))
-    (if (and buffer
-            (with-current-buffer buffer (> (point-max) (point-min))))
-       (gnus-configure-windows 'pipe))))
+    (when (and buffer
+              (not (zerop (buffer-size buffer))))
+      (gnus-configure-windows 'pipe))))
 
 (defun gnus-summary-save-article-mail (&optional arg)
   "Append the current article to an mail file.
@@ -11011,21 +11083,18 @@ If REVERSE, save parts that do not match TYPE."
 
 (defun gnus-summary-highlight-line ()
   "Highlight current line according to `gnus-summary-highlight'."
-  (let*
-      ((list gnus-summary-highlight)
-       (beg (gnus-point-at-bol))
+  (let* ((beg (gnus-point-at-bol))
         (article (or (gnus-summary-article-number) gnus-current-article))
         (score (or (cdr (assq article
-                            gnus-newsgroup-scored))
-                 gnus-summary-default-score 0))
-       (mark (or (gnus-summary-article-mark) gnus-unread-mark))
-       (inhibit-read-only t)
-       (default gnus-summary-default-score)
-       (default-high gnus-summary-default-high-score)
-       (default-low gnus-summary-default-low-score)
+                              gnus-newsgroup-scored))
+                   gnus-summary-default-score 0))
+        (mark (or (gnus-summary-article-mark) gnus-unread-mark))
+        (inhibit-read-only t)
+        (default gnus-summary-default-score)
+        (default-high gnus-summary-default-high-score)
+        (default-low gnus-summary-default-low-score)
         (uncached (memq article gnus-newsgroup-undownloaded))
-        (downloaded (not uncached))
-        )
+        (downloaded (not uncached)))
     (let ((face (funcall (gnus-summary-highlight-line-0))))
       (unless (eq face (get-text-property beg 'face))
        (gnus-put-text-property-excluding-characters-with-faces