*** empty log message ***
[gnus] / lisp / gnus-score.el
index dd4d1c9..9732f6f 100644 (file)
@@ -26,8 +26,9 @@
 
 ;;; Code:
 
-(require 'gnus)
-(eval-when-compile (require 'cl))
+(require 'gnus-load)
+(require 'gnus-sum)
+(require 'gnus-range)
 
 (defvar gnus-global-score-files nil
   "*List of global score files and directories.
@@ -109,15 +110,98 @@ 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.")
 
+(defvar gnus-decay-scores nil
+  "*If non-nil, decay non-permanent scores.")
+
+(defvar gnus-decay-score-function 'gnus-decay-score
+  "*Function called to decay a score.
+It is called with one parameter -- the score to be decayed.")
+
+(defvar gnus-score-decay-constant 3
+  "*Decay all \"small\" scores with this amount.")
+
+(defvar gnus-score-decay-scale .05
+  "*Decay all \"big\" scores with this factor.")
+
+(defvar gnus-home-score-file nil
+  "Variable to control where interative score entries are to go.
+It can be:
+
+ * A string
+   This file file will be used as the home score file.
+
+ * A function
+   The result of this function will be used as the home score file.
+
+ * A list
+   The elements in this list can be:
+
+   * `(regexp file-name ...)'
+     If the `regexp' matches the group name, the first `file-name' will
+     will be used as the home score file.  (Multiple filenames are
+     allowed so that one may use gnus-score-file-single-match-alist to
+     set this variable.)
+
+   * A function.
+     If the function returns non-nil, the result will be used
+     as the home score file.
+
+   * A string.
+     Use the string as the home score file.
+
+   The list will be traversed from the beginning towards the end looking
+   for matches.")
+
+(defvar gnus-home-adapt-file nil
+  "Variable to control where new adaptive score entries are to go.
+This variable allows the same syntax as `gnus-home-score-file'.")
+
 (defvar gnus-default-adaptive-score-alist  
   '((gnus-kill-file-mark)
     (gnus-unread-mark)
-    (gnus-read-mark (from  3) (subject  30))
+    (gnus-read-mark (from 3) (subject 30))
     (gnus-catchup-mark (subject -10))
     (gnus-killed-mark (from -1) (subject -20))
     (gnus-del-mark (from -2) (subject -15)))
 "*Alist of marks and scores.")
 
+(defvar gnus-ignored-adaptive-words nil
+  "*List of words to be ignored when doing adaptive word scoring.")
+
+(defvar gnus-default-ignored-adaptive-words
+  '("a" "i" "the" "to" "of" "and" "in" "is" "it" "for" "that" "if" "you"
+    "this" "be" "on" "with" "not" "have" "are" "or" "as" "from" "can"
+    "but" "by" "at" "an" "will" "no" "all" "was" "do" "there" "my" "one"
+    "so" "we" "they" "what" "would" "any" "which" "about" "get" "your"
+    "use" "some" "me" "then" "name" "like" "out" "when" "up" "time"
+    "other" "more" "only" "just" "end" "also" "know" "how" "new" "should"
+    "been" "than" "them" "he" "who" "make" "may" "people" "these" "now"
+    "their" "here" "into" "first" "could" "way" "had" "see" "work" "well"
+    "were" "two" "very" "where" "while" "us" "because" "good" "same"
+    "even" "much" "most" "many" "such" "long" "his" "over" "last" "since"
+    "right" "before" "our" "without" "too" "those" "why" "must" "part"
+    "being" "current" "back" "still" "go" "point" "value" "each" "did"
+    "both" "true" "off" "say" "another" "state" "might" "under" "start"
+    "try"
+
+    "re")
+  "Default list of words to be ignored when doing adaptive word scoring.")
+
+(defvar gnus-adaptive-word-syntax-table
+  (let ((table (copy-syntax-table (standard-syntax-table)))
+       (numbers '(?0 ?1 ?2 ?3 ?4 ?5 ?6 ?7 ?8 ?9)))
+    (while numbers
+      (modify-syntax-entry (pop numbers) " " table))
+    table)
+  "Syntax table used when doing adaptive word scoring.")
+
+(defvar gnus-default-adaptive-word-score-alist  
+  `((,gnus-read-mark . 30)
+    (,gnus-catchup-mark . -10)
+    (,gnus-killed-mark . -20)
+    (,gnus-del-mark . -15))
+"*Alist of marks and scores.")
+
 (defvar gnus-score-mimic-keymap nil
   "*Have the score entry functions pretend that they are a keymap.")
 
@@ -190,6 +274,7 @@ If nil, the user will be asked for a duration.")
 
 ;; Internal variables.
 
+(defvar gnus-scores-exclude-files nil)
 (defvar gnus-internal-global-score-files nil)
 (defvar gnus-score-file-list nil)
 
@@ -218,27 +303,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)
 
+
+(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)))
+
 (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
- "t" gnus-score-find-trace
- "C" gnus-score-customize)
+(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
+  "w" gnus-score-find-favourite-words
+  "C" gnus-score-customize)
 
 ;; Summary score file commands
 
@@ -295,8 +398,8 @@ used as score."
            (?e e "exact string" string)
            (?f f "fuzzy string" string)
            (?r r "regexp string" string)
-           (?s s "substring" body-string)
-           (?r s "regexp string" body-string)
+           (?z s "substring" body-string)
+           (?p s "regexp string" body-string)
            (?b before "before date" date)
            (?a at "at date" date) 
            (?n now "this date" date)
@@ -314,80 +417,93 @@ used as score."
         (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
-         (progn 
-           (sit-for 1)
-           (message "%c-" prefix))
-       (message "%s header (%s?): " (if increase "Increase" "Lower")
-                (mapconcat (lambda (s) (char-to-string (car s)))
-                           char-to-header "")))
-      (setq hchar (read-char))
-      (when (or (= hchar ??) (= hchar ?\C-h))
-       (setq hchar nil)
-       (gnus-score-insert-help "Match on header" char-to-header 1)))
-
-    (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
-           (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
-           (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 "")))
+    (unwind-protect
+       (progn
+
+         ;; First we read the header to score.
+         (while (not hchar)
+           (if mimic
+               (progn 
+                 (sit-for 1)
+                 (message "%c-" prefix))
+             (message "%s header (%s?): " (if increase "Increase" "Lower")
+                      (mapconcat (lambda (s) (char-to-string (car s)))
+                                 char-to-header "")))
+           (setq hchar (read-char))
+           (when (or (= hchar ??) (= hchar ?\C-h))
+             (setq hchar nil)
+             (gnus-score-insert-help "Match on header" char-to-header 1)))
+
+         (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
+                 (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"
+              (delq nil
+                    (mapcar (lambda (s) 
+                              (if (eq (nth 4 entry) 
+                                      (nth 3 s))
+                                  s nil))
+                            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 use 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
+                 (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 ""))))
+      ;; Always kill the score help buffer.
+      (gnus-score-kill-help-buffer))
 
     ;; We have all the data, so we enter this score.
     (setq match (if (string= (nth 2 entry) "") ""
@@ -434,8 +550,8 @@ used as score."
            (setq max n))
        (setq list (cdr list)))
       (setq max (+ max 4))             ; %c, `:', SPACE, a SPACE at end
-      (setq n (/ (window-width) max))  ; items per line
-      (setq width (/ (window-width) n)) ; width of each item
+      (setq n (/ (1- (window-width)) max))     ; items per line
+      (setq width (/ (1- (window-width)) n)) ; width of each item
       ;; insert `n' items, each in a field of width `width' 
       (while alist
        (if (< i n)
@@ -452,7 +568,8 @@ used as score."
     (gnus-appt-select-lowest-window)
     (split-window)
     (pop-to-buffer "*Score Help*")
-    (shrink-window-if-larger-than-buffer)
+    (let ((window-min-height 1))
+      (shrink-window-if-larger-than-buffer))
     (select-window (get-buffer-window gnus-summary-buffer))))
   
 (defun gnus-summary-header (header &optional no-err)
@@ -470,18 +587,25 @@ used as score."
          (error "No article on current line")
        nil))))
 
+(defun gnus-newsgroup-score-alist ()
+  (or
+   (let ((param-file (gnus-group-find-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)
+(defun gnus-summary-score-entry (header match type score date
+                                       &optional prompt silent)
   "Enter score file entry.
 HEADER is the header being scored.
 MATCH is the string we are looking for.
@@ -512,25 +636,24 @@ 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)))
-    (and prompt (setq match (read-string 
-                            (format "Match %s on %s, %s: " 
-                                    (cond ((eq date 'now)
-                                           "now")
-                                          ((stringp date)
-                                           "temp")
-                                          (t "permanent"))
-                                    header
-                                    (if (< score 0) "lower" "raise"))
-                            (if (numberp match)
-                                (int-to-string match)
-                              match))))
-
-    ;; Score the current buffer.
-    (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))
+       (header (format "%s" (downcase header)))
+       new)
+    (when prompt
+      (setq match (read-string 
+                  (format "Match %s on %s, %s: " 
+                          (cond ((eq date 'now)
+                                 "now")
+                                ((stringp date)
+                                 "temp")
+                                (t "permanent"))
+                          header
+                          (if (< score 0) "lower" "raise"))
+                  (if (numberp match)
+                      (int-to-string match)
+                    match))))
+
+    ;; Get rid of string props.
+    (setq match (format "%s" match))
 
     ;; If this is an integer comparison, we transform from string to int. 
     (and (eq (nth 2 (assoc header gnus-header-index)) 'gnus-score-integer)
@@ -540,17 +663,18 @@ If optional argument `SILENT' is nil, show effect of score entry."
       ;; 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 (if (numberp date) 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.
@@ -566,8 +690,18 @@ If optional argument `SILENT' is nil, show effect of score entry."
                                      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))
-       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.
@@ -668,7 +802,7 @@ SCORE is the score to add."
   "Automatically expunge articles with score below SCORE."
   (interactive 
    (list (or (and current-prefix-arg (prefix-numeric-value current-prefix-arg))
-            (string-to-int (read-string "Expunge below: ")))))
+            (string-to-int (read-string "Set expunge below: ")))))
   (setq score (or score gnus-summary-default-score 0))
   (gnus-score-set 'expunge (list score))
   (gnus-score-set 'touched '(t)))
@@ -679,11 +813,11 @@ SCORE is the score to add."
   (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))
+       (message-narrow-to-headers)
        (let ((id (mail-fetch-field "message-id")))
          (when id
+           (set-buffer gnus-summary-buffer)
            (gnus-summary-score-entry
             "references" (concat id "[ \t]*$") 'r
             score (current-time-string) nil t)))))))
@@ -694,11 +828,11 @@ SCORE is the score to add."
   (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
+           (set-buffer gnus-summary-buffer)
            (gnus-summary-score-entry
             "references" id 's
             score (current-time-string))))))))
@@ -708,9 +842,7 @@ SCORE is the score to add."
   (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.
@@ -723,54 +855,89 @@ 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))
 
+(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))
+    (make-directory (file-name-directory file) t)
     (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))
+  (make-directory (file-name-directory file) t)
   (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 
    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-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
                                       gnus-kill-files-directory)) 
                          (expand-file-name file))
                         file)
-                   (concat gnus-kill-files-directory file))))
+                   (concat (file-name-as-directory gnus-kill-files-directory)
+                           file))))
         (cached (assoc file gnus-score-cache))
         (global (member file gnus-internal-global-score-files))
         lists alist)
@@ -789,10 +956,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)))
@@ -803,9 +978,15 @@ 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))
+         (decay (car (gnus-score-get 'decay alist)))
          (eval (car (gnus-score-get 'eval alist))))
+      ;; Perform possible decays.
+      (when (and gnus-decay-scores
+                (gnus-decay-scores alist decay))
+       (gnus-score-set 'touched '(t) alist)
+       (gnus-score-set 'decay (list (gnus-time-to-day (current-time)))))
       ;; We do not respect eval and files atoms from global score
       ;; files. 
       (and files (not global)
@@ -875,9 +1056,12 @@ SCORE is the score to add."
        (delq (assoc file gnus-score-cache) gnus-score-cache)))
 
 (defun gnus-score-load-score-alist (file)
+  "Read score FILE."
   (let (alist)
     (if (not (file-readable-p file))
+       ;; Couldn't read file.
        (setq gnus-score-alist nil)
+      ;; Read file.
       (save-excursion
        (gnus-set-work-buffer)
        (insert-file-contents file)
@@ -888,11 +1072,7 @@ SCORE is the score to add."
                (condition-case ()
                    (read (current-buffer))
                  (error 
-                  (progn
-                    (gnus-message 3 "Problem with score file %s" file)
-                    (ding) 
-                    (sit-for 2)
-                    nil))))))
+                  (gnus-error 3.2 "Problem with score file %s" file))))))
       (if (eq (car alist) 'setq)
          ;; This is an old-style score file.
          (setq gnus-score-alist (gnus-score-transform-old-to-new alist))
@@ -976,56 +1156,55 @@ SCORE is the score to add."
   
 (defun gnus-score-save ()
   ;; Save all score information.
-  (let ((cache gnus-score-cache))
+  (let ((cache gnus-score-cache)
+       entry score file)
     (save-excursion
       (setq gnus-score-alist nil)
-      (set-buffer (get-buffer-create "*Score*"))
-      (buffer-disable-undo (current-buffer))
-      (let (entry score file)
-       (while cache
-         (setq entry (car cache)
-               cache (cdr cache)
-               file (car entry)
-               score (cdr entry))
-         (if (or (not (equal (gnus-score-get 'touched score) '(t)))
-                 (gnus-score-get 'read-only score)
-                 (and (file-exists-p file)
-                      (not (file-writable-p file))))
-             ()
-           (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)
-                 ;; 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))
-               ;; This is a normal score file, so we print it very
-               ;; prettily. 
-               (pp score (current-buffer))))
-           (if (not (gnus-make-directory (file-name-directory file)))
-               ()
-             ;; If the score file is empty, we delete it.
-             (if (zerop (buffer-size))
-                 (delete-file file)
-               ;; There are scores, so we write the file. 
-               (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)))))
+      (nnheader-set-temp-buffer "*Score*")
+      (while cache
+       (current-buffer)
+       (setq entry (pop cache)
+             file (car entry)
+             score (cdr entry))
+       (if (or (not (equal (gnus-score-get 'touched score) '(t)))
+               (gnus-score-get 'read-only score)
+               (and (file-exists-p file)
+                    (not (file-writable-p file))))
+           ()
+         (setq score (setcdr entry (delq (assq 'touched score) score)))
+         (erase-buffer)
+         (let (emacs-lisp-mode-hook)
+           (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.
+               (gnus-prin1 score)
+             ;; This is a normal score file, so we print it very
+             ;; prettily. 
+             (pp score (current-buffer))))
+         (if (and (not (file-exists-p (file-name-directory file)))
+                  (make-directory (file-name-directory file) t))
+             (gnus-error 1 "Can't create directory %s"
+                         (file-name-directory file))
+           ;; If the score file is empty, we delete it.
+           (if (zerop (buffer-size))
+               (delete-file file)
+             ;; There are scores, so we write the file. 
+             (when (file-writable-p file)
+               (write-region (point-min) (point-max) file nil 'silent)
+               (when 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))))
       (kill-buffer (current-buffer)))))
-  
-(defun gnus-score-headers (score-files &optional trace)
-  ;; Score `gnus-newsgroup-headers'.
-  (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)
-    (setq gnus-scores-exclude-files nil)
-    ;; Load the score files.
+
+(defun gnus-score-load-files (score-files)
+  "Load all score files in SCORE-FILES."
+  ;; Load the score files.
+  (let (scores)
     (while score-files
       (if (stringp (car score-files))
          ;; It is a string, which means that it's a score file name,
@@ -1044,6 +1223,16 @@ SCORE is the score to add."
               (member (car c) gnus-scores-exclude-files)
               (setq scores (delq (car s) scores)))
          (setq s (cdr s)))))
+    scores))
+
+(defun gnus-score-headers (score-files &optional trace)
+  ;; Score `gnus-newsgroup-headers'.
+  (let (scores news)
+    ;; PLM: probably this is not the best place to clear orphan-score
+    (setq gnus-orphan-score nil
+         gnus-scores-articles nil
+         gnus-scores-exclude-files nil
+         scores (gnus-score-load-files score-files))
     (setq news scores)
     ;; Do the scoring.
     (while news
@@ -1082,10 +1271,9 @@ SCORE is the score to add."
              (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)))
+             (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)))
@@ -1099,14 +1287,21 @@ SCORE is the score to add."
 
          ;; 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)))
+           (when (or (/= gnus-summary-default-score
+                         (cdar gnus-scores-articles))
+                     gnus-save-score)
+             (push (cons (mail-header-number (caar gnus-scores-articles))
+                         (cdar gnus-scores-articles))
+                   gnus-newsgroup-scored))
            (setq gnus-scores-articles (cdr gnus-scores-articles)))
 
+         (let (score)
+           (while (setq score (pop scores))
+             (while score
+               (when (listp (caar score))
+                 (gnus-score-advanced (car score) trace))
+               (pop score))))
+               
          (gnus-message 5 "Scoring...done"))))))
 
 
@@ -1208,18 +1403,14 @@ SCORE is the score to add."
          ;; matches on numbers that any cleverness will take more
          ;; time than one would gain.
          (while articles
-           (and (funcall match-func 
+           (when (funcall match-func 
                          (or (aref (caar articles) gnus-score-index) 0)
                          match)
-                (progn
-                  (and trace (setq gnus-score-trace 
-                                   (cons
-                                    (cons
-                                     (car-safe (rassq alist gnus-score-cache))
-                                     kill)
-                                    gnus-score-trace)))
-                  (setq found t)
-                  (setcdr (car articles) (+ score (cdar articles)))))
+             (when trace 
+               (push (cons (car-safe (rassq alist gnus-score-cache)) kill)
+                     gnus-score-trace))
+             (setq found t)
+             (setcdr (car articles) (+ score (cdar articles))))
            (setq articles (cdr articles)))
          ;; Update expire date
          (cond ((null date))           ;Permanent entry.
@@ -1235,7 +1426,7 @@ SCORE is the score to add."
 
 (defun gnus-score-date (scores header now expire &optional trace)
   (let ((gnus-score-index (nth 1 (assoc header gnus-header-index)))
-       entries alist)
+       entries alist match match-func article)
 
     ;; Find matches.
     (while scores
@@ -1245,37 +1436,40 @@ SCORE is the score to add."
       (while (cdr entries)             ;First entry is the header index.
        (let* ((rest (cdr entries))             
               (kill (car rest))
-              (match (timezone-make-date-sortable (nth 0 kill)))
               (type (or (nth 3 kill) 'before))
               (score (or (nth 1 kill) gnus-score-interactive-default-score))
               (date (nth 2 kill))
               (found nil)
-              (match-func 
-               (cond ((eq type 'after) 'string<)
-                     ((eq type 'before) 'gnus-string>)
-                     ((eq type 'at) 'string=)
-                     (t (error "Illegal match type: %s" type))))
               (articles gnus-scores-articles)
               l)
+         (cond
+          ((eq type 'after)
+           (setq match-func 'string<
+                 match (gnus-date-iso8601 (nth 0 kill))))
+          ((eq type 'before)
+           (setq match-func 'gnus-string>
+                 match (gnus-date-iso8601 (nth 0 kill))))
+          ((eq type 'at)
+           (setq match-func 'string=
+                 match (gnus-date-iso8601 (nth 0 kill))))
+          ((eq type 'regexp)
+           (setq match-func 'string-match
+                 match (nth 0 kill)))
+          (t (error "Illegal match type: %s" type)))
          ;; Instead of doing all the clever stuff that
          ;; `gnus-score-string' does to minimize searches and stuff,
          ;; I will assume that people generally will put so few
          ;; matches on numbers that any cleverness will take more
          ;; time than one would gain.
-         (while articles
-           (and
-            (setq l (aref (caar articles) gnus-score-index))
-            (funcall match-func match (timezone-make-date-sortable l))
-            (progn
-              (and trace (setq gnus-score-trace 
-                               (cons
-                                (cons
-                                 (car-safe (rassq alist gnus-score-cache))
-                                 kill)
-                                gnus-score-trace)))
-              (setq found t)
-              (setcdr (car articles) (+ score (cdar articles)))))
-           (setq articles (cdr articles)))
+         (while (setq article (pop articles))
+           (when (and
+                  (setq l (aref (car article) gnus-score-index))
+                  (funcall match-func match (gnus-date-iso8601 l)))
+             (when trace
+               (push (cons (car-safe (rassq alist gnus-score-cache)) kill)
+                     gnus-score-trace))
+             (setq found t)
+             (setcdr article (+ score (cdr article)))))
          ;; Update expire date
          (cond ((null date))           ;Permanent entry.
                ((and found gnus-update-score-entry-dates) ;Match, update date.
@@ -1300,96 +1494,92 @@ SCORE is the score to add."
       (let* ((buffer-read-only nil)
             (articles 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 last)
-       (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)
-         (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)
+       (when articles
+         (setq last (mail-header-number (caar (last articles))))
+         ;; Not all backends support partial fetching.  In that case,
+         ;; we just fetch the entire article.
+         (unless (gnus-check-backend-function 
+                  (and (string-match "^gnus-" (symbol-name request-func))
+                       (intern (substring (symbol-name request-func)
+                                          (match-end 0))))
+                  gnus-newsgroup-name)
+           (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 (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))))))
+                    (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)
@@ -1468,7 +1658,8 @@ SCORE is the score to add."
                         (setq art (car arts)
                               arts (cdr arts))
                         (gnus-score-add-followups 
-                         (car art) score all-scores thread)))))
+                         (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)))
@@ -1520,7 +1711,8 @@ SCORE is the score to add."
   ;; Insert the unique article headers in the buffer.
   (let ((gnus-score-index (nth 1 (assoc header gnus-header-index)))
        ;; gnus-score-index is used as a free variable.
-       alike last this art entries alist articles scores fuzzy)
+       alike last this art entries alist articles scores 
+       fuzzies arts words kill)
 
     ;; Sorting the articles costs os O(N*log N) but will allow us to
     ;; only match with each unique header.  Thus the actual matching
@@ -1532,172 +1724,224 @@ SCORE is the score to add."
          articles gnus-scores-articles)
 
     (erase-buffer)
-    (while articles
-      (setq art (car articles)
-           this (aref (car art) gnus-score-index)
-           articles (cdr articles))
+    (while (setq art (pop articles))
+      (setq this (aref (car art) gnus-score-index))
       (if (equal last this)
          ;; O(N*H) cons-cells used here, where H is the number of
          ;; headers.
          (setq alike (cons art alike))
-       (if last
-           (progn
-             ;; Insert the line, with a text property on the
-             ;; terminating newline referring to the articles with
-             ;; this line.
-             (insert last ?\n)
-             (put-text-property (1- (point)) (point) 'articles alike)))
+       (when last
+         ;; Insert the line, with a text property on the
+         ;; terminating newline referring to the articles with
+         ;; this line.
+         (insert last ?\n)
+         (put-text-property (1- (point)) (point) 'articles alike))
        (setq alike (list art)
              last this)))
-    (and last                          ; Bwadr, duplicate code.
-        (progn
-          (insert last ?\n)                    
-          (put-text-property (1- (point)) (point) 'articles alike)))
-
-    ;; Find ordinary matches.
-    (setq scores score-list) 
-    (while scores
-      (setq alist (car scores)
-           scores (cdr scores)
+    (when last                         ; Bwadr, duplicate code.
+      (insert last ?\n)                        
+      (put-text-property (1- (point)) (point) 'articles alike))
+
+    ;; Go through all the score alists and pick out the entries
+    ;; for this header.
+    (while score-list
+      (setq alist (pop score-list)
+           ;; There's only one instance of this header for
+           ;; each score alist.
            entries (assoc header alist))
       (while (cdr entries)             ;First entry is the header index.
-       (let* ((rest (cdr entries))             
-              (kill (car rest))
+       (let* ((kill (cadr entries))
               (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)
               (mt (aref (symbol-name type) 0))
-              (case-fold-search 
-               (not (or (= mt ?R) (= mt ?S) (= mt ?E) (= mt ?F))))
+              (case-fold-search (not (memq mt '(?R ?S ?E ?F))))
               (dmt (downcase mt))
               (search-func 
                (cond ((= dmt ?r) 're-search-forward)
                      ((or (= dmt ?e) (= dmt ?s) (= dmt ?f)) 'search-forward)
-                     (t (error "Illegal match type: %s" type))))
-              arts art)
-         (if (= dmt ?f)
-             (setq fuzzy t)
-           ;; Do non-fuzzy matching.
+                     ((= dmt ?w) nil)
+                     (t (error "Illegal match type: %s" type)))))
+         (cond
+          ;; Fuzzy matches.  We save these for later.
+          ((= dmt ?f)
+           (push entries fuzzies))
+          ;; Word matches.  Save these for even later.
+          ((= dmt ?w)
+           (push entries words))
+          ;; Exact matches.
+          ((= dmt ?e)
+           ;; Do exact matching.
            (goto-char (point-min))
-           (if (= dmt ?e)
-               ;; Do exact matching.
-               (while (and (not (eobp)) 
-                           (funcall search-func match nil t))
-                 (and (= (progn (beginning-of-line) (point))
-                         (match-beginning 0))
-                      (= (progn (end-of-line) (point))
-                         (match-end 0))
-                      (progn
-                        (setq found (setq arts (get-text-property 
-                                                (point) 'articles)))
-                        ;; Found a match, update scores.
-                        (if trace
-                            (while arts
-                              (setq art (car arts)
-                                    arts (cdr arts))
-                              (setcdr art (+ score (cdr art)))
-                              (setq gnus-score-trace
-                                    (cons
-                                     (cons
-                                      (car-safe
-                                       (rassq alist gnus-score-cache))
-                                      kill)
-                                     gnus-score-trace)))
-                          (while arts
-                            (setq art (car arts)
-                                  arts (cdr arts))
-                            (setcdr art (+ score (cdr art)))))))
-                 (forward-line 1))
-             ;; Do regexp and substring matching.
-             (and (string= match "") (setq match "\n"))
-             (while (and (not (eobp))
-                         (funcall search-func match nil t))
-               (goto-char (match-beginning 0))
-               (end-of-line)
-               (setq found (setq arts (get-text-property (point) 'articles)))
-               ;; Found a match, update scores.
-               (if trace
-                   (while arts
-                     (setq art (pop arts))
-                     (setcdr art (+ score (cdr art)))
-                     (push (cons
-                             (car-safe (rassq alist gnus-score-cache))
-                             kill)
-                           gnus-score-trace))
-                 (while arts
-                   (setq art (pop arts))
-                   (setcdr art (+ score (cdr art)))))
-               (forward-line 1)))
-           ;; Update expire date
+           (while (and (not (eobp)) 
+                       (funcall search-func match nil t))
+             ;; Is it really exact?
+             (and (eolp)
+                  (= (gnus-point-at-bol) (match-beginning 0))
+                  ;; Yup.
+                  (progn
+                    (setq found (setq arts (get-text-property 
+                                            (point) 'articles)))
+                    ;; Found a match, update scores.
+                    (if trace
+                        (while (setq art (pop arts))
+                          (setcdr art (+ score (cdr art)))
+                          (setq gnus-score-trace
+                                (cons
+                                 (cons
+                                  (car-safe
+                                   (rassq alist gnus-score-cache))
+                                  kill)
+                                 gnus-score-trace)))
+                      (while (setq art (pop arts))
+                        (setcdr art (+ score (cdr art)))))))
+             (forward-line 1)))
+          ;; Regexp and substring matching.
+          (t
+           (goto-char (point-min))
+           (when (string= match "")
+             (setq match "\n"))
+           (while (and (not (eobp))
+                       (funcall search-func match nil t))
+             (goto-char (match-beginning 0))
+             (end-of-line)
+             (setq found (setq arts (get-text-property (point) 'articles)))
+             ;; Found a match, update scores.
+             (if trace
+                 (while (setq art (pop arts))
+                   (setcdr art (+ score (cdr art)))
+                   (push (cons (car-safe (rassq alist gnus-score-cache)) kill)
+                         gnus-score-trace))
+               (while (setq art (pop arts))
+                 (setcdr art (+ score (cdr art)))))
+             (forward-line 1))))
+         ;; Update expiry date
+         (if trace
+             (setq entries (cdr entries))
            (cond 
-            ((null date))              ;Permanent entry.
-            ((and found gnus-update-score-entry-dates) ;Match, update date.
+            ;; Permanent entry.
+            ((null date)
+             (setq entries (cdr entries)))
+            ;; We have a match, so we update the date.
+            ((and found gnus-update-score-entry-dates)
              (gnus-score-set 'touched '(t) alist)
-             (setcar (nthcdr 2 kill) now))
-            ((and expire (< date expire)) ;Old entry, remove.
+             (setcar (nthcdr 2 kill) now)
+             (setq entries (cdr entries)))
+            ;; This entry has expired, so we remove it.
+            ((and expire (< date expire))
              (gnus-score-set 'touched '(t) alist)
-             (setcdr entries (cdr rest))
-             (setq rest entries))))
-         (setq entries rest))))
+             (setcdr entries (cddr entries)))
+            ;; No match; go to next entry.
+            (t
+             (setq entries (cdr entries))))))))
 
     ;; Find fuzzy matches.
-    (when fuzzy
-      (setq scores score-list)
+    (when fuzzies
+      ;; Simplify the entire buffer for easy matching.
       (gnus-simplify-buffer-fuzzy)
-      (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))
+      (while (setq kill (cadar fuzzies))
+       (let* ((match (nth 0 kill))
+              (type (nth 3 kill))
+              (score (or (nth 1 kill) gnus-score-interactive-default-score))
+              (date (nth 2 kill))
+              (mt (aref (symbol-name type) 0))
+              (case-fold-search (not (= mt ?F)))
+              found)
+         (goto-char (point-min))
+         (while (and (not (eobp)) 
+                     (search-forward match nil t))
+           (when (and (= (gnus-point-at-bol) (match-beginning 0))
+                      (eolp))
+             (setq found (setq arts (get-text-property (point) 'articles)))
+             (if trace
+                 (while (setq art (pop arts))
+                   (setcdr art (+ score (cdr art)))
+                   (push (cons
+                          (car-safe (rassq alist gnus-score-cache)) kill)
+                         gnus-score-trace))
+               ;; Found a match, update scores.
+               (while (setq art (pop arts))
+                 (setcdr art (+ score (cdr art))))))
+           (forward-line 1))
+         ;; Update expiry date
+         (cond
+          ;; Permanent.
+          ((null date)
+           )
+          ;; Match, update date.
+          ((and found gnus-update-score-entry-dates)
+           (gnus-score-set 'touched '(t) alist)
+           (setcar (nthcdr 2 kill) now))
+          ;; Old entry, remove.
+          ((and expire (< date expire)) 
+           (gnus-score-set 'touched '(t) alist)
+           (setcdr (car fuzzies) (cddar fuzzies))))
+         (setq fuzzies (cdr fuzzies)))))
+
+    (when words
+      ;; Enter all words into the hashtb.
+      (let ((hashtb (gnus-make-hashtable
+                    (* 10 (count-lines (point-min) (point-max))))))
+       (gnus-enter-score-words-into-hashtb hashtb)
+       (while (setq kill (cadar words))
+         (let* ((score (or (nth 1 kill) gnus-score-interactive-default-score))
                 (date (nth 2 kill))
-                (found nil)
-                (mt (aref (symbol-name type) 0))
-                (case-fold-search (not (= mt ?F)))
-                (dmt (downcase mt))
-                arts art)
-           (when (= dmt ?f)
-             (goto-char (point-min))
-             (while (and (not (eobp)) 
-                         (search-forward match nil t))
-               (when (and (= (progn (beginning-of-line) (point))
-                             (match-beginning 0))
-                          (= (progn (end-of-line) (point))
-                             (match-end 0)))
-                 (setq found (setq arts (get-text-property 
-                                         (point) 'articles)))
-                 ;; Found a match, update scores.
-                 (if trace
-                     (while arts
-                       (setq art (pop arts))
-                       (setcdr art (+ score (cdr art)))
-                       (push (cons
-                              (car-safe (rassq alist gnus-score-cache))
-                              kill)
-                             gnus-score-trace))
-                   (while arts
-                     (setq art (pop arts))
-                     (setcdr art (+ score (cdr art))))))
-               (forward-line 1))
-             ;; Update expire date
-             (unless trace
-               (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)
+                found)
+           (when (setq arts (intern-soft (nth 0 kill) hashtb))
+             (setq arts (symbol-value arts))
+             (setq found t)
+             (if trace
+                 (while (setq art (pop arts))
+                   (setcdr art (+ score (cdr art)))
+                   (push (cons
+                          (car-safe (rassq alist gnus-score-cache)) kill)
+                         gnus-score-trace))
+               ;; Found a match, update scores.
+               (while (setq art (pop arts))
+                 (setcdr art (+ score (cdr art))))))
+           ;; Update expiry date
+           (cond
+            ;; Permanent.
+            ((null date)
+             )
+            ;; Match, update date.
+            ((and found gnus-update-score-entry-dates)
+             (gnus-score-set 'touched '(t) alist)
+             (setcar (nthcdr 2 kill) now))
+            ;; Old entry, remove.
+            ((and expire (< date expire)) 
+             (gnus-score-set 'touched '(t) alist)
+             (setcdr (car words) (cddar words))))
+           (setq words (cdr words))))))
+    nil))
+
+(defun gnus-enter-score-words-into-hashtb (hashtb)
+  ;; Find all the words in the buffer and enter them into
+  ;; the hashtable.
+  (let ((syntab (syntax-table))
+        word val)
+    (goto-char (point-min))
+    (unwind-protect
+       (progn
+         (set-syntax-table gnus-adaptive-word-syntax-table)
+         (while (re-search-forward "\\b\\w+\\b" nil t)
+           (setq val
+                 (gnus-gethash 
+                  (setq word (downcase (buffer-substring
+                                        (match-beginning 0) (match-end 0))))
+                  hashtb))
+           (gnus-sethash
+            word
+            (append (get-text-property (gnus-point-at-eol) 'articles) val)
+            hashtb)))
+      (set-syntax-table syntab))
+    ;; Make all the ignorable words ignored.
+    (let ((ignored (append gnus-ignored-adaptive-words
+                          gnus-default-ignored-adaptive-words)))
+      (while ignored
+       (gnus-sethash (pop ignored) nil hashtb)))))
 
 (defun gnus-score-string< (a1 a2)
   ;; Compare headers in articles A2 and A2.
@@ -1705,27 +1949,6 @@ SCORE is the score to add."
   (string-lessp (aref (car a1) gnus-score-index)
                (aref (car a2) gnus-score-index)))
 
-(defun gnus-score-build-cons (article)
-  ;; 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)
-    ("thread" 5 gnus-score-thread)))
-
 (defun gnus-current-score-file-nondirectory (&optional score-file)
   (let ((score-file (or score-file gnus-current-score-file)))
     (if score-file 
@@ -1733,137 +1956,125 @@ SCORE is the score to add."
       "none")))
 
 (defun gnus-score-adaptive ()
-  (save-excursion
-    (let* ((malist (gnus-copy-sequence gnus-adaptive-score-alist))
-          (alist malist)
-          (date (current-time-string)) 
-          (data gnus-newsgroup-data)
-          elem headers match)
-      ;; First we transform the adaptive rule alist into something
-      ;; that's faster to process.
-      (while malist
-       (setq elem (car malist))
-       (if (symbolp (car elem))
-           (setcar elem (symbol-value (car elem))))
-       (setq elem (cdr elem))
-       (while elem
-         (setcdr (car elem) 
-                 (cons (if (eq (caar elem) 'followup)
-                           "references"
-                         (symbol-name (caar elem)))
-                       (cdar elem)))
-         (setcar (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.
+  "Create adaptive score rules for this newsgroup."
+  (when gnus-use-adaptive-scoring
+    ;; We change the score file to the adaptive score file.
+    (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)))
-       (if (or (not elem)
-               (gnus-data-pseudo-p (car data)))
-           ()
-         (when (setq headers (gnus-data-header (car data)))
-           (while elem 
-             (setq match (funcall (caar elem) headers))
-             (gnus-summary-score-entry 
-              (nth 1 (car elem)) match
-              (cond
-               ((numberp match)
-                '=)
-               ((equal (nth 1 (car elem)) "date")
-                'a)
-               (t
-                ;; Whether we use substring or exact matches are controlled
-                ;; here.  
-                (if (or (not gnus-score-exact-adapt-limit)
-                        (< (length match) gnus-score-exact-adapt-limit))
-                    'e 
-                  (if (equal (nth 1 (car elem)) "subject")
-                      'f 's))))
-              (nth 2 (car elem)) date nil t)
-             (setq elem (cdr elem)))))
-       (setq data (cdr data))))))
-
-;;;
-;;; Score mode.
-;;;
-
-(defvar gnus-score-mode-hook nil
-  "*Hook run in score mode buffers.")
-
-(defvar gnus-score-menu-hook nil
-  "*Hook run after creating the score mode menu.")
-
-(defvar gnus-score-mode-map nil)
-(unless gnus-score-mode-map
-  (setq gnus-score-mode-map (copy-keymap emacs-lisp-mode-map))
-  (gnus-define-keys 
-   gnus-score-mode-map
-   "\C-c\C-c" gnus-score-edit-done
-   "\C-c\C-d" gnus-score-edit-insert-date
-   "\C-c\C-p" gnus-score-pretty-print))
-
-(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)
-  (when (and menu-bar-mode
-            (gnus-visual-p 'score-menu 'menu))
-    (gnus-score-make-menu-bar))
-  (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-make-menu-bar ()
-  (unless (boundp 'gnus-score-menu)
-    (easy-menu-define
-     gnus-score-menu gnus-score-mode-map ""
-     '("Score"
-       ["Exit" gnus-score-edit-done t]
-       ["Insert date" gnus-score-edit-insert-date t]
-       ["Format" gnus-score-pretty-print t]
-       ))
-    (run-hooks 'gnus-score-menu-hook)))
-
-(defun gnus-score-edit-insert-date ()
-  "Insert date in numerical format."
-  (interactive)
-  (princ (gnus-day-number (current-time-string)) (current-buffer)))
-
-(defun gnus-score-pretty-print ()
-  "Format the current score file."
-  (interactive)
-  (goto-char (point-min))
-  (let ((form (read (current-buffer))))
-    (erase-buffer)
-    (pp form (current-buffer)))
-  (goto-char (point-min)))
+           gnus-newsgroup-name gnus-adaptive-file-suffix))))
+    (cond
+     ;; Perform ordinary line scoring.
+     ((or (not (listp gnus-use-adaptive-scoring))
+         (memq 'line gnus-use-adaptive-scoring))
+      (save-excursion
+       (let* ((malist (gnus-copy-sequence gnus-adaptive-score-alist))
+              (alist malist)
+              (date (current-time-string)) 
+              (data gnus-newsgroup-data)
+              elem headers match)
+         ;; First we transform the adaptive rule alist into something
+         ;; that's faster to process.
+         (while malist
+           (setq elem (car malist))
+           (if (symbolp (car elem))
+               (setcar elem (symbol-value (car elem))))
+           (setq elem (cdr elem))
+           (while elem
+             (setcdr (car elem) 
+                     (cons (if (eq (caar elem) 'followup)
+                               "references"
+                             (symbol-name (caar elem)))
+                           (cdar elem)))
+             (setcar (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)))
+         ;; Then we score away.
+         (while data
+           (setq elem (cdr (assq (gnus-data-mark (car data)) alist)))
+           (if (or (not elem)
+                   (gnus-data-pseudo-p (car data)))
+               ()
+             (when (setq headers (gnus-data-header (car data)))
+               (while elem 
+                 (setq match (funcall (caar elem) headers))
+                 (gnus-summary-score-entry 
+                  (nth 1 (car elem)) match
+                  (cond
+                   ((numberp match)
+                    '=)
+                   ((equal (nth 1 (car elem)) "date")
+                    'a)
+                   (t
+                    ;; Whether we use substring or exact matches is
+                    ;; controlled here.  
+                    (if (or (not gnus-score-exact-adapt-limit)
+                            (< (length match) gnus-score-exact-adapt-limit))
+                        'e 
+                      (if (equal (nth 1 (car elem)) "subject")
+                          'f 's))))
+                  (nth 2 (car elem)) date nil t)
+                 (setq elem (cdr elem)))))
+           (setq data (cdr data))))))
+
+     ;; Perform adaptive word scoring.
+     ((memq 'word gnus-use-adaptive-scoring)
+      (nnheader-temp-write nil
+       (let* ((hashtb (gnus-make-hashtable 1000))
+              (date (gnus-day-number (current-time-string)))
+              (data gnus-newsgroup-data)
+              (syntab (syntax-table))
+              word d score val)
+         (unwind-protect
+             (progn
+               (set-syntax-table syntab)
+               ;; Go through all articles.
+               (while (setq d (pop data))
+                 (when (setq score
+                             (cdr (assq 
+                                   (gnus-data-mark d)
+                                   gnus-default-adaptive-word-score-alist)))
+                   ;; This article has a mark that should lead to
+                   ;; adaptive word rules, so we insert the subject
+                   ;; and find all words in that string.
+                   (insert (mail-header-subject (gnus-data-header d)))
+                   (downcase-region (point-min) (point-max))
+                   (goto-char (point-min))
+                   (while (re-search-forward "\\b\\w+\\b" nil t)
+                     ;; Put the word and score into the hashtb.
+                     (setq val (gnus-gethash (setq word (match-string 0))
+                                             hashtb))
+                     (gnus-sethash word (+ (or val 0) score) hashtb))
+                   (erase-buffer))))
+           (set-syntax-table syntab))
+         ;; Make all the ignorable words ignored.
+         (let ((ignored (append gnus-ignored-adaptive-words
+                                gnus-default-ignored-adaptive-words)))
+           (while ignored
+             (gnus-sethash (pop ignored) nil hashtb)))
+         ;; Now we have all the words and scores, so we
+         ;; add these rules to the ADAPT file.
+         (set-buffer gnus-summary-buffer)
+         (mapatoms
+          (lambda (word)
+            (when (symbol-value word)
+              (gnus-summary-score-entry
+               "subject" (symbol-name word) 'w (symbol-value word)
+               date nil t)))
+          hashtb)))))))
 
 (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)))
@@ -1874,26 +2085,61 @@ This mode is an extended emacs-lisp mode.
   (let ((gnus-newsgroup-headers
         (list (gnus-summary-article-header)))
        (gnus-newsgroup-scored nil)
-       (buf (current-buffer))
        trace)
+    (save-excursion
+      (nnheader-set-temp-buffer "*Score Trace*"))
     (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*")
+    (if (not (setq trace gnus-score-trace))
+       (gnus-error 1 "No score rules apply to the current article.")
+      (set-buffer "*Score Trace*")
+      (gnus-add-current-to-buffer-list)
+      (while trace
+       (insert (format "%S  ->  %s\n" (cdar trace)
+                       (file-name-nondirectory (caar trace))))
+       (setq trace (cdr trace)))
+      (goto-char (point-min))
+      (gnus-configure-windows 'score-trace))))
+
+(defun gnus-score-find-favourite-words ()
+  "List words used in scoring."
+  (interactive)
+  (let ((alists (gnus-score-load-files (gnus-all-score-files)))
+       alist rule rules)
+    ;; Go through all the score alists for this group
+    ;; and find all `w' rules.
+    (while (setq alist (pop alists))
+      (when (and (stringp (setq rule (pop alist)))
+                (equal "subject" (downcase (pop rule))))
+       (while rule
+         (when (memq (nth 3 (car rule)) '(w W word Word))
+           (push (cons (or (nth 1 rule) gnus-score-interactive-default-score)
+                       (car rule))
+                 rules))
+         (pop rule))))
+    (setq rules (sort rules (lambda (r1 r2)
+                             (string-lessp (cdr r1) (cdr r2)))))
+    ;; Add up words that have appeared several times.
+    (let ((r rules))
+      (while (cdr r)
+       (if (equal (cdar r) (cdadr r))
+           (progn
+             (setcar (car r) (+ (caar r) (caadr r)))
+             (setcdr r (cddr r)))
+         (pop r))))
+    ;; Insert the words.
+    (nnheader-set-temp-buffer "*Score Words*")
+    (setq rules (sort rules (lambda (r1 r2) (> (car r1) (car r2)))))
+    (while rules
+      (insert (format "%-5d: %s\n" (caar rules) (cdar rules)))
+      (pop rules))
     (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)))
+    (gnus-configure-windows 'score-words)))
 
 (defun gnus-summary-rescore ()
   "Redo the entire scoring process in the current summary."
   (interactive)
-  (setq gnus-newsgroup-scored nil)
+  (gnus-score-save)
   (setq gnus-score-cache nil)
   (setq gnus-newsgroup-scored nil)
   (gnus-possibly-score-headers)
@@ -1902,12 +2148,16 @@ This mode is an extended emacs-lisp mode.
 (defun gnus-score-flush-cache ()
   "Flush the cache of score files."
   (interactive)
+  (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
@@ -1983,8 +2233,7 @@ 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)
@@ -2017,11 +2266,12 @@ This mode is an extended emacs-lisp mode.
   "Return all possible score files under DIR."
   (let ((files (directory-files (expand-file-name dir) t nil t))
        (regexp (gnus-score-file-regexp))
+       (case-fold-search nil)
        out file)
     (while (setq file (pop files))
       (cond 
        ;; Ignore "." and "..".
-       ((string-match "/\\.\\.?\\'" file)
+       ((member (file-name-nondirectory file) '("." ".."))
        nil)
        ;; Recurse down directories.
        ((file-directory-p file)
@@ -2035,8 +2285,8 @@ This mode is an extended emacs-lisp mode.
        
 (defun gnus-score-file-regexp ()
   "Return a regexp that match all score files."
-  (concat "\\(" gnus-score-file-suffix 
-         "\\|" gnus-adaptive-file-suffix "\\)\\'"))
+  (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.
@@ -2083,7 +2333,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))
@@ -2133,8 +2388,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."
@@ -2167,8 +2420,10 @@ The list is determined from the variable gnus-score-file-alist."
            (cons (cons group score-files) gnus-score-file-alist-cache))
       score-files)))
 
-(defun gnus-possibly-score-headers (&optional trace)
+(defun gnus-all-score-files ()
+  "Return a list of all score files for the current group."
   (let ((funcs gnus-score-find-score-files-function)
+       (group gnus-newsgroup-name)
        score-files)
     ;; Make sure funcs is a list.
     (and funcs
@@ -2176,20 +2431,38 @@ The list is determined from the variable gnus-score-file-alist."
         (setq funcs (list funcs)))
     ;; Get the initial score files for this group.
     (when funcs 
-      (setq score-files (gnus-score-find-alist gnus-newsgroup-name)))
+      (setq score-files (gnus-score-find-alist group)))
+    ;; Add any home adapt files.
+    (let ((home (gnus-home-score-file group t)))
+      (when home
+       (push home score-files)
+       (setq gnus-newsgroup-adaptive-score-file home)))
+    ;; Check whether there is a `adapt-file' group parameter.
+    (let ((param-file (gnus-group-find-parameter group 'adapt-file)))
+      (when param-file
+       (push param-file score-files)
+       (setq gnus-newsgroup-adaptive-score-file param-file)))
     ;; Go through all the functions for finding score files (or actual
     ;; scores) and add them to a list.
     (while funcs
       (when (gnus-functionp (car funcs))
        (setq score-files 
-             (nconc score-files (funcall (car funcs) gnus-newsgroup-name))))
+             (nconc score-files (funcall (car funcs) group))))
       (setq funcs (cdr funcs)))
+    ;; Add any home score files.
+    (let ((home (gnus-home-score-file group)))
+      (when home
+       (push home score-files)))
     ;; Check whether there is a `score-file' group parameter.
-    (let ((param-file (gnus-group-get-parameter 
-                      gnus-newsgroup-name 'score-file)))
+    (let ((param-file (gnus-group-find-parameter group 'score-file)))
       (when param-file
        (push param-file score-files)))
     ;; Do the scoring if there are any score files for this group.
+    score-files))
+    
+(defun gnus-possibly-score-headers (&optional trace)
+  "Do scoring if scoring is required."
+  (let ((score-files (gnus-all-score-files)))
     (when score-files
       (gnus-score-headers score-files trace))))
 
@@ -2202,17 +2475,17 @@ The list is determined from the variable gnus-score-file-alist."
           (string-equal newsgroup ""))
        ;; The global score file is placed at top of the directory.
        (expand-file-name 
-       suffix (or gnus-kill-files-directory "~/News")))
+       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)
-                        (or gnus-kill-files-directory "~/News")))
+                        gnus-kill-files-directory))
       (t
        ;; Place "SCORE" under the hierarchical directory.
        (expand-file-name (concat (gnus-newsgroup-directory-form newsgroup)
                                 "/" suffix)
-                        (or gnus-kill-files-directory "~/News")))))))
+                        gnus-kill-files-directory))))))
 
 (defun gnus-score-search-global-directories (files)
   "Scan all global score directories for score files."
@@ -2234,8 +2507,83 @@ The list is determined from the variable gnus-score-file-alist."
   (interactive)
   (setq gnus-score-default-fold (not gnus-score-default-fold))
   (if gnus-score-default-fold
-      (message "New score file entries will be case insensitive.")
-    (message "New score file entries will be case sensitive.")))
+      (gnus-message 1 "New score file entries will be case insensitive.")
+    (gnus-message 1 "New score file entries will be case sensitive.")))
+
+;;; Home score file.
+
+(defun gnus-home-score-file (group &optional adapt)
+  "Return the home score file for GROUP.
+If ADAPT, return the home adaptive file instead."
+  (let ((list (if adapt gnus-home-adapt-file gnus-home-score-file))
+       elem found)
+    ;; Make sure we have a list.
+    (unless (listp list)
+      (setq list (list list)))
+    ;; Go through the list and look for matches.
+    (while (and (not found)
+               (setq elem (pop list)))
+      (setq found
+           (cond
+            ;; Simple string.
+            ((stringp elem)
+             elem)
+            ;; Function.
+            ((gnus-functionp elem)
+             (funcall elem group))
+            ;; Regexp-file cons
+            ((consp elem)
+             (when (string-match (car elem) group)
+               (cadr elem))))))
+    (when found
+      (nnheader-concat gnus-kill-files-directory found))))
+
+(defun gnus-hierarchial-home-score-file (group)
+  "Return the score file of the top-level hierarchy of GROUP."
+  (if (string-match "^[^.]+\\." group)
+      (concat (match-string 0 group) gnus-score-file-suffix)
+    ;; Group name without any dots.
+    (concat group "." gnus-score-file-suffix)))
+      
+(defun gnus-hierarchial-home-adapt-file (group)
+  "Return the adapt file of the top-level hierarchy of GROUP."
+  (if (string-match "^[^.]+\\." group)
+      (concat (match-string 0 group) gnus-adaptive-file-suffix)
+    ;; Group name without any dots.
+    (concat group "." gnus-adaptive-file-suffix)))
+
+;;;
+;;; Score decays
+;;;
+
+(defun gnus-decay-score (score)
+  "Decay SCORE."
+  (floor
+   (- score
+      (* (if (< score 0) 1 -1)
+        (min score
+             (max gnus-score-decay-constant
+                  (* (abs score)
+                     gnus-score-decay-scale)))))))
+
+(defun gnus-decay-scores (alist day)
+  "Decay non-permanent scores in ALIST."
+  (let ((times (- (gnus-time-to-day (current-time)) day))
+       kill entry updated score n)
+    (unless (zerop times) ;Done decays today already?
+      (while (setq entry (pop alist))
+       (when (stringp (car entry))
+         (setq entry (cdr entry))
+         (while (setq kill (pop entry))
+           (when (nth 2 kill)
+             (setq updated t)
+             (setq score (or (car kill) gnus-score-interactive-default-score)
+                   n times)
+             (while (natnump (decf n))
+               (setq score (funcall gnus-decay-score-function score)))
+             (setcar kill score))))))
+    ;; Return whether this score file needs to be saved.  By Je-haysuss!
+    updated))
 
 (provide 'gnus-score)