*** empty log message ***
[gnus] / lisp / gnus-score.el
index 2f27b90..b0927ef 100644 (file)
@@ -1,5 +1,5 @@
 ;;; gnus-score.el --- scoring code for Gnus
-;; Copyright (C) 1995 Free Software Foundation, Inc.
+;; Copyright (C) 1995,96 Free Software Foundation, Inc.
 
 ;; Author: Per Abrahamsen <amanda@iesd.auc.dk>
 ;;     Lars Magne Ingebrigtsen <larsi@ifi.uio.no>
@@ -18,8 +18,9 @@
 ;; GNU General Public License for more details.
 
 ;; 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, 675 Mass Ave, Cambridge, MA 02139, USA.
+;; 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.
 
 ;;; Commentary:
 
@@ -97,7 +98,13 @@ score alists.")
   "*Scoring commands will raise/lower the score with this number as the default.")
 
 (defvar gnus-score-expiry-days 7
-  "*Number of days before unused score file entries are expired.")
+  "*Number of days before unused score file entries are expired.
+If this variable is nil, no score file entries will be expired.")
+
+(defvar gnus-update-score-entry-dates t
+  "*In non-nil, update matching score entry dates.
+If this variable is nil, then score entries that provide matches
+will be expired along with non-matching score entries.")
 
 (defvar gnus-orphan-score nil
   "*All orphans get this score added. Set in the score file.")
@@ -117,7 +124,7 @@ score alists.")
 (defvar gnus-score-exact-adapt-limit 10
   "*Number that says how long a match has to be before using substring matching.
 When doing adaptive scoring, one normally uses fuzzy or substring
-matching. However, if the header one matches is short, the possibility
+matching.  However, if the header one matches is short, the possibility
 for false positives is great, so if the length of the match is less
 than this variable, exact matching will be used.
 
@@ -126,6 +133,59 @@ If this variable is nil, exact matching will always be used.")
 (defvar gnus-score-uncacheable-files "ADAPT$"
   "*All score files that match this regexp will not be cached.")
 
+(defvar gnus-score-default-header nil
+  "Default header when entering new scores.
+
+Should be one of the following symbols.
+
+ a: from
+ s: subject
+ b: body
+ h: head
+ i: message-id
+ t: references
+ x: xref
+ l: lines
+ d: date
+ f: followup
+
+If nil, the user will be asked for a header.")
+
+(defvar gnus-score-default-type nil
+  "Default match type when entering new scores.
+
+Should be one of the following symbols.
+
+ s: substring
+ e: exact string
+ f: fuzzy string
+ r: regexp string
+ b: before date
+ a: at date
+ n: this date
+ <: less than number
+ >: greater than number
+ =: equal to number
+
+If nil, the user will be asked for a match type.")
+
+(defvar gnus-score-default-fold nil
+  "Use case folding for new score file entries iff not nil.")
+
+(defvar gnus-score-default-duration nil
+  "Default duration of effect when entering new scores.
+
+Should be one of the following symbols.
+
+ t: temporary
+ p: permanent
+ i: immediate
+
+If nil, the user will be asked for a duration.")
+
+(defvar gnus-score-after-write-file-function nil
+  "*Function called with the name of the score file just written to disk.")
+
 \f
 
 ;; Internal variables.
@@ -133,6 +193,8 @@ If this variable is nil, exact matching will always be used.")
 (defvar gnus-internal-global-score-files nil)
 (defvar gnus-score-file-list nil)
 
+(defvar gnus-short-name-score-file-cache nil)
+
 (defvar gnus-score-help-winconf nil)
 (defvar gnus-adaptive-score-alist gnus-default-adaptive-score-alist)
 (defvar gnus-score-trace nil)
@@ -156,31 +218,45 @@ of the last successful match.")
 
 (defvar gnus-score-cache nil)
 (defvar gnus-scores-articles nil)
-(defvar gnus-header-index nil)
 (defvar gnus-score-index nil)
 
-(eval-and-compile
-  (autoload 'gnus-uu-ctl-map "gnus-uu" nil nil 'keymap)
-  (autoload 'appt-select-lowest-window "appt.el"))
 
-;;; Summary mode score maps.
-
-(defvar gnus-summary-score-map nil)
+(defconst gnus-header-index
+  ;; Name to index alist.
+  '(("number" 0 gnus-score-integer)
+    ("subject" 1 gnus-score-string)
+    ("from" 2 gnus-score-string)
+    ("date" 3 gnus-score-date)
+    ("message-id" 4 gnus-score-string) 
+    ("references" 5 gnus-score-string) 
+    ("chars" 6 gnus-score-integer) 
+    ("lines" 7 gnus-score-integer) 
+    ("xref" 8 gnus-score-string)
+    ("head" -1 gnus-score-body)
+    ("body" -1 gnus-score-body)
+    ("all" -1 gnus-score-body)
+    ("followup" 2 gnus-score-followup)
+    ("thread" 5 gnus-score-thread)))
 
-(define-prefix-command 'gnus-summary-score-map)
-(define-key gnus-summary-mode-map "V" 'gnus-summary-score-map)
-(define-key gnus-summary-score-map "s" 'gnus-summary-set-score)
-(define-key gnus-summary-score-map "a" 'gnus-summary-score-entry)
-(define-key gnus-summary-score-map "S" 'gnus-summary-current-score)
-(define-key gnus-summary-score-map "c" 'gnus-score-change-score-file)
-(define-key gnus-summary-score-map "m" 'gnus-score-set-mark-below)
-(define-key gnus-summary-score-map "x" 'gnus-score-set-expunge-below)
-(define-key gnus-summary-score-map "e" 'gnus-score-edit-alist)
-(define-key gnus-summary-score-map "f" 'gnus-score-edit-file)
-(define-key gnus-summary-score-map "t" 'gnus-score-find-trace)
-(define-key gnus-summary-score-map "C" 'gnus-score-customize)
+(eval-and-compile
+  (autoload 'gnus-uu-ctl-map "gnus-uu" nil nil 'keymap))
 
+;;; Summary mode score maps.
 
+(gnus-define-keys
+ (gnus-summary-score-map "V" gnus-summary-mode-map)
+ "s" gnus-summary-set-score
+ "a" gnus-summary-score-entry
+ "S" gnus-summary-current-score
+ "c" gnus-score-change-score-file
+ "m" gnus-score-set-mark-below
+ "x" gnus-score-set-expunge-below
+ "R" gnus-summary-rescore
+ "e" gnus-score-edit-current-scores
+ "f" gnus-score-edit-file
+ "F" gnus-score-flush-cache
+ "t" gnus-score-find-trace
+ "C" gnus-score-customize)
 
 ;; Summary score file commands
 
@@ -195,6 +271,21 @@ used as score."
   (interactive "P")
   (gnus-summary-increase-score (- (gnus-score-default score))))
 
+(defvar gnus-score-default-header nil
+  "*The default header to score on when entering a score rule interactively.")
+
+(defvar gnus-score-default-type nil
+  "*The default score type to use when entering a score rule interactively.")
+
+(defvar gnus-score-default-duration nil
+  "*The default score duration to use on when entering a score rule interactively.")
+
+(defun gnus-score-kill-help-buffer ()
+  (when (get-buffer "*Score Help*")
+    (kill-buffer "*Score Help*")
+    (and gnus-score-help-winconf
+        (set-window-configuration gnus-score-help-winconf))))
+
 (defun gnus-summary-increase-score (&optional score)
   "Make a score entry based on the current article.
 The user will be prompted for header to score on, match type,
@@ -211,11 +302,12 @@ used as score."
            (?b "body" "" nil body-string)
            (?h "head" "" nil body-string)
            (?i "message-id" nil t string)
-           (?t "references" "message-id" t string)
+           (?t "references" "message-id" nil string)
            (?x "xref" nil nil string)
            (?l "lines" nil nil number)
            (?d "date" nil nil date)
-           (?f "followup" nil nil string)))
+           (?f "followup" nil nil string)
+           (?T "thread" nil nil string)))
         (char-to-type
          '((?s s "substring" string)
            (?e e "exact string" string)
@@ -233,7 +325,14 @@ used as score."
          (list (list ?t (current-time-string) "temporary") 
                '(?p perm "permanent") '(?i now "immediate")))
         (mimic gnus-score-mimic-keymap)
-        hchar entry temporary tchar pchar end type match)
+        (hchar (and gnus-score-default-header 
+                    (aref (symbol-name gnus-score-default-header) 0)))
+        (tchar (and gnus-score-default-type
+                    (aref (symbol-name gnus-score-default-type) 0)))
+        (pchar (and gnus-score-default-duration
+                    (aref (symbol-name gnus-score-default-duration) 0)))
+        entry temporary type match)
+
     ;; First we read the header to score.
     (while (not hchar)
       (if mimic
@@ -244,123 +343,95 @@ used as score."
                 (mapconcat (lambda (s) (char-to-string (car s)))
                            char-to-header "")))
       (setq hchar (read-char))
-      (if (not (or (= hchar ??) (= hchar ?\C-h)))
-         ()
+      (when (or (= hchar ??) (= hchar ?\C-h))
        (setq hchar nil)
        (gnus-score-insert-help "Match on header" char-to-header 1)))
 
-    (and (get-buffer "*Score Help*")
-        (progn
-          (kill-buffer "*Score Help*")
-          (and gnus-score-help-winconf
-               (set-window-configuration gnus-score-help-winconf))))
-
-    (or (setq entry (assq (downcase hchar) char-to-header))
-       (progn
-         (ding)
-         (setq end t)
-         (if mimic (message "%c %c" prefix hchar) (message ""))))
-    (if (or end (/= (downcase hchar) hchar))
-       (progn
-         ;; This was a majuscle, so we end reading and set the defaults.
-         (if mimic (message "%c %c" prefix hchar) (message ""))
-         (setq type nil
-               temporary (current-time-string)))
-
-      ;; We continue reading - the type.
-      (while (not tchar)
-       (if mimic
-           (progn
-             (sit-for 1)
-             (message "%c %c-" prefix hchar))
-         (message "%s header '%s' with match type (%s?): "
-                  (if increase "Increase" "Lower")
-                  (nth 1 entry)
-                  (mapconcat (lambda (s) 
-                               (if (eq (nth 4 entry) 
-                                       (nth 3 s))
-                                   (char-to-string (car s))
-                                 ""))
-                             char-to-type "")))
-       (setq tchar (read-char))
-       (if (not (or (= tchar ??) (= tchar ?\C-h)))
-           ()
-         (setq tchar nil)
-         (gnus-score-insert-help "Match type" char-to-type 2)))
-
-      (and (get-buffer "*Score Help*")
-          (progn
-            (and gnus-score-help-winconf
-                 (set-window-configuration gnus-score-help-winconf))
-            (kill-buffer "*Score Help*")))
-      
-      (or (setq type (nth 1 (assq (downcase tchar) char-to-type)))
+    (gnus-score-kill-help-buffer)
+    (unless (setq entry (assq (downcase hchar) char-to-header))
+      (if mimic (error "%c %c" prefix hchar) (error "")))
+
+    (when (/= (downcase hchar) hchar)
+      ;; This was a majuscle, so we end reading and set the defaults.
+      (if mimic (message "%c %c" prefix hchar) (message ""))
+      (setq tchar (or tchar ?s)
+           pchar (or pchar ?t)))
+    
+    ;; We continue reading - the type.
+    (while (not tchar)
+      (if mimic
          (progn
-           (ding)
-           (if mimic (message "%c %c" prefix hchar) (message ""))
-           (setq end t)))
-      (if (or end (/= (downcase tchar) tchar))
+           (sit-for 1) (message "%c %c-" prefix hchar))
+       (message "%s header '%s' with match type (%s?): "
+                (if increase "Increase" "Lower")
+                (nth 1 entry)
+                (mapconcat (lambda (s) 
+                             (if (eq (nth 4 entry) 
+                                     (nth 3 s))
+                                 (char-to-string (car s))
+                               ""))
+                           char-to-type "")))
+      (setq tchar (read-char))
+      (when (or (= tchar ??) (= tchar ?\C-h))
+       (setq tchar nil)
+       (gnus-score-insert-help "Match type" char-to-type 2)))
+
+    (gnus-score-kill-help-buffer)
+    (unless (setq type (nth 1 (assq (downcase tchar) char-to-type)))
+      (if mimic (error "%c %c" prefix hchar) (error "")))
+
+    (when (/= (downcase tchar) tchar)
+      ;; It was a majuscle, so we end reading and the the default.
+      (if mimic (message "%c %c %c" prefix hchar tchar)
+       (message ""))
+      (setq pchar (or pchar ?p)))
+
+    ;; We continue reading.
+    (while (not pchar)
+      (if mimic
          (progn
-           ;; It was a majuscle, so we end reading and the the default.
-           (if mimic (message "%c %c %c" prefix hchar tchar)
-             (message ""))
-           (setq temporary (current-time-string)))
-
-       ;; We continue reading.
-       (while (not pchar)
-         (if mimic
-             (progn
-               (sit-for 1)
-               (message "%c %c %c-" prefix hchar tchar))
-           (message "%s permanence (%s?): " (if increase "Increase" "Lower")
-                    (mapconcat (lambda (s) (char-to-string (car s)))
-                               char-to-perm "")))
-         (setq pchar (read-char))
-         (if (not (or (= pchar ??) (= pchar ?\C-h)))
-             ()
-           (setq pchar nil)
-           (gnus-score-insert-help "Match permanence" char-to-perm 2)))
-
-       (and (get-buffer "*Score Help*")
-            (progn
-              (and gnus-score-help-winconf
-                   (set-window-configuration gnus-score-help-winconf))
-              (kill-buffer "*Score Help*")))
-
-       (if mimic (message "%c %c %c" prefix hchar tchar pchar)
-         (message ""))
-       (if (setq temporary (nth 1 (assq pchar char-to-perm)))
-           ()
-         (ding)
-         (setq end t)
-         (if mimic 
-             (message "%c %c %c %c" prefix hchar tchar pchar)
-           (message "")))))
+           (sit-for 1) (message "%c %c %c-" prefix hchar tchar))
+       (message "%s permanence (%s?): " (if increase "Increase" "Lower")
+                (mapconcat (lambda (s) (char-to-string (car s)))
+                           char-to-perm "")))
+      (setq pchar (read-char))
+      (when (or (= pchar ??) (= pchar ?\C-h))
+       (setq pchar nil)
+       (gnus-score-insert-help "Match permanence" char-to-perm 2)))
+
+    (gnus-score-kill-help-buffer)
+    (if mimic (message "%c %c %c" prefix hchar tchar pchar)
+      (message ""))
+    (unless (setq temporary (cadr (assq pchar char-to-perm)))
+      (if mimic 
+         (error "%c %c %c %c" prefix hchar tchar pchar)
+       (error "")))
 
     ;; We have all the data, so we enter this score.
-    (if end
-       ()
-      (setq match (if (string= (nth 2 entry) "") ""
-                   (gnus-summary-header (or (nth 2 entry) (nth 1 entry)))))
+    (setq match (if (string= (nth 2 entry) "") ""
+                 (gnus-summary-header (or (nth 2 entry) (nth 1 entry)))))
       
-      ;; Modify the match, perhaps.
-      (cond 
-       ((equal (nth 1 entry) "xref")
-       (when (string-match "^Xref: *" match)
-         (setq match (substring match (match-end 0))))
-       (when (string-match "^[^:]* +" match)
-         (setq match (substring match (match-end 0))))))
-
-      (gnus-summary-score-entry
-       (nth 1 entry)                   ; Header
-       match                           ; Match
-       type                            ; Type
-       (if (eq 's score) nil score)     ; Score
-       (if (eq 'perm temporary)         ; Temp
-           nil
-         temporary)
-       (not (nth 3 entry)))            ; Prompt
-      )))
+    ;; Modify the match, perhaps.
+    (cond 
+     ((equal (nth 1 entry) "xref")
+      (when (string-match "^Xref: *" match)
+       (setq match (substring match (match-end 0))))
+      (when (string-match "^[^:]* +" match)
+       (setq match (substring match (match-end 0))))))
+    
+    (when (memq type '(r R regexp Regexp))
+      (setq match (regexp-quote match)))
+
+    (gnus-summary-score-entry
+     (nth 1 entry)                     ; Header
+     match                             ; Match
+     type                              ; Type
+     (if (eq 's score) nil score)      ; Score
+     (if (eq 'perm temporary)          ; Temp
+        nil
+        temporary)
+     (not (nth 3 entry)))              ; Prompt
+    ))
   
 (defun gnus-score-insert-help (string alist idx)
   (setq gnus-score-help-winconf (current-window-configuration))
@@ -392,11 +463,11 @@ used as score."
          (insert "\n"))
        (setq pad (- width 3))
        (setq format (concat "%c: %-" (int-to-string pad) "s"))
-       (insert (format format (car (car alist)) (nth idx (car alist))))
+       (insert (format format (caar alist) (nth idx (car alist))))
        (setq alist (cdr alist))
        (setq i (1+ i))))
     ;; display ourselves in a small window at the bottom
-    (appt-select-lowest-window)
+    (gnus-appt-select-lowest-window)
     (split-window)
     (pop-to-buffer "*Score Help*")
     (shrink-window-if-larger-than-buffer)
@@ -417,15 +488,22 @@ used as score."
          (error "No article on current line")
        nil))))
 
+(defun gnus-newsgroup-score-alist ()
+  (or
+   (let ((param-file (gnus-group-get-parameter 
+                     gnus-newsgroup-name 'score-file)))
+     (when param-file
+       (gnus-score-load param-file)))
+   (gnus-score-load
+    (gnus-score-file-name gnus-newsgroup-name)))
+  gnus-score-alist)
+
 (defsubst gnus-score-get (symbol &optional alist)
   ;; Get SYMBOL's definition in ALIST.
   (cdr (assoc symbol 
              (or alist 
                  gnus-score-alist
-                 (progn
-                   (gnus-score-load 
-                    (gnus-score-file-name gnus-newsgroup-name))
-                   gnus-score-alist)))))
+                 (gnus-newsgroup-score-alist)))))
 
 (defun gnus-summary-score-entry 
   (header match type score date &optional prompt silent)
@@ -459,7 +537,8 @@ If optional argument `SILENT' is nil, show effect of score entry."
        ((eq type 'f)
         (setq match (gnus-simplify-subject-fuzzy match))))
   (let ((score (gnus-score-default score))
-       (header (downcase header)))
+       (header (downcase header))
+       new)
     (and prompt (setq match (read-string 
                             (format "Match %s on %s, %s: " 
                                     (cond ((eq date 'now)
@@ -472,30 +551,23 @@ If optional argument `SILENT' is nil, show effect of score entry."
                             (if (numberp match)
                                 (int-to-string match)
                               match))))
-    (and (>= (nth 1 (assoc header gnus-header-index)) 0)
-        (eq (nth 2 (assoc header gnus-header-index)) 'gnus-score-string)
-        (not silent)
-        (gnus-summary-score-effect header match type score))
 
     ;; If this is an integer comparison, we transform from string to int. 
     (and (eq (nth 2 (assoc header gnus-header-index)) 'gnus-score-integer)
         (setq match (string-to-int match)))
 
-    (if (eq date 'now)
-       ()
-      (and (= score gnus-score-interactive-default-score)
+    (unless (eq date 'now)
+      ;; Add the score entry to the score file.
+      (when (= score gnus-score-interactive-default-score)
           (setq score nil))
-      (let ((new (cond 
-                 (type
-                  (list match score (and date (gnus-day-number date)) type))
-                 (date
-                  (list match score (gnus-day-number date)))
-                 (score
-                  (list match score))
-                 (t
-                  (list match))))
-           (old (gnus-score-get header))
+      (let ((old (gnus-score-get header))
            elem)
+       (setq new
+             (cond 
+              (type (list match score (and date (gnus-day-number date)) type))
+              (date (list match score (gnus-day-number date)))
+              (score (list match score))
+              (t (list match))))
        ;; We see whether we can collapse some score entries.
        ;; This isn't quite correct, because there may be more elements
        ;; later on with the same key that have matching elems... Hm.
@@ -510,8 +582,19 @@ If optional argument `SILENT' is nil, show effect of score entry."
                                  (or (nth 1 new)
                                      gnus-score-interactive-default-score)))
          ;; Nope, we have to add a new elem.
-         (gnus-score-set header (if old (cons new old) (list new)))))
-      (gnus-score-set 'touched '(t)))))
+         (gnus-score-set header (if old (cons new old) (list new))))
+       (gnus-score-set 'touched '(t))))
+
+    ;; Score the current buffer.
+    (unless silent
+      (if (and (>= (nth 1 (assoc header gnus-header-index)) 0)
+              (eq (nth 2 (assoc header gnus-header-index))
+                  'gnus-score-string))
+         (gnus-summary-score-effect header match type score)
+       (gnus-summary-rescore)))
+
+    ;; Return the new scoring rule.
+    new))
 
 (defun gnus-summary-score-effect (header match type score)
   "Simulate the effect of a score file entry.
@@ -532,8 +615,12 @@ SCORE is the score to add."
     (goto-char (point-min))
     (let ((regexp (cond ((eq type 'f)
                         (gnus-simplify-subject-fuzzy match))
-                       (type match)
-                       (t (concat "\\`.*" (regexp-quote match) ".*\\'")))))
+                       ((eq type 'r) 
+                        match)
+                       ((eq type 'e)
+                        (concat "\\`" (regexp-quote match) "\\'"))
+                       (t 
+                        (regexp-quote match)))))
       (while (not (eobp))
        (let ((content (gnus-summary-header header 'noerr))
              (case-fold-search t))
@@ -582,12 +669,28 @@ SCORE is the score to add."
   (gnus-score-update-lines))
 
 (defun gnus-score-update-lines ()
+  "Update all lines in the summary buffer."
   (save-excursion
     (goto-char (point-min))
     (while (not (eobp))
       (gnus-summary-update-line)
       (forward-line 1))))
 
+(defun gnus-score-update-all-lines ()
+  "Update all lines in the summary buffer, even the hidden ones."
+  (save-excursion
+    (goto-char (point-min))
+    (let (hidden)
+      (while (not (eobp))
+       (when (gnus-summary-show-thread)
+         (push (point) hidden))
+       (gnus-summary-update-line)
+       (forward-line 1))
+      ;; Re-hide the hidden threads.
+      (while hidden
+       (goto-char (pop hidden))
+       (gnus-summary-hide-thread)))))
+
 (defun gnus-score-set-expunge-below (score)
   "Automatically expunge articles with score below SCORE."
   (interactive 
@@ -597,14 +700,42 @@ SCORE is the score to add."
   (gnus-score-set 'expunge (list score))
   (gnus-score-set 'touched '(t)))
 
+(defun gnus-score-followup-article (&optional score)
+  "Add SCORE to all followups to the article in the current buffer."
+  (interactive "P")
+  (setq score (gnus-score-default score))
+  (when (gnus-buffer-live-p gnus-summary-buffer)
+    (save-excursion
+      (set-buffer gnus-summary-buffer)
+      (save-restriction
+       (goto-char (point-min))
+       (let ((id (mail-fetch-field "message-id")))
+         (when id
+           (gnus-summary-score-entry
+            "references" (concat id "[ \t]*$") 'r
+            score (current-time-string) nil t)))))))
+
+(defun gnus-score-followup-thread (&optional score)
+  "Add SCORE to all later articles in the thread the current buffer is part of."
+  (interactive "P")
+  (setq score (gnus-score-default score))
+  (when (gnus-buffer-live-p gnus-summary-buffer)
+    (save-excursion
+      (set-buffer gnus-summary-buffer)
+      (save-restriction
+       (goto-char (point-min))
+       (let ((id (mail-fetch-field "message-id")))
+         (when id
+           (gnus-summary-score-entry
+            "references" id 's
+            score (current-time-string))))))))
+
 (defun gnus-score-set (symbol value &optional alist)
   ;; Set SYMBOL to VALUE in ALIST.
   (let* ((alist 
          (or alist 
              gnus-score-alist
-             (progn
-               (gnus-score-load (gnus-score-file-name gnus-newsgroup-name))
-               gnus-score-alist)))
+             (gnus-newsgroup-score-alist)))
         (entry (assoc symbol alist)))
     (cond ((gnus-score-get 'read-only alist)
           ;; This is a read-only score file, so we do nothing.
@@ -617,36 +748,73 @@ SCORE is the score to add."
           (setcdr alist
                   (cons (cons symbol value) (cdr alist)))))))
 
+(defun gnus-summary-raise-score (n)
+  "Raise the score of the current article by N."
+  (interactive "p")
+  (gnus-set-global-variables)
+  (gnus-summary-set-score (+ (gnus-summary-article-score) 
+                            (or n gnus-score-interactive-default-score ))))
+
+(defun gnus-summary-set-score (n)
+  "Set the score of the current article to N."
+  (interactive "p")
+  (gnus-set-global-variables)
+  (save-excursion
+    (gnus-summary-show-thread)
+    (let ((buffer-read-only nil))
+      ;; Set score.
+      (gnus-summary-update-mark
+       (if (= n (or gnus-summary-default-score 0)) ? 
+        (if (< n (or gnus-summary-default-score 0))
+            gnus-score-below-mark gnus-score-over-mark)) 'score))
+    (let* ((article (gnus-summary-article-number))
+          (score (assq article gnus-newsgroup-scored)))
+      (if score (setcdr score n)
+       (setq gnus-newsgroup-scored
+             (cons (cons article n) gnus-newsgroup-scored))))
+    (gnus-summary-update-line)))
+
+(defun gnus-summary-current-score ()
+  "Return the score of the current article."
+  (interactive)
+  (gnus-set-global-variables)
+  (gnus-message 1 "%s" (gnus-summary-article-score)))
+
 (defun gnus-score-change-score-file (file)
   "Change current score alist."
   (interactive 
-   (list (read-file-name "Edit score file: " gnus-kill-files-directory)))
+   (list (read-file-name "Change to score file: " gnus-kill-files-directory)))
   (gnus-score-load-file file)
   (gnus-set-mode-line 'summary))
 
-(defun gnus-score-edit-alist (file)
+(defvar gnus-score-edit-exit-function)
+(defun gnus-score-edit-current-scores (file)
   "Edit the current score alist."
   (interactive (list gnus-current-score-file))
   (let ((winconf (current-window-configuration)))
     (and (buffer-name gnus-summary-buffer) (gnus-score-save))
+    (gnus-make-directory (file-name-directory file))
     (setq gnus-score-edit-buffer (find-file-noselect file))
     (gnus-configure-windows 'edit-score)
     (gnus-score-mode)
+    (setq gnus-score-edit-exit-function 'gnus-score-edit-done)
     (make-local-variable 'gnus-prev-winconf)
     (setq gnus-prev-winconf winconf))
   (gnus-message 
    4 (substitute-command-keys 
-      "\\<gnus-score-mode-map>\\[gnus-score-edit-done] to save edits")))
+      "\\<gnus-score-mode-map>\\[gnus-score-edit-exit] to save edits")))
   
 (defun gnus-score-edit-file (file)
   "Edit a score file."
   (interactive 
    (list (read-file-name "Edit score file: " gnus-kill-files-directory)))
+  (gnus-make-directory (file-name-directory file))
   (and (buffer-name gnus-summary-buffer) (gnus-score-save))
   (let ((winconf (current-window-configuration)))
     (setq gnus-score-edit-buffer (find-file-noselect file))
     (gnus-configure-windows 'edit-score)
     (gnus-score-mode)
+    (setq gnus-score-edit-exit-function 'gnus-score-edit-done)
     (make-local-variable 'gnus-prev-winconf)
     (setq gnus-prev-winconf winconf))
   (gnus-message 
@@ -655,7 +823,6 @@ SCORE is the score to add."
   
 (defun gnus-score-load-file (file)
   ;; Load score file FILE.  Returns a list a retrieved score-alists.
-  (setq gnus-kill-files-directory (or gnus-kill-files-directory "~/News/"))
   (let* ((file (expand-file-name 
                (or (and (string-match
                          (concat "^" (expand-file-name
@@ -681,10 +848,18 @@ SCORE is the score to add."
           (setq alist (cons (list 'read-only t) alist)))
       (setq gnus-score-cache
            (cons (cons file alist) gnus-score-cache)))
-    ;; If there are actual scores in the alist, we add it to the
-    ;; return value of this function.
-    (if (memq t (mapcar (lambda (e) (stringp (car e))) alist))
-       (setq lists (list alist)))
+    (let ((a alist)
+         found)
+      (while a
+       ;; Downcase all header names.
+       (when (stringp (caar a))
+         (setcar (car a) (downcase (caar a)))
+         (setq found t))
+       (pop a))
+      ;; If there are actual scores in the alist, we add it to the
+      ;; return value of this function.
+      (when found
+       (setq lists (list alist))))
     ;; Treat the other possible atoms in the score alist.
     (let ((mark (car (gnus-score-get 'mark alist)))
          (expunge (car (gnus-score-get 'expunge alist)))
@@ -695,7 +870,7 @@ SCORE is the score to add."
          (adapt (gnus-score-get 'adapt alist))
          (thread-mark-and-expunge
           (car (gnus-score-get 'thread-mark-and-expunge alist)))
-         (adapt-file (car (gnus-score-get 'adapt-file)))
+         (adapt-file (car (gnus-score-get 'adapt-file alist)))
          (local (gnus-score-get 'local alist))
          (eval (car (gnus-score-get 'eval alist))))
       ;; We do not respect eval and files atoms from global score
@@ -720,10 +895,10 @@ SCORE is the score to add."
          (set-buffer gnus-summary-buffer)
          (while local
            (and (consp (car local))
-                (symbolp (car (car local)))
+                (symbolp (caar local))
                 (progn
-                  (make-local-variable (car (car local)))
-                  (set (car (car local)) (nth 1 (car local)))))
+                  (make-local-variable (caar local))
+                  (set (caar local) (nth 1 (car local)))))
            (setq local (cdr local)))))
       (if orphan (setq gnus-orphan-score orphan))
       (setq gnus-adaptive-score-alist
@@ -768,31 +943,33 @@ SCORE is the score to add."
 
 (defun gnus-score-load-score-alist (file)
   (let (alist)
-    (if (file-readable-p file)
-       (progn
-         (save-excursion
-           (gnus-set-work-buffer)
-           (insert-file-contents file)
-           (goto-char (point-min))
-           ;; Only do the loading if the score file isn't empty.
-           (if (save-excursion (re-search-forward "[()0-9a-zA-Z]" nil t))
-               (setq alist
-                     (condition-case ()
-                         (read (current-buffer))
-                       (error 
-                        (progn
-                          (gnus-message 3 "Problem with score file %s" file)
-                          (ding) 
-                          (sit-for 2)
-                          nil))))))
-         (if (eq (car alist) 'setq)
-             (setq gnus-score-alist (gnus-score-transform-old-to-new alist))
-           (setq gnus-score-alist alist))
-         (setq gnus-score-alist
-               (gnus-score-check-syntax gnus-score-alist file)))
-      (setq gnus-score-alist nil))))
+    (if (not (file-readable-p file))
+       (setq gnus-score-alist nil)
+      (save-excursion
+       (gnus-set-work-buffer)
+       (insert-file-contents file)
+       (goto-char (point-min))
+       ;; Only do the loading if the score file isn't empty.
+       (when (save-excursion (re-search-forward "[()0-9a-zA-Z]" nil t))
+         (setq alist
+               (condition-case ()
+                   (read (current-buffer))
+                 (error 
+                  (progn
+                    (gnus-message 3 "Problem with score file %s" file)
+                    (ding) 
+                    (sit-for 2)
+                    nil))))))
+      (if (eq (car alist) 'setq)
+         ;; This is an old-style score file.
+         (setq gnus-score-alist (gnus-score-transform-old-to-new alist))
+       (setq gnus-score-alist alist))
+      ;; Check the syntax of the score file.
+      (setq gnus-score-alist
+           (gnus-score-check-syntax gnus-score-alist file)))))
 
 (defun gnus-score-check-syntax (alist file)
+  "Check the syntax of the score ALIST."
   (cond 
    ((null alist)
     nil)
@@ -802,20 +979,41 @@ SCORE is the score to add."
     nil)
    (t
     (let ((a alist)
-         err)
+         sr err s type)
       (while (and a (not err))
-       (cond ((not (listp (car a)))
-              (gnus-message 3 "Illegal score element %s in %s" (car a) file)
-              (setq err t))
-             ((and (stringp (car (car a)))
-                   (not (listp (nth 1 (car a)))))
-              (gnus-message 3 "Illegal header match %s in %s" (nth 1 (car a)) file)
-              (setq err t))
-             (t
-              (setq a (cdr a)))))
+       (setq
+        err
+        (cond
+         ((not (listp (car a)))
+          (format "Illegal score element %s in %s" (car a) file))
+         ((stringp (caar a))
+          (cond 
+           ((not (listp (setq sr (cdar a))))
+            (format "Illegal header match %s in %s" (nth 1 (car a)) file))
+           (t
+            (setq type (caar a))
+            (while (and sr (not err))
+              (setq s (pop sr))
+              (setq 
+               err
+               (cond
+                ((if (member (downcase type) '("lines" "chars"))
+                     (not (numberp (car s)))
+                   (not (stringp (car s))))
+                 (format "Illegal match %s in %s" (car s) file))
+                ((and (cadr s) (not (integerp (cadr s))))
+                 (format "Non-integer score %s in %s" (cadr s) file))
+                ((and (caddr s) (not (integerp (caddr s))))
+                 (format "Non-integer date %s in %s" (caddr s) file))
+                ((and (cadddr s) (not (symbolp (cadddr s))))
+                 (format "Non-symbol match type %s in %s" (cadddr s) file)))))
+            err)))))
+       (setq a (cdr a)))
       (if err
          (progn
            (ding)
+           (gnus-message 3 err)
+           (sit-for 2)
            nil)
        alist)))))    
 
@@ -831,7 +1029,7 @@ SCORE is the score to add."
            (setq out (cons entry out))
            (while scor
              (setcar scor
-                     (list (car (car scor)) (nth 2 (car scor))
+                     (list (caar scor) (nth 2 (car scor))
                            (and (nth 3 (car scor))
                                 (gnus-day-number (nth 3 (car scor))))
                            (if (nth 1 (car scor)) 'r 's)))
@@ -864,11 +1062,13 @@ SCORE is the score to add."
            (setq score (setcdr entry (delq (assq 'touched score) score)))
            (erase-buffer)
            (let (emacs-lisp-mode-hook)
-             (if (string-match (concat gnus-adaptive-file-suffix "$") file)
+             (if (string-match 
+                  (concat (regexp-quote gnus-adaptive-file-suffix)
+                          "$") file)
                  ;; This is an adaptive score file, so we do not run
                  ;; it through `pp'.  These files can get huge, and
                  ;; are not meant to be edited by human hands.
-                 (insert (format "%S" score))
+                 (prin1 score (current-buffer))
                ;; This is a normal score file, so we print it very
                ;; prettily. 
                (pp score (current-buffer))))
@@ -878,9 +1078,10 @@ SCORE is the score to add."
              (if (zerop (buffer-size))
                  (delete-file file)
                ;; There are scores, so we write the file. 
-               (and (file-writable-p file)
-                    (write-region (point-min) (point-max) 
-                                  file nil 'silent))))
+               (when (file-writable-p file)
+                 (write-region (point-min) (point-max) file nil 'silent)
+                 (and gnus-score-after-write-file-function
+                      (funcall gnus-score-after-write-file-function file)))))
            (and gnus-score-uncacheable-files
                 (string-match gnus-score-uncacheable-files file)
                 (gnus-score-remove-from-cache file)))))
@@ -888,7 +1089,7 @@ SCORE is the score to add."
   
 (defun gnus-score-headers (score-files &optional trace)
   ;; Score `gnus-newsgroup-headers'.
-  (let (scores)
+  (let (scores news)
     ;; PLM: probably this is not the best place to clear orphan-score
     (setq gnus-orphan-score nil)
     (setq gnus-scores-articles nil)
@@ -912,68 +1113,69 @@ SCORE is the score to add."
               (member (car c) gnus-scores-exclude-files)
               (setq scores (delq (car s) scores)))
          (setq s (cdr s)))))
+    (setq news scores)
     ;; Do the scoring.
-    (when (and gnus-summary-default-score
-              scores
-              (> (length gnus-newsgroup-headers)
-                 (length gnus-newsgroup-scored)))
-      (let* ((entries gnus-header-index)
-            (now (gnus-day-number (current-time-string)))
-            (expire (- now gnus-score-expiry-days))
-            (headers gnus-newsgroup-headers)
-            (current-score-file gnus-current-score-file)
-            entry header)
-       (gnus-message 5 "Scoring...")
-       ;; Create articles, an alist of the form `(HEADER . SCORE)'.
-       (while headers
-         (setq header (car headers)
-               headers (cdr headers))
-         ;; WARNING: The assq makes the function O(N*S) while it could
-         ;; be written as O(N+S), where N is (length gnus-newsgroup-headers)
-         ;; and S is (length gnus-newsgroup-scored).
-         (or (assq (mail-header-number header) gnus-newsgroup-scored)
-             (setq gnus-scores-articles ;Total of 2 * N cons-cells used.
-                   (cons (cons header (or gnus-summary-default-score 0))
-                         gnus-scores-articles))))
+    (while news
+      (setq scores news
+           news nil)
+      (when (and gnus-summary-default-score
+                scores)
+       (let* ((entries gnus-header-index)
+              (now (gnus-day-number (current-time-string)))
+              (expire (and gnus-score-expiry-days
+                           (- now gnus-score-expiry-days)))
+              (headers gnus-newsgroup-headers)
+              (current-score-file gnus-current-score-file)
+              entry header new)
+         (gnus-message 5 "Scoring...")
+         ;; Create articles, an alist of the form `(HEADER . SCORE)'.
+         (while (setq header (pop headers))
+           ;; WARNING: The assq makes the function O(N*S) while it could
+           ;; be written as O(N+S), where N is (length gnus-newsgroup-headers)
+           ;; and S is (length gnus-newsgroup-scored).
+           (or (assq (mail-header-number header) gnus-newsgroup-scored)
+               (setq gnus-scores-articles ;Total of 2 * N cons-cells used.
+                     (cons (cons header (or gnus-summary-default-score 0))
+                           gnus-scores-articles))))
 
-       (save-excursion
-         (set-buffer (get-buffer-create "*Headers*"))
-         (buffer-disable-undo (current-buffer))
-
-         ;; Set the global variant of this variable.
-         (setq gnus-current-score-file current-score-file)
-          ;; score orphans
-          (if gnus-orphan-score 
-              (progn
-                (setq gnus-score-index 
-                      (nth 1 (assoc "references" gnus-header-index)))
-                (gnus-score-orphans gnus-orphan-score)))
-         ;; Run each header through the score process.
-         (while entries
-           (setq entry (car entries)
-                 header (downcase (nth 0 entry))
-                 entries (cdr entries))
-           (setq gnus-score-index (nth 1 (assoc header gnus-header-index)))
-           (if (< 0 (apply 'max (mapcar
-                                 (lambda (score)
-                                   (length (gnus-score-get header score)))
-                                 scores)))
+         (save-excursion
+           (set-buffer (get-buffer-create "*Headers*"))
+           (buffer-disable-undo (current-buffer))
+
+           ;; Set the global variant of this variable.
+           (setq gnus-current-score-file current-score-file)
+           ;; score orphans
+           (when gnus-orphan-score 
+             (setq gnus-score-index 
+                   (nth 1 (assoc "references" gnus-header-index)))
+             (gnus-score-orphans gnus-orphan-score))
+           ;; Run each header through the score process.
+           (while entries
+             (setq entry (pop entries)
+                   header (nth 0 entry)
+                   gnus-score-index (nth 1 (assoc header gnus-header-index)))
+             (when (< 0 (apply 'max (mapcar
+                                     (lambda (score)
+                                       (length (gnus-score-get header score)))
+                                     scores)))
                ;; Call the scoring function for this type of "header".
-               (funcall (nth 2 entry) scores header now expire trace)))
-         ;; Remove the buffer.
-         (kill-buffer (current-buffer)))
+               (when (setq new (funcall (nth 2 entry) scores header
+                                        now expire trace))
+                 (push new news))))
+           ;; Remove the buffer.
+           (kill-buffer (current-buffer)))
 
-       ;; Add articles to `gnus-newsgroup-scored'.
-       (while gnus-scores-articles
-         (or (= gnus-summary-default-score (cdr (car gnus-scores-articles)))
-             (setq gnus-newsgroup-scored
-                   (cons (cons (mail-header-number 
-                                (car (car gnus-scores-articles)))
-                               (cdr (car gnus-scores-articles)))
-                         gnus-newsgroup-scored)))
-         (setq gnus-scores-articles (cdr gnus-scores-articles)))
+         ;; Add articles to `gnus-newsgroup-scored'.
+         (while gnus-scores-articles
+           (or (= gnus-summary-default-score (cdar gnus-scores-articles))
+               (setq gnus-newsgroup-scored
+                     (cons (cons (mail-header-number 
+                                  (caar gnus-scores-articles))
+                                 (cdar gnus-scores-articles))
+                           gnus-newsgroup-scored)))
+           (setq gnus-scores-articles (cdr gnus-scores-articles)))
 
-       (gnus-message 5 "Scoring...done")))))
+         (gnus-message 5 "Scoring...done"))))))
 
 
 (defun gnus-get-new-thread-ids (articles)
@@ -1012,7 +1214,7 @@ SCORE is the score to add."
             (if last
                 (progn
                   ;; Insert the line, with a text property on the
-                  ;; terminating newline refering to the articles with
+                  ;; terminating newline referring to the articles with
                   ;; this line.
                   (insert last ?\n)
                   (put-text-property (1- (point)) (point) 'articles alike)))
@@ -1075,7 +1277,7 @@ SCORE is the score to add."
          ;; time than one would gain.
          (while articles
            (and (funcall match-func 
-                         (or (aref (car (car articles)) gnus-score-index) 0)
+                         (or (aref (caar articles) gnus-score-index) 0)
                          match)
                 (progn
                   (and trace (setq gnus-score-trace 
@@ -1085,18 +1287,19 @@ SCORE is the score to add."
                                      kill)
                                     gnus-score-trace)))
                   (setq found t)
-                  (setcdr (car articles) (+ score (cdr (car articles))))))
+                  (setcdr (car articles) (+ score (cdar articles)))))
            (setq articles (cdr articles)))
          ;; Update expire date
          (cond ((null date))           ;Permanent entry.
-               (found                  ;Match, update date.
+               ((and found gnus-update-score-entry-dates) ;Match, update date.
                 (gnus-score-set 'touched '(t) alist)
                 (setcar (nthcdr 2 kill) now))
-               ((< date expire)        ;Old entry, remove.
+               ((and expire (< date expire)) ;Old entry, remove.
                 (gnus-score-set 'touched '(t) alist)
                 (setcdr entries (cdr rest))
                 (setq rest entries)))
-         (setq entries rest))))))
+         (setq entries rest)))))
+  nil)
 
 (defun gnus-score-date (scores header now expire &optional trace)
   (let ((gnus-score-index (nth 1 (assoc header gnus-header-index)))
@@ -1129,7 +1332,7 @@ SCORE is the score to add."
          ;; time than one would gain.
          (while articles
            (and
-            (setq l (aref (car (car articles)) gnus-score-index))
+            (setq l (aref (caar articles) gnus-score-index))
             (funcall match-func match (timezone-make-date-sortable l))
             (progn
               (and trace (setq gnus-score-trace 
@@ -1139,125 +1342,141 @@ SCORE is the score to add."
                                  kill)
                                 gnus-score-trace)))
               (setq found t)
-              (setcdr (car articles) (+ score (cdr (car articles))))))
+              (setcdr (car articles) (+ score (cdar articles)))))
            (setq articles (cdr articles)))
          ;; Update expire date
          (cond ((null date))           ;Permanent entry.
-               (found                  ;Match, update date.
+               ((and found gnus-update-score-entry-dates) ;Match, update date.
                 (gnus-score-set 'touched '(t) alist)
                 (setcar (nthcdr 2 kill) now))
-               ((< date expire)        ;Old entry, remove.
+               ((and expire (< date expire))   ;Old entry, remove.
                 (gnus-score-set 'touched '(t) alist)
                 (setcdr entries (cdr rest))
                 (setq rest entries)))
-         (setq entries rest))))))
+         (setq entries rest)))))
+  nil)
 
 (defun gnus-score-body (scores header now expire &optional trace)
   (save-excursion
     (set-buffer nntp-server-buffer)
+    (setq gnus-scores-articles
+         (sort gnus-scores-articles
+               (lambda (a1 a2)
+                 (< (mail-header-number (car a1))
+                    (mail-header-number (car a2))))))
     (save-restriction
       (let* ((buffer-read-only nil)
             (articles gnus-scores-articles)
-            (last (mail-header-number (car (car gnus-scores-articles))))
             (all-scores scores)
-            (request-func (cond ((string= "head" (downcase header))
+            (request-func (cond ((string= "head" header)
                                  'gnus-request-head)
-                                ((string= "body" (downcase header))
+                                ((string= "body" header)
                                  'gnus-request-body)
                                 (t 'gnus-request-article)))
-            entries alist ofunc article)
-       ;; Not all backends support partial fetching.  In that case,
-       ;; we just fetch the entire article.
-       (or (gnus-check-backend-function 
-            (and (string-match "^gnus-" (symbol-name request-func))
-                 (intern (substring (symbol-name request-func)
-                                    (match-end 0))))
-            gnus-newsgroup-name)
-           (progn
-             (setq ofunc request-func)
-             (setq request-func 'gnus-request-article)))
-       (while articles
-         (setq article (mail-header-number (car (car articles))))
-         (gnus-message 7 "Scoring on article %s of %s..." article last)
-         (if (not (funcall request-func article gnus-newsgroup-name))
-             ()
-           (widen)
-           (goto-char (point-min))
-           ;; If just parts of the article is to be searched, but the
-           ;; backend didn't support partial fetching, we just narrow
-           ;; to the relevant parts.
-           (if ofunc
-               (if (eq ofunc 'gnus-request-head)
+            entries alist ofunc article last)
+       (when articles
+         (while (cdr articles)
+           (setq articles (cdr articles)))
+         (setq last (mail-header-number (caar articles)))
+         (setq articles gnus-scores-articles)
+         ;; Not all backends support partial fetching.  In that case,
+         ;; we just fetch the entire article.
+         (or (gnus-check-backend-function 
+              (and (string-match "^gnus-" (symbol-name request-func))
+                   (intern (substring (symbol-name request-func)
+                                      (match-end 0))))
+              gnus-newsgroup-name)
+             (progn
+               (setq ofunc request-func)
+               (setq request-func 'gnus-request-article)))
+         (while articles
+           (setq article (mail-header-number (caar articles)))
+           (gnus-message 7 "Scoring on article %s of %s..." article last)
+           (when (funcall request-func article gnus-newsgroup-name)
+             (widen)
+             (goto-char (point-min))
+             ;; If just parts of the article is to be searched, but the
+             ;; backend didn't support partial fetching, we just narrow
+             ;; to the relevant parts.
+             (if ofunc
+                 (if (eq ofunc 'gnus-request-head)
+                     (narrow-to-region
+                      (point)
+                      (or (search-forward "\n\n" nil t) (point-max)))
                    (narrow-to-region
-                    (point)
-                    (or (search-forward "\n\n" nil t) (point-max)))
-                 (narrow-to-region
-                  (or (search-forward "\n\n" nil t) (point))
-                  (point-max))))
-           (setq scores all-scores)
-           ;; Find matches.
-           (while scores
-             (setq alist (car scores)
-                   scores (cdr scores)
-                   entries (assoc header alist))
-             (while (cdr entries)      ;First entry is the header index.
-               (let* ((rest (cdr entries))             
-                      (kill (car rest))
-                      (match (nth 0 kill))
-                      (type (or (nth 3 kill) 's))
-                      (score (or (nth 1 kill) 
-                                 gnus-score-interactive-default-score))
-                      (date (nth 2 kill))
-                      (found nil)
-                      (case-fold-search 
-                       (not (or (eq type 'R) (eq type 'S)
-                                (eq type 'Regexp) (eq type 'String))))
-                      (search-func 
-                       (cond ((or (eq type 'r) (eq type 'R)
-                                  (eq type 'regexp) (eq type 'Regexp))
-                              're-search-forward)
-                             ((or (eq type 's) (eq type 'S)
-                                  (eq type 'string) (eq type 'String))
-                              'search-forward)
-                             (t
-                              (error "Illegal match type: %s" type)))))
-                 (goto-char (point-min))
-                 (if (funcall search-func match nil t)
-                     ;; Found a match, update scores.
-                     (progn
-                       (setcdr (car articles) (+ score (cdr (car articles))))
-                       (setq found t)
-                       (and trace (setq gnus-score-trace 
-                                        (cons
-                                         (cons
-                                          (car-safe
-                                           (rassq alist gnus-score-cache))
-                                          kill)
-                                         gnus-score-trace)))))
-                 ;; Update expire date
-                 (cond ((null date))   ;Permanent entry.
-                       (found          ;Match, update date.
-                        (gnus-score-set 'touched '(t) alist)
-                        (setcar (nthcdr 2 kill) now))
-                       ((< date expire) ;Old entry, remove.
-                        (gnus-score-set 'touched '(t) alist)
-                        (setcdr entries (cdr rest))
-                        (setq rest entries)))
-                 (setq entries rest)))))
-         (setq articles (cdr articles)))))))
-
-(defun gnus-score-followup (scores header now expire &optional trace)
+                    (or (search-forward "\n\n" nil t) (point))
+                    (point-max))))
+             (setq scores all-scores)
+             ;; Find matches.
+             (while scores
+               (setq alist (car scores)
+                     scores (cdr scores)
+                     entries (assoc header alist))
+               (while (cdr entries)    ;First entry is the header index.
+                 (let* ((rest (cdr entries))           
+                        (kill (car rest))
+                        (match (nth 0 kill))
+                        (type (or (nth 3 kill) 's))
+                        (score (or (nth 1 kill) 
+                                   gnus-score-interactive-default-score))
+                        (date (nth 2 kill))
+                        (found nil)
+                        (case-fold-search 
+                         (not (or (eq type 'R) (eq type 'S)
+                                  (eq type 'Regexp) (eq type 'String))))
+                        (search-func 
+                         (cond ((or (eq type 'r) (eq type 'R)
+                                    (eq type 'regexp) (eq type 'Regexp))
+                                're-search-forward)
+                               ((or (eq type 's) (eq type 'S)
+                                    (eq type 'string) (eq type 'String))
+                                'search-forward)
+                               (t
+                                (error "Illegal match type: %s" type)))))
+                   (goto-char (point-min))
+                   (if (funcall search-func match nil t)
+                       ;; Found a match, update scores.
+                       (progn
+                         (setcdr (car articles) (+ score (cdar articles)))
+                         (setq found t)
+                         (and trace (setq gnus-score-trace 
+                                          (cons
+                                           (cons
+                                            (car-safe
+                                             (rassq alist gnus-score-cache))
+                                            kill)
+                                           gnus-score-trace)))))
+                   ;; Update expire date
+                   (cond
+                    ((null date))      ;Permanent entry.
+                    ((and found gnus-update-score-entry-dates) ;Match, update date.
+                     (gnus-score-set 'touched '(t) alist)
+                     (setcar (nthcdr 2 kill) now))
+                    ((and expire (< date expire)) ;Old entry, remove.
+                     (gnus-score-set 'touched '(t) alist)
+                     (setcdr entries (cdr rest))
+                     (setq rest entries)))
+                   (setq entries rest)))))
+           (setq articles (cdr articles)))))))
+  nil)
+
+(defun gnus-score-followup (scores header now expire &optional trace thread)
   ;; Insert the unique article headers in the buffer.
   (let ((gnus-score-index (nth 1 (assoc header gnus-header-index)))
        (current-score-file gnus-current-score-file)
        (all-scores scores)
        ;; gnus-score-index is used as a free variable.
-       alike last this art entries alist articles)
+       alike last this art entries alist articles
+       new news)
 
     ;; Change score file to the adaptive score file.  All entries that
     ;; this function makes will be put into this file.
-    (gnus-score-load-file (gnus-score-file-name 
-                          gnus-newsgroup-name gnus-adaptive-file-suffix))
+    (save-excursion
+      (set-buffer gnus-summary-buffer)
+      (gnus-score-load-file
+       (or gnus-newsgroup-adaptive-score-file
+          (gnus-score-file-name 
+           gnus-newsgroup-name gnus-adaptive-file-suffix))))
 
     (setq gnus-scores-articles (sort gnus-scores-articles 'gnus-score-string<)
          articles gnus-scores-articles)
@@ -1317,52 +1536,54 @@ SCORE is the score to add."
                         (setq art (car arts)
                               arts (cdr arts))
                         (gnus-score-add-followups 
-                         (car art) score all-scores)))))
+                         (car art) score all-scores thread))))
+               (end-of-line))
            (while (funcall search-func match nil t)
              (end-of-line)
              (setq found (setq arts (get-text-property (point) 'articles)))
              ;; Found a match, update scores.
-             (while arts
-               (setq art (car arts)
-                     arts (cdr arts))
-               (gnus-score-add-followups (car art) score all-scores))))
+             (while (setq art (pop arts))
+               (when (setq new (gnus-score-add-followups
+                                (car art) score all-scores thread))
+                 (push new news)))))
          ;; Update expire date
          (cond ((null date))           ;Permanent entry.
-               (found                  ;Match, update date.
+               ((and found gnus-update-score-entry-dates) ;Match, update date.
                 (gnus-score-set 'touched '(t) alist)
                 (setcar (nthcdr 2 kill) now))
-               ((< date expire)        ;Old entry, remove.
+               ((and expire (< date expire))   ;Old entry, remove.
                 (gnus-score-set 'touched '(t) alist)
                 (setcdr entries (cdr rest))
                 (setq rest entries)))
          (setq entries rest))))
     ;; We change the score file back to the previous one.
-    (gnus-score-load-file current-score-file)))
+    (save-excursion
+      (set-buffer gnus-summary-buffer)
+      (gnus-score-load-file current-score-file))
+    (list (cons "references" news))))
 
-(defun gnus-score-add-followups (header score scores)
+(defun gnus-score-add-followups (header score scores &optional thread)
+  "Add a score entry to the adapt file."
   (save-excursion
     (set-buffer gnus-summary-buffer)
     (let* ((id (mail-header-id header))
           (scores (car scores))
           entry dont)
       ;; Don't enter a score if there already is one.
-      (while scores
-       (setq entry (car scores))
+      (while (setq entry (pop scores))
        (and (equal "references" (car entry))
-            (or (null (nth 3 (car (cdr entry))))
-                (eq 's (nth 3 (car (cdr entry)))))
-            (progn
-              (if (assoc id entry)
-                  (setq dont t))))
-       (setq scores (cdr scores)))
-      (or dont
-         (gnus-summary-score-entry 
-          "references" id 's score (current-time-string) nil t)))))
-
+            (or (null (nth 3 (cadr entry)))
+                (eq 's (nth 3 (cadr entry))))
+            (assoc id entry)
+            (setq dont t)))
+      (unless dont
+       (gnus-summary-score-entry 
+        (if thread "thread" "references")
+        id 's score (current-time-string) nil t)))))
 
 (defun gnus-score-string (score-list header now expire &optional trace)
   ;; Score ARTICLES according to HEADER in SCORE-LIST.
-  ;; Update matches entries to NOW and remove unmatched entried older
+  ;; Update matching entries to NOW and remove unmatched entries older
   ;; than EXPIRE.
   
   ;; Insert the unique article headers in the buffer.
@@ -1391,7 +1612,7 @@ SCORE is the score to add."
        (if last
            (progn
              ;; Insert the line, with a text property on the
-             ;; terminating newline refering to the articles with
+             ;; terminating newline referring to the articles with
              ;; this line.
              (insert last ?\n)
              (put-text-property (1- (point)) (point) 'articles alike)))
@@ -1479,14 +1700,15 @@ SCORE is the score to add."
                    (setcdr art (+ score (cdr art)))))
                (forward-line 1)))
            ;; Update expire date
-           (cond ((null date))         ;Permanent entry.
-                 (found                ;Match, update date.
-                  (gnus-score-set 'touched '(t) alist)
-                  (setcar (nthcdr 2 kill) now))
-                 ((< date expire)      ;Old entry, remove.
-                  (gnus-score-set 'touched '(t) alist)
-                  (setcdr entries (cdr rest))
-                  (setq rest entries))))
+           (cond 
+            ((null date))              ;Permanent entry.
+            ((and found gnus-update-score-entry-dates) ;Match, update date.
+             (gnus-score-set 'touched '(t) alist)
+             (setcar (nthcdr 2 kill) now))
+            ((and expire (< date expire)) ;Old entry, remove.
+             (gnus-score-set 'touched '(t) alist)
+             (setcdr entries (cdr rest))
+             (setq rest entries))))
          (setq entries rest))))
 
     ;; Find fuzzy matches.
@@ -1534,15 +1756,17 @@ SCORE is the score to add."
                (forward-line 1))
              ;; Update expire date
              (unless trace
-               (cond ((null date))             ;Permanent entry.
-                     (found            ;Match, update date.
-                      (gnus-score-set 'touched '(t) alist)
-                      (setcar (nthcdr 2 kill) now))
-                     ((< date expire)  ;Old entry, remove.
-                      (gnus-score-set 'touched '(t) alist)
-                      (setcdr entries (cdr rest))
-                      (setq rest entries)))))
-           (setq entries rest)))))))
+               (cond 
+                ((null date))          ;Permanent entry.
+                ((and found gnus-update-score-entry-dates) ;Match, update date.
+                 (gnus-score-set 'touched '(t) alist)
+                 (setcar (nthcdr 2 kill) now))
+                ((and expire (< date expire)) ;Old entry, remove.
+                 (gnus-score-set 'touched '(t) alist)
+                 (setcdr entries (cdr rest))
+                 (setq rest entries)))))
+           (setq entries rest))))))
+  nil)
 
 (defun gnus-score-string< (a1 a2)
   ;; Compare headers in articles A2 and A2.
@@ -1554,22 +1778,6 @@ SCORE is the score to add."
   ;; Build a `gnus-newsgroup-scored' type cons from ARTICLE.
   (cons (mail-header-number (car article)) (cdr article)))
 
-(defconst gnus-header-index
-  ;; Name to index alist.
-  '(("number" 0 gnus-score-integer)
-    ("subject" 1 gnus-score-string)
-    ("from" 2 gnus-score-string)
-    ("date" 3 gnus-score-date)
-    ("message-id" 4 gnus-score-string) 
-    ("references" 5 gnus-score-string) 
-    ("chars" 6 gnus-score-integer) 
-    ("lines" 7 gnus-score-integer) 
-    ("xref" 8 gnus-score-string)
-    ("head" -1 gnus-score-body)
-    ("body" -1 gnus-score-body)
-    ("all" -1 gnus-score-body)
-    ("followup" 2 gnus-score-followup)))
-
 (defun gnus-current-score-file-nondirectory (&optional score-file)
   (let ((score-file (or score-file gnus-current-score-file)))
     (if score-file 
@@ -1592,23 +1800,27 @@ SCORE is the score to add."
        (setq elem (cdr elem))
        (while elem
          (setcdr (car elem) 
-                 (cons (if (eq (car (car elem)) 'followup)
+                 (cons (if (eq (caar elem) 'followup)
                            "references"
-                         (symbol-name (car (car elem))))
-                       (cdr (car elem))))
+                         (symbol-name (caar elem)))
+                       (cdar elem)))
          (setcar (car elem) 
-                 (intern 
-                  (concat "gnus-header-" 
-                          (if (eq (car (car elem)) 'followup)
-                              "message-id"
-                            (downcase (symbol-name (car (car elem))))))))
+                 `(lambda (h)
+                    (,(intern 
+                       (concat "mail-header-" 
+                               (if (eq (caar elem) 'followup)
+                                   "message-id"
+                                 (downcase (symbol-name (caar elem))))))
+                     h)))
          (setq elem (cdr elem)))
        (setq malist (cdr malist)))
       ;; We change the score file to the adaptive score file.
-      (gnus-score-load-file 
-       (or gnus-newsgroup-adaptive-score-file
-          (gnus-score-file-name 
-           gnus-newsgroup-name gnus-adaptive-file-suffix)))
+      (save-excursion
+       (set-buffer gnus-summary-buffer)
+       (gnus-score-load-file 
+        (or gnus-newsgroup-adaptive-score-file
+            (gnus-score-file-name 
+             gnus-newsgroup-name gnus-adaptive-file-suffix))))
       ;; The we score away.
       (while data
        (setq elem (cdr (assq (gnus-data-mark (car data)) alist)))
@@ -1617,7 +1829,7 @@ SCORE is the score to add."
            ()
          (when (setq headers (gnus-data-header (car data)))
            (while elem 
-             (setq match (funcall (car (car elem)) headers))
+             (setq match (funcall (caar elem) headers))
              (gnus-summary-score-entry 
               (nth 1 (car elem)) match
               (cond
@@ -1637,82 +1849,67 @@ SCORE is the score to add."
              (setq elem (cdr elem)))))
        (setq data (cdr data))))))
 
-;;;
-;;; Score mode.
-;;;
-
-(defvar gnus-score-mode-map nil)
-(defvar gnus-score-mode-hook nil)
-
-(if gnus-score-mode-map
-    ()
-  (setq gnus-score-mode-map (copy-keymap emacs-lisp-mode-map))
-  (define-key gnus-score-mode-map "\C-c\C-c" 'gnus-score-edit-done)
-  (define-key gnus-score-mode-map "\C-c\C-d" 'gnus-score-edit-insert-date))
-
-(defun gnus-score-mode ()
-  "Mode for editing score files.
-This mode is an extended emacs-lisp mode.
-
-\\{gnus-score-mode-map}"
-  (interactive)
-  (kill-all-local-variables)
-  (use-local-map gnus-score-mode-map)
-  (set-syntax-table emacs-lisp-mode-syntax-table)
-  (setq major-mode 'gnus-score-mode)
-  (setq mode-name "Score")
-  (lisp-mode-variables nil)
-  (run-hooks 'emacs-lisp-mode-hook 'gnus-score-mode-hook))
-
-(defun gnus-score-edit-insert-date ()
-  "Insert date in numerical format."
-  (interactive)
-  (insert (int-to-string (gnus-day-number (current-time-string)))))
-
 (defun gnus-score-edit-done ()
-  "Save the score file and return to the summary buffer."
-  (interactive)
   (let ((bufnam (buffer-file-name (current-buffer)))
        (winconf gnus-prev-winconf))
-    (gnus-make-directory (file-name-directory (buffer-file-name)))
-    (save-buffer)
-    (kill-buffer (current-buffer))
+    (and winconf (set-window-configuration winconf))
     (gnus-score-remove-from-cache bufnam)
-    (gnus-score-load-file bufnam)
-    (and winconf (set-window-configuration winconf))))
+    (gnus-score-load-file bufnam)))
 
 (defun gnus-score-find-trace ()
-  "Find all score rules applied to this article."
+  "Find all score rules that applies to the current article."
   (interactive)
   (let ((gnus-newsgroup-headers
         (list (gnus-summary-article-header)))
        (gnus-newsgroup-scored nil)
        (buf (current-buffer))
        trace)
+    (when (get-buffer "*Gnus Scores*")
+      (save-excursion
+       (set-buffer "*Gnus Scores*")
+       (erase-buffer)))
     (setq gnus-score-trace nil)
     (gnus-possibly-score-headers 'trace)
-    (or (setq trace gnus-score-trace)
-       (error "No score rules apply to the current article."))
-    (pop-to-buffer "*Gnus Scores*")
-    (gnus-add-current-to-buffer-list)
-    (erase-buffer)
-    (while trace
-      (insert (format "%S  ->  %s\n"  (cdr (car trace))
-                     (file-name-nondirectory (car (car trace)))))
-      (setq trace (cdr trace)))
-    (goto-char (point-min))
-    (pop-to-buffer buf)))
+    (if (not (setq trace gnus-score-trace))
+       (gnus-error 1 "No score rules apply to the current article.")
+      (pop-to-buffer "*Gnus Scores*")
+      (gnus-add-current-to-buffer-list)
+      (erase-buffer)
+      (while trace
+       (insert (format "%S  ->  %s\n" (cdar trace)
+                       (file-name-nondirectory (caar trace))))
+       (setq trace (cdr trace)))
+      (goto-char (point-min))
+      (pop-to-buffer buf))))
+
+(defun gnus-summary-rescore ()
+  "Redo the entire scoring process in the current summary."
+  (interactive)
+  (gnus-score-save)
+  (setq gnus-score-cache nil)
+  (setq gnus-newsgroup-scored nil)
+  (gnus-possibly-score-headers)
+  (gnus-score-update-all-lines))
   
 (defun gnus-score-flush-cache ()
   "Flush the cache of score files."
   (interactive)
-  (setq gnus-score-cache nil)
+  (gnus-score-save)
+  (setq gnus-score-cache nil
+       gnus-score-alist nil
+       gnus-short-name-score-file-cache nil)
   (gnus-message 6 "The score cache is now flushed"))
 
+(gnus-add-shutdown 'gnus-score-close 'gnus)
+
+(defvar gnus-score-file-alist-cache nil)
+
 (defun gnus-score-close ()
   "Clear all internal score variables."
   (setq gnus-score-cache nil
-       gnus-internal-global-score-files nil))
+       gnus-internal-global-score-files nil
+       gnus-score-file-list nil
+       gnus-score-file-alist-cache nil))
 
 ;; Summary score marking commands.
 
@@ -1782,53 +1979,63 @@ This mode is an extended emacs-lisp mode.
           (gnus-score-search-global-directories gnus-global-score-files)))
   ;; Fix the kill-file dir variable.
   (setq gnus-kill-files-directory 
-       (file-name-as-directory
-        (or gnus-kill-files-directory "~/News/")))
+       (file-name-as-directory gnus-kill-files-directory))
   ;; If we can't read it, there are no score files.
   (if (not (file-exists-p (expand-file-name gnus-kill-files-directory)))
       (setq gnus-score-file-list nil)
-    (if (gnus-use-long-file-name 'not-score)
-       ;; We want long file names.
-       (if (or (not gnus-score-file-list)
+    (if (not (gnus-use-long-file-name 'not-score))
+       ;; We do not use long file names, so we have to do some
+       ;; directory traversing.  
+       (setq gnus-score-file-list 
+             (cons nil 
+                   (or gnus-short-name-score-file-cache
+                       (prog2
+                           (gnus-message 6 "Finding all score files...")
+                           (setq gnus-short-name-score-file-cache
+                                 (gnus-score-score-files-1
+                                  gnus-kill-files-directory))
+                         (gnus-message 6 "Finding all score files...done")))))
+      ;; We want long file names.
+      (when (or (not gnus-score-file-list)
                (not (car gnus-score-file-list))
                (gnus-file-newer-than gnus-kill-files-directory
                                      (car gnus-score-file-list)))
-           (setq gnus-score-file-list 
-                 (cons (nth 5 (file-attributes gnus-kill-files-directory))
-                       (nreverse 
-                        (directory-files 
-                         gnus-kill-files-directory t 
-                         (gnus-score-file-regexp))))))
-      ;; We do not use long file names, so we have to do some
-      ;; directory traversing.  
-      (let ((mdir (length (expand-file-name gnus-kill-files-directory)))
-           (suffixes (list gnus-score-file-suffix gnus-adaptive-file-suffix))
-           dir files suffix)
-       (while suffixes
-         (setq dir (expand-file-name
-                    (concat gnus-kill-files-directory
-                            (gnus-replace-chars-in-string group ?. ?/))))
-         (setq dir (gnus-replace-chars-in-string dir ?: ?/))
-         (setq suffix (car suffixes)
-               suffixes (cdr suffixes))
-         (if (file-exists-p (concat dir "/" suffix))
-             (setq files (cons (concat dir "/" suffix) files)))
-         (while (>= (1+ (length dir)) mdir)
-           (and (file-exists-p (concat dir "/all/" suffix))
-                (setq files (cons (concat dir "/all/" suffix) files)))
-           (string-match "/[^/]*$" dir)
-           (setq dir (substring dir 0 (match-beginning 0)))))
        (setq gnus-score-file-list 
-             (cons nil (nreverse files)))))
+             (cons (nth 5 (file-attributes gnus-kill-files-directory))
+                   (nreverse 
+                    (directory-files 
+                     gnus-kill-files-directory t 
+                     (gnus-score-file-regexp)))))))
     (cdr gnus-score-file-list)))
 
+(defun gnus-score-score-files-1 (dir)
+  "Return all possible score files under DIR."
+  (let ((files (directory-files (expand-file-name dir) t nil t))
+       (regexp (gnus-score-file-regexp))
+       out file)
+    (while (setq file (pop files))
+      (cond 
+       ;; Ignore "." and "..".
+       ((member (file-name-nondirectory file) '("." ".."))
+       nil)
+       ;; Recurse down directories.
+       ((file-directory-p file)
+       (setq out (nconc (gnus-score-score-files-1 file) out)))
+       ;; Add files to the list of score files.
+       ((string-match regexp file)
+       (push file out))))
+    (or out
+       ;; Return a dummy value.
+       (list "~/News/this.file.does.not.exist.SCORE"))))
+       
 (defun gnus-score-file-regexp ()
-  (concat "\\(" gnus-score-file-suffix 
-         "\\|" gnus-adaptive-file-suffix "\\)$"))
+  "Return a regexp that match all score files."
+  (concat "\\(" (regexp-quote gnus-score-file-suffix )
+         "\\|" (regexp-quote gnus-adaptive-file-suffix) "\\)\\'"))
        
 (defun gnus-score-find-bnews (group)
   "Return a list of score files for GROUP.
-The score files are those files in the ~/News directory which matches
+The score files are those files in the ~/News/ directory which matches
 GROUP using BNews sys file syntax."
   (let* ((sfiles (append (gnus-score-score-files group)
                         gnus-internal-global-score-files))
@@ -1836,6 +2043,7 @@ GROUP using BNews sys file syntax."
                    (expand-file-name gnus-kill-files-directory)))
         (klen (length kill-dir))
         (score-regexp (gnus-score-file-regexp))
+        (trans (cdr (assq ?: nnheader-file-name-translation-alist)))
         ofiles not-match regexp)
     (save-excursion
       (set-buffer (get-buffer-create "*gnus score files*"))
@@ -1859,8 +2067,10 @@ GROUP using BNews sys file syntax."
            (delete-region (1+ (point)) (point-min)))
          ;; If short file names were used, we have to translate slashes.
          (goto-char (point-min))
-         (while (re-search-forward "[/:]" nil t)
-           (replace-match "." t t))
+         (let ((regexp (concat
+                        "[/:" (if trans (char-to-string trans) "") "]")))
+           (while (re-search-forward regexp nil t)
+             (replace-match "." t t)))
          ;; Cludge to get rid of "nntp+" problems.
          (goto-char (point-min))
          (and (looking-at "nn[a-z]+\\+")
@@ -1868,7 +2078,12 @@ GROUP using BNews sys file syntax."
                 (search-forward "+")
                 (forward-char -1)
                 (insert "\\")))
+         ;; Kludge to deal with "++".
+         (goto-char (point-min))
+         (while (search-forward "++" nil t)
+           (replace-match "\\+\\+" t t))
          ;; Translate "all" to ".*".
+         (goto-char (point-min))
          (while (search-forward "all" nil t)
            (replace-match ".*" t t))
          (goto-char (point-min))
@@ -1899,7 +2114,8 @@ GROUP using BNews sys file syntax."
 
 (defun gnus-score-find-single (group)
   "Return list containing the score file for GROUP."
-  (list (gnus-score-file-name group gnus-adaptive-file-suffix)
+  (list (or gnus-newsgroup-adaptive-score-file
+           (gnus-score-file-name group gnus-adaptive-file-suffix))
        (gnus-score-file-name group)))
 
 (defun gnus-score-find-hierarchical (group)
@@ -1917,8 +2133,6 @@ This includes the score file for the group and all its parents."
             (setq all (nreverse all)))
      (mapcar 'gnus-score-file-name all))))
 
-(defvar gnus-score-file-alist-cache nil)
-
 (defun gnus-score-find-alist (group)
   "Return list of score files for GROUP.
 The list is determined from the variable gnus-score-file-alist."
@@ -1929,21 +2143,21 @@ The list is determined from the variable gnus-score-file-alist."
        (cdr score-files)               ;ensures caching groups with no matches
       ;; handle the multiple match alist
       (while alist
-       (and (string-match (car (car alist)) group)
+       (and (string-match (caar alist) group)
             (setq score-files
-                  (nconc score-files (copy-sequence (cdr (car alist))))))
+                  (nconc score-files (copy-sequence (cdar alist)))))
        (setq alist (cdr alist)))
       (setq alist gnus-score-file-single-match-alist)
       ;; handle the single match alist
       (while alist
-       (and (string-match (car (car alist)) group)
+       (and (string-match (caar alist) group)
             ;; progn used just in case ("regexp") has no files
             ;; and score-files is still nil. -sj
             ;; this can be construed as a "stop searching here" feature :>
             ;; and used to simplify regexps in the single-alist 
             (progn
               (setq score-files
-                    (nconc score-files (copy-sequence (cdr (car alist)))))
+                    (nconc score-files (copy-sequence (cdar alist))))
               (setq alist nil)))
        (setq alist (cdr alist)))
       ;; cache the score files
@@ -1952,41 +2166,51 @@ The list is determined from the variable gnus-score-file-alist."
       score-files)))
 
 (defun gnus-possibly-score-headers (&optional trace)
-  (let ((func gnus-score-find-score-files-function)
+  (let ((funcs gnus-score-find-score-files-function)
        score-files)
-    (and func 
-        (not (listp func))
-        (setq func (list func)))
+    ;; Make sure funcs is a list.
+    (and funcs
+        (not (listp funcs))
+        (setq funcs (list funcs)))
+    ;; Get the initial score files for this group.
+    (when funcs 
+      (setq score-files (gnus-score-find-alist gnus-newsgroup-name)))
     ;; Go through all the functions for finding score files (or actual
     ;; scores) and add them to a list.
-    (and func (setq score-files (gnus-score-find-alist gnus-newsgroup-name)))
-    (while func
-      (and (symbolp (car func))
-          (fboundp (car func))
-          (setq score-files 
-                (nconc score-files (funcall (car func) gnus-newsgroup-name))))
-      (setq func (cdr func)))
-    (if score-files (gnus-score-headers score-files trace))))
+    (while funcs
+      (when (gnus-functionp (car funcs))
+       (setq score-files 
+             (nconc score-files (funcall (car funcs) gnus-newsgroup-name))))
+      (setq funcs (cdr funcs)))
+    ;; Check whether there is a `score-file' group parameter.
+    (let ((param-file (gnus-group-get-parameter 
+                      gnus-newsgroup-name 'score-file)))
+      (when param-file
+       (push param-file score-files)))
+    ;; Do the scoring if there are any score files for this group.
+    (when score-files
+      (gnus-score-headers score-files trace))))
 
 (defun gnus-score-file-name (newsgroup &optional suffix)
   "Return the name of a score file for NEWSGROUP."
   (let ((suffix (or suffix gnus-score-file-suffix)))
-    (cond 
-     ((or (null newsgroup)
-         (string-equal newsgroup ""))
-      ;; The global score file is placed at top of the directory.
-      (expand-file-name 
-       suffix (or gnus-kill-files-directory "~/News")))
-     ((gnus-use-long-file-name 'not-score)
-      ;; Append ".SCORE" to newsgroup name.
-      (expand-file-name (concat (gnus-newsgroup-saveable-name newsgroup)
-                               "." suffix)
-                       (or gnus-kill-files-directory "~/News")))
-     (t
-      ;; Place "SCORE" under the hierarchical directory.
-      (expand-file-name (concat (gnus-newsgroup-directory-form newsgroup)
-                               "/" suffix)
-                       (or gnus-kill-files-directory "~/News"))))))
+    (nnheader-translate-file-chars
+     (cond
+      ((or (null newsgroup)
+          (string-equal newsgroup ""))
+       ;; The global score file is placed at top of the directory.
+       (expand-file-name 
+       suffix gnus-kill-files-directory))
+      ((gnus-use-long-file-name 'not-score)
+       ;; Append ".SCORE" to newsgroup name.
+       (expand-file-name (concat (gnus-newsgroup-savable-name newsgroup)
+                                "." suffix)
+                        gnus-kill-files-directory))
+      (t
+       ;; Place "SCORE" under the hierarchical directory.
+       (expand-file-name (concat (gnus-newsgroup-directory-form newsgroup)
+                                "/" suffix)
+                        gnus-kill-files-directory))))))
 
 (defun gnus-score-search-global-directories (files)
   "Scan all global score directories for score files."
@@ -2003,6 +2227,14 @@ The list is determined from the variable gnus-score-file-alist."
       (setq files (cdr files)))
     (setq gnus-internal-global-score-files out)))
 
+(defun gnus-score-default-fold-toggle ()
+  "Toggle folding for new score file entries."
+  (interactive)
+  (setq gnus-score-default-fold (not gnus-score-default-fold))
+  (if gnus-score-default-fold
+      (gnus-message 1 "New score file entries will be case insensitive.")
+    (gnus-message 1 "New score file entries will be case sensitive.")))
+
 (provide 'gnus-score)
 
 ;;; gnus-score.el ends here