*** empty log message ***
[gnus] / lisp / gnus-score.el
index 6d52b29..5128361 100644 (file)
@@ -1,5 +1,5 @@
-1;;; gnus-score.el --- scoring code for Gnus
-;; Copyright (C) 1995,96,97 Free Software Foundation, Inc.
+;;; gnus-score.el --- scoring code for Gnus
+;; Copyright (C) 1995,96,97,98 Free Software Foundation, Inc.
 
 ;; Author: Per Abrahamsen <amanda@iesd.auc.dk>
 ;;     Lars Magne Ingebrigtsen <larsi@ifi.uio.no>
@@ -34,7 +34,7 @@
 (require 'message)
 
 (defcustom gnus-global-score-files nil
-  "List of global score files and directories.
+  "*List of global score files and directories.
 Set this variable if you want to use people's score files.  One entry
 for each score file or each score file directory.  Gnus will decide
 by itself what score files are applicable to which group.
@@ -50,7 +50,7 @@ score files in the \"/ftp.some-where:/pub/score\" directory.
   :type '(repeat file))
 
 (defcustom gnus-score-file-single-match-alist nil
-  "Alist mapping regexps to lists of score files.
+  "*Alist mapping regexps to lists of score files.
 Each element of this alist should be of the form
        (\"REGEXP\" [ \"SCORE-FILE-1\" ] [ \"SCORE-FILE-2\" ] ... )
 
@@ -65,7 +65,7 @@ gnus-score-find-score-files-function (which see)."
   :type '(repeat (cons regexp (repeat file))))
 
 (defcustom gnus-score-file-multiple-match-alist nil
-  "Alist mapping regexps to lists of score files.
+  "*Alist mapping regexps to lists of score files.
 Each element of this alist should be of the form
        (\"REGEXP\" [ \"SCORE-FILE-1\" ] [ \"SCORE-FILE-2\" ] ... )
 
@@ -81,18 +81,18 @@ gnus-score-find-score-files-function (which see)."
   :type '(repeat (cons regexp (repeat file))))
 
 (defcustom gnus-score-file-suffix "SCORE"
-  "Suffix of the score files."
+  "*Suffix of the score files."
   :group 'gnus-score-files
   :type 'string)
 
 (defcustom gnus-adaptive-file-suffix "ADAPT"
-  "Suffix of the adaptive score files."
+  "*Suffix of the adaptive score files."
   :group 'gnus-score-files
   :group 'gnus-score-adapt
   :type 'string)
 
 (defcustom gnus-score-find-score-files-function 'gnus-score-find-bnews
-  "Function used to find score files.
+  "*Function used to find score files.
 The function will be called with the group name as the argument, and
 should return a list of score files to apply to that group.  The score
 files do not actually have to exist.
@@ -136,7 +136,8 @@ will be expired along with non-matching score entries."
 (defcustom gnus-orphan-score nil
   "*All orphans get this score added.  Set in the score file."
   :group 'gnus-score-default
-  :type 'integer)
+  :type '(choice (const nil)
+                integer))
 
 (defcustom gnus-decay-scores nil
   "*If non-nil, decay non-permanent scores."
@@ -161,7 +162,7 @@ It is called with one parameter -- the score to be decayed."
   :type 'number)
 
 (defcustom gnus-home-score-file nil
-  "Variable to control where interactive score entries are to go.
+  "*Variable to control where interactive score entries are to go.
 It can be:
 
  * A string
@@ -194,19 +195,19 @@ It can be:
   :type '(choice string
                 (repeat (choice string
                                 (cons regexp (repeat file))
-                                function))
-                function))
+                                (function :value fun)))
+                (function :value fun)))
 
 (defcustom gnus-home-adapt-file nil
-  "Variable to control where new adaptive score entries are to go.
+  "*Variable to control where new adaptive score entries are to go.
 This variable allows the same syntax as `gnus-home-score-file'."
   :group 'gnus-score-adapt
   :group 'gnus-score-files
   :type '(choice string
                 (repeat (choice string
                                 (cons regexp (repeat file))
-                                function))
-                function))
+                                (function :value fun)))
+                (function :value fun)))
 
 (defcustom gnus-default-adaptive-score-alist
   '((gnus-kill-file-mark)
@@ -215,7 +216,7 @@ This variable allows the same syntax as `gnus-home-score-file'."
     (gnus-catchup-mark (subject -10))
     (gnus-killed-mark (from -1) (subject -20))
     (gnus-del-mark (from -2) (subject -15)))
-"Alist of marks and scores."
+"*Alist of marks and scores."
 :group 'gnus-score-adapt
 :type '(repeat (cons (symbol :tag "Mark")
                     (repeat (list (choice :tag "Header"
@@ -225,7 +226,7 @@ This variable allows the same syntax as `gnus-home-score-file'."
                                   (integer :tag "Score"))))))
 
 (defcustom gnus-ignored-adaptive-words nil
-  "List of words to be ignored when doing adaptive word scoring."
+  "*List of words to be ignored when doing adaptive word scoring."
   :group 'gnus-score-adapt
   :type '(repeat string))
 
@@ -244,7 +245,7 @@ This variable allows the same syntax as `gnus-home-score-file'."
     "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."
+  "*Default list of words to be ignored when doing adaptive word scoring."
   :group 'gnus-score-adapt
   :type '(repeat string))
 
@@ -253,11 +254,16 @@ This variable allows the same syntax as `gnus-home-score-file'."
     (,gnus-catchup-mark . -10)
     (,gnus-killed-mark . -20)
     (,gnus-del-mark . -15))
-"Alist of marks and scores."
+"*Alist of marks and scores."
 :group 'gnus-score-adapt
 :type '(repeat (cons (character :tag "Mark")
                     (integer :tag "Score"))))
 
+(defcustom gnus-adaptive-word-minimum nil
+  "*If a number, this is the minimum score value that can be assigned to a word."
+  :group 'gnus-score-adapt
+  :type '(choice (const nil) integer))
+
 (defcustom gnus-score-mimic-keymap nil
   "*Have the score entry functions pretend that they are a keymap."
   :group 'gnus-score-default
@@ -275,13 +281,13 @@ If this variable is nil, exact matching will always be used."
   :type '(choice (const nil) integer))
 
 (defcustom gnus-score-uncacheable-files "ADAPT$"
-  "All score files that match this regexp will not be cached."
+  "*All score files that match this regexp will not be cached."
   :group 'gnus-score-adapt
   :group 'gnus-score-files
   :type 'regexp)
 
 (defcustom gnus-score-default-header nil
-  "Default header when entering new scores.
+  "*Default header when entering new scores.
 
 Should be one of the following symbols.
 
@@ -307,10 +313,11 @@ If nil, the user will be asked for a header."
                 (const :tag "xref" x)
                 (const :tag "lines" l)
                 (const :tag "date" d)
-                (const :tag "followup" f)))
+                (const :tag "followup" f)
+                (const :tag "ask" nil)))
 
 (defcustom gnus-score-default-type nil
-  "Default match type when entering new scores.
+  "*Default match type when entering new scores.
 
 Should be one of the following symbols.
 
@@ -336,15 +343,16 @@ If nil, the user will be asked for a match type."
                 (const :tag "this date" n)
                 (const :tag "less than number" <)
                 (const :tag "greater than number" >)
-                (const :tag "equal than number" =)))
+                (const :tag "equal than number" =)
+                (const :tag "ask" nil)))
 
 (defcustom gnus-score-default-fold nil
-  "Use case folding for new score file entries iff not nil."
+  "*Use case folding for new score file entries iff not nil."
   :group 'gnus-score-default
   :type 'boolean)
 
 (defcustom gnus-score-default-duration nil
-  "Default duration of effect when entering new scores.
+  "*Default duration of effect when entering new scores.
 
 Should be one of the following symbols.
 
@@ -360,10 +368,15 @@ If nil, the user will be asked for a duration."
                 (const :tag "ask" nil)))
 
 (defcustom gnus-score-after-write-file-function nil
-  "Function called with the name of the score file just written to disk."
+  "*Function called with the name of the score file just written to disk."
   :group 'gnus-score-files
   :type 'function)
 
+(defcustom gnus-score-thread-simplify nil
+  "*If non-nil, subjects will simplified as in threading."
+  :group 'gnus-score-various
+  :type 'boolean) 
+
 \f
 
 ;; Internal variables.
@@ -469,7 +482,6 @@ The user will be prompted for header to score on, match type,
 permanence, and the string to be used.  The numerical prefix will be
 used as score."
   (interactive (gnus-interactive "P\ny"))
-  (gnus-set-global-variables)
   (let* ((nscore (gnus-score-default score))
         (prefix (if (< nscore 0) ?L ?I))
         (increase (> nscore 0))
@@ -969,14 +981,12 @@ SCORE is the score to add."
 (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))
@@ -995,7 +1005,6 @@ SCORE is the score to add."
 (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)
@@ -1009,21 +1018,21 @@ SCORE is the score to add."
 (defun gnus-score-edit-current-scores (file)
   "Edit the current score alist."
   (interactive (list gnus-current-score-file))
-  (gnus-set-global-variables)
-  (let ((winconf (current-window-configuration)))
-    (when (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)
-    (select-window (get-buffer-window gnus-score-edit-buffer))
-    (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-exit] to save edits")))
+  (if (not gnus-current-score-file)
+      (error "No current score file")
+    (let ((winconf (current-window-configuration)))
+      (when (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-exit] to save edits"))))
 
 (defun gnus-score-edit-file (file)
   "Edit a score file."
@@ -1098,30 +1107,35 @@ SCORE is the score to add."
          (decay (car (gnus-score-get 'decay alist)))
          (eval (car (gnus-score-get 'eval alist))))
       ;; Perform possible decays.
-      (when gnus-decay-scores
-       (when (or (not decay)
-                 (gnus-decay-scores alist decay))
-         (gnus-score-set 'touched '(t) alist)
-         (gnus-score-set 'decay (list (gnus-time-to-day (current-time))))))
+      (when (and gnus-decay-scores
+                (or cached (file-exists-p file))
+                (or (not decay)
+                    (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)
-          (setq lists (apply 'append lists
-                             (mapcar (lambda (file)
-                                       (gnus-score-load-file file))
-                                     (if adapt-file (cons adapt-file files)
-                                       files)))))
-      (and eval (not global) (eval eval))
+      (when (and files (not global))
+       (setq lists (apply 'append lists
+                          (mapcar (lambda (file)
+                                    (gnus-score-load-file file))
+                                  (if adapt-file (cons adapt-file files)
+                                    files)))))
+      (when (and eval (not global))
+       (eval eval))
       ;; We then expand any exclude-file directives.
       (setq gnus-scores-exclude-files
            (nconc
-            (mapcar
-             (lambda (sfile)
-               (expand-file-name sfile (file-name-directory file)))
-             exclude-files)
+            (apply
+             'nconc
+             (mapcar
+              (lambda (sfile)
+                (list
+                 (expand-file-name sfile (file-name-directory file))
+                 (expand-file-name sfile gnus-kill-files-directory)))
+              exclude-files))
             gnus-scores-exclude-files))
-      (if (not local)
-         ()
+      (unless local
        (save-excursion
          (set-buffer gnus-summary-buffer)
          (while local
@@ -1823,6 +1837,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.
+        (simplify (and gnus-score-thread-simplify
+                       (string= "subject" header)))
        alike last this art entries alist articles
        fuzzies arts words kill)
 
@@ -1838,6 +1854,8 @@ SCORE is the score to add."
     (erase-buffer)
     (while (setq art (pop articles))
       (setq this (aref (car art) gnus-score-index))
+      (if simplify
+        (setq this (gnus-map-function gnus-simplify-subject-functions this)))
       (if (equal last this)
          ;; O(N*H) cons-cells used here, where H is the number of
          ;; headers.
@@ -1863,7 +1881,6 @@ SCORE is the score to add."
            entries (assoc header alist))
       (while (cdr entries)             ;First entry is the header index.
        (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))
@@ -1871,6 +1888,12 @@ SCORE is the score to add."
               (mt (aref (symbol-name type) 0))
               (case-fold-search (not (memq mt '(?R ?S ?E ?F))))
               (dmt (downcase mt))
+               ; Assume user already simplified regexp and fuzzies
+              (match (if (and simplify (not (memq dmt '(?f ?r))))
+                          (gnus-map-function
+                           gnus-simplify-subject-functions
+                           (nth 0 kill))
+                        (nth 0 kill)))
               (search-func
                (cond ((= dmt ?r) 're-search-forward)
                      ((or (= dmt ?e) (= dmt ?s) (= dmt ?f)) 'search-forward)
@@ -2075,6 +2098,7 @@ SCORE is the score to add."
       (set-buffer gnus-summary-buffer)
       (gnus-score-load-file
        (or gnus-newsgroup-adaptive-score-file
+          (gnus-home-score-file gnus-newsgroup-name t)
           (gnus-score-file-name
            gnus-newsgroup-name gnus-adaptive-file-suffix))))
     ;; Perform ordinary line scoring.
@@ -2085,7 +2109,7 @@ SCORE is the score to add."
               (alist malist)
               (date (current-time-string))
               (data gnus-newsgroup-data)
-              elem headers match)
+              elem headers match func)
          ;; First we transform the adaptive rule alist into something
          ;; that's faster to process.
          (while malist
@@ -2094,19 +2118,21 @@ SCORE is the score to add."
              (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
+             (when (fboundp
+                    (setq func
+                          (intern
                            (concat "mail-header-"
                                    (if (eq (caar elem) 'followup)
                                        "message-id"
-                                     (downcase (symbol-name (caar elem))))))
-                         h)))
+                                     (downcase (symbol-name (caar elem))))))))
+               (setcdr (car elem)
+                       (cons (if (eq (caar elem) 'followup)
+                                 "references"
+                               (symbol-name (caar elem)))
+                             (cdar elem)))
+               (setcar (car elem)
+                       `(lambda (h)
+                          (,func h))))
              (setq elem (cdr elem)))
            (setq malist (cdr malist)))
          ;; Then we score away.
@@ -2167,7 +2193,11 @@ SCORE is the score to add."
                      ;; 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))
+                     (setq val (+ score (or val 0)))
+                     (if (and gnus-adaptive-word-minimum
+                              (< val gnus-adaptive-word-minimum))
+                         (setq val gnus-adaptive-word-minimum))
+                     (gnus-sethash word val hashtb))
                    (erase-buffer))))
            (set-syntax-table syntab))
          ;; Make all the ignorable words ignored.
@@ -2594,57 +2624,58 @@ The list is determined from the variable gnus-score-file-alist."
   (let ((funcs gnus-score-find-score-files-function)
        (group (or group gnus-newsgroup-name))
        score-files)
-    ;; 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 (nreverse (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 (nreverse (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-find-parameter group 'score-file)))
-      (when param-file
-       (push param-file score-files)))
-    ;; Expand all files names.
-    (let ((files score-files))
-      (while files
-       (when (stringp (car files))
-         (setcar files (expand-file-name
-                        (car files) gnus-kill-files-directory)))
-       (pop files)))
-    (setq score-files (nreverse score-files))
-    ;; Remove any duplicate score files.
-    (while (and score-files
-               (member (car score-files) (cdr score-files)))
-      (pop score-files))
-    (let ((files score-files))
-      (while (cdr files)
-       (if (member (cadr files) (cddr files))
-           (setcdr files (cddr files))
-         (pop files))))
-    ;; Do the scoring if there are any score files for this group.
-    score-files))
+    (when group
+      ;; 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 (nreverse (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 (nreverse (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-find-parameter group 'score-file)))
+       (when param-file
+         (push param-file score-files)))
+      ;; Expand all files names.
+      (let ((files score-files))
+       (while files
+         (when (stringp (car files))
+           (setcar files (expand-file-name
+                          (car files) gnus-kill-files-directory)))
+         (pop files)))
+      (setq score-files (nreverse score-files))
+      ;; Remove any duplicate score files.
+      (while (and score-files
+                 (member (car score-files) (cdr score-files)))
+       (pop score-files))
+      (let ((files score-files))
+       (while (cdr files)
+         (if (member (cadr files) (cddr files))
+             (setcdr files (cddr files))
+           (pop 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."