;;; fast-kill.el --- fast kill files for gnus

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; File:         fast-kill.el
;; RCS:          $Header: $
;; Description:  fast kill files for gnus
;; Author:       Christian Limpach <chris@nice.ch>
;; Created:      Mon Oct 17 22:54:01 1994
;; Modified:     Thu Apr  6 18:05:41 1995 (Christian Limpach) chris@nice.ch
;; Language:     Emacs-Lisp
;; Package:      N/A
;; Status:       Released
;;
;; (C) Copyright 1994, Christian Limpach, all rights reserved.
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;; This program is free software; you can redistribute it and/or
;; modify it under the terms of the GNU General Public License as
;; published by the Free Software Foundation; either version 2 of the
;; License, or (at your option) any later version.
;;
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
;; General Public License for more details.
;;
;; A copy of the GNU General Public License can be obtained from this
;; program's author (send electronic mail to chris@nice.ch) or from
;; the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA
;; 02139, USA.

;;
;; Purpose of this package:
;;
;; Large kill files take often quite a long time to apply. I found it
;; really annoying that kill files are applied more than once to
;; articles which have been left unread during the last session. This
;; is not necessary since usually adding new kill commands will have
;; killed the articles currently unread anyway. This package remembers
;; on which articles the kill file has been applied and will only
;; check new articles. Furthermore, this is done before the articles
;; get sorted in the gnus-select-group-hook and before the summary
;; buffer gets prepared. This results in further speed gain as the
;; killed articles are no longer sorted or prepared to be displayed in
;; the summary buffer.
;; The time required to enter a large newsgroup with a large kill file
;; is reduced to 1/3 of the time taken with the usual kill packages
;; (either gnus-kill or expire-kill) Subsequent entering will even
;; take less time, only new articles will be checked...
;; fast-kill is also expire-kill compatible, just load expirekill
;; before loading fast-kill.

;; Installation instructions
;;
;; all you need to do is to load fast-kill.el (it's best to compile it
;; though, you can ignore all byte-compiler warnings) and setup some
;; key bindings as described in the Usage instructions. To use
;; fast-kill while batch-killing:
;; emacs -batch -l gnus -l fast-kill -f gnus-batch-kill [NEWSGROUP]
;; or if you use expire-kill as well:
;; emacs -batch -l gnus -l expirekill -l fast-kill -f gnus-batch-kill
;; [NEWSGROUP]

;; Usage instructions:
;;
;; use fast-kill like any other kill file mechanism.
;; setting up key bindings, Examples:
;; - without expire-kill:
;;   (setq fast-kill-summary-kill-same-subject "k"
;;   	   fast-kill-summary-kill-same-subject-and-select "\C-ck"
;;   	   fast-kill-kill-file-kill-by-subject "\C-c\C-f\C-s"
;;   	   fast-kill-kill-file-kill-by-thread "\C-c\C-f\C-t")
;; - with expire-kill:
;;   (setq fast-kill-expire-summary-kill-same-subject "k"
;;   	   fast-kill-expire-summary-kill-same-subject-and-select "\C-ck"
;;   	   fast-kill-expire-summary-kill-thread "K"
;;   	   fast-kill-expire-summary-kill-thread-and-select "\C-cK"
;;   	   fast-kill-summary-kill-same-subject "\C-c\C-k"
;;   	   fast-kill-summary-kill-same-subject-and-select "\C-c\C-c\C-k"
;;   	   fast-kill-expire-kill-file-kill-by-subject "\C-c\C-f\C-s"
;;   	   fast-kill-expire-kill-file-kill-by-thread "\C-c\C-f\C-t"
;;   	   fast-kill-kill-file-kill-by-subject "\C-c\C-fs"
;;   	   fast-kill-kill-file-kill-by-thread "\C-c\C-ft")

;; Conversion of existing kill files:
;;
;; you can convert your existing kill files to fast-kill files, as
;; long as you have only used normal or expiring kills with the
;; default kill action. fast-kill-header-alist lists all supported
;; kill keys...  DO NOT CONVERT KILL COMMANDS WHICH PERFORM OTHER
;; ACTIONS THAN THE DEFAULT KILL ACTION !!!
;;
;; Examples:
;; (gnus-kill "Subject" "here goes the subject") becomes:
;; (fast-kill 'subject "here goes the subject") and
;; (expire-kill "Subject" "here goes the subject" 123456) becomes:
;; (fast-kill 'subject "here goes the subject" 123456)
;;
;; the following perl script converts gnus-kill and expire-kill
;; commands on the subject or references field in all *.KILL files:
;; perl -pi.old -e 's/^\((expire|gnus)-kill "(Subject|References)" /(fast-kill '\''\l\2 /' *.KILL
;; after this: egrep -l '\((expire|gnus)-kill' *.KILL
;; and edit the files listed by egrep (if any) manually...

;; Known bugs and Limitations:
;;
;; - empty kill files are not deleted at all.
;; - kill-file-mode gnus-kill-file-apply-buffer and
;;   gnus-kill-file-apply-last-sexp do not apply fast-kill commands.
;; - you can only kill articles, you can't mark them.
;; - crossposts are not killed

;;
;; the latest version is available as
;; ftp://nice.ethz.ch/users/chris/fast-kill.el

;;
;; LCD Archive Entry:
;; fast-kill|Christian Limpach|chris@nice.ch
;; |fast kill files for gnus
;; |Thu Apr  6 17:39:47 1995|0.1|


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; User customizable variables
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(defvar fast-kill-high-alist nil
  "alist of last killed message in newsgroup.")

(defvar fast-kill-high-filename "FAST-KILL.high"
  "filename where to store fast-kill-high-alist.
expanded relativ to gnus-kill-files-directory.")
  
(defconst fast-kill-header-alist
  '((date . (nntp-header-date h))
    (from . (nntp-header-from h))
    (id . (nntp-header-id h))
    (references . (nntp-header-references h))
    (subject . (nntp-header-subject h))
    (xref . (nntp-header-xref h)))
  "alist of header and expressions how to extract these headers.")

(defvar fast-kill-simplify-subject t
  "*Determines whether simplified subjects will be used as kill patterns.
If nil, the original subject will be used in subject-based kill
patterns.  Otherwise, the subject will first be simplified using
gnus-simplify-subject.")

(defvar fast-kill-truncate-subject nil
  "*If set to a number, subject patterns are truncated at that length.
UUCP often cuts off subjects at 24 characters.  Truncating kill
patterns at this limit will tend to improve hit rates.  If
fast-kill-simplify-subject is set, simplification happens first, then
truncation.")

(defvar fast-kill-append-kills nil
  "*Determines where new patterns should be placed in kill files.
If nil, new patterns are placed at the beginning of kill files.
Otherwise, they are added at the end.  If you're not sure how best to
set this, you don't need to worry about it.")

(defvar fast-kill-summary-kill-same-subject nil
  "*Key sequence to which to bind the function of the same name.
If set to a string representing a key sequence, that sequence will
evoke the same-named function in the *Summary* buffer.  Otherwise, no
binding will be made for this function.")

(defvar fast-kill-summary-kill-same-subject-and-select nil
  "*Key sequence to which to bind the function of the same name.
If set to a string representing a key sequence, that sequence will
evoke the same-named function in the *Summary* buffer.  Otherwise, no
binding will be made for this function.")

(defvar fast-kill-summary-kill-thread nil
  "*Key sequence to which to bind the function of the same name.
If set to a string representing a key sequence, that sequence will
evoke the same-named function in the *Summary* buffer.  Otherwise, no
binding will be made for this function.")

(defvar fast-kill-summary-kill-thread-and-select nil
  "*Key sequence to which to bind the function of the same name.
If set to a string representing a key sequence, that sequence will
evoke the same-named function in the *Summary* buffer.  Otherwise, no
binding will be made for this function.")

(defvar fast-kill-expire-summary-kill-same-subject nil
  "*Key sequence to which to bind the function of the same name.
If set to a string representing a key sequence, that sequence will
evoke the same-named function in the *Summary* buffer.  Otherwise, no
binding will be made for this function.")

(defvar fast-kill-expire-summary-kill-same-subject-and-select nil
  "*Key sequence to which to bind the function of the same name.
If set to a string representing a key sequence, that sequence will
evoke the same-named function in the *Summary* buffer.  Otherwise, no
binding will be made for this function.")

(defvar fast-kill-expire-summary-kill-thread nil
  "*Key sequence to which to bind the function of the same name.
If set to a string representing a key sequence, that sequence will
evoke the same-named function in the *Summary* buffer.  Otherwise, no
binding will be made for this function.")

(defvar fast-kill-expire-summary-kill-thread-and-select nil
  "*Key sequence to which to bind the function of the same name.
If set to a string representing a key sequence, that sequence will
evoke the same-named function in the *Summary* buffer.  Otherwise, no
binding will be made for this function.")

(defvar fast-kill-kill-file-kill-by-subject nil
  "*Key sequence to which to bind the function of the same name.
If set to a string representing a key sequence, that sequence will
evoke the same-named function in the kill file buffers.  Otherwise, no
binding will be made for this function.")

(defvar fast-kill-kill-file-kill-by-thread nil
  "*Key sequence to which to bind the function of the same name.
If set to a string representing a key sequence, that sequence will
evoke the same-named function in the kill file buffers.  Otherwise, no
binding will be made for this function.")

(defvar fast-kill-expire-kill-file-kill-by-subject nil
  "*Key sequence to which to bind the function of the same name.
If set to a string representing a key sequence, that sequence will
evoke the same-named function in the kill file buffers.  Otherwise, no
binding will be made for this function.")

(defvar fast-kill-expire-kill-file-kill-by-thread nil
  "*Key sequence to which to bind the function of the same name.
If set to a string representing a key sequence, that sequence will
evoke the same-named function in the kill file buffers.  Otherwise, no
binding will be made for this function.")



(require 'cl)
(require 'gnus)
(require 'advice)

(autoload 'expire-days-since "expirekill"
  "Return the integral number of days between today and TIMESTAMP.")
(autoload 'expire-convert-timestamp "expirekill"
  "Convert a time stamp to the user's preferred format.
If TIMESTAMP is a string, it is assumed to be in calc format; integers
are assumed to belong to calendar.  An equivalent string or integer is
returned, depending upon the value of expire-date-package.")
(autoload 'expire-current-date "expirekill"
  "Return some representation of today's date.")


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Initialization
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;; Bind each of the major user-callable functions, if the same-named
;; variable is set to a string representing a key sequence.

(mapcar
 (function
  (lambda (binding)
    (let ((function (car binding))
	  (keymap (cdr binding)))
      (and (stringp (symbol-value function))
	   (define-key
	     (symbol-value keymap)
	     (symbol-value function)
	     function)))))
 '((fast-kill-summary-kill-same-subject . gnus-summary-mode-map)
   (fast-kill-summary-kill-same-subject-and-select . gnus-summary-mode-map)
   (fast-kill-summary-kill-thread . gnus-summary-mode-map)
   (fast-kill-summary-kill-thread-and-select . gnus-summary-mode-map)
   (fast-kill-expire-summary-kill-same-subject . gnus-summary-mode-map)
   (fast-kill-expire-summary-kill-same-subject-and-select
    . gnus-summary-mode-map)
   (fast-kill-expire-summary-kill-thread . gnus-summary-mode-map)
   (fast-kill-expire-summary-kill-thread-and-select . gnus-summary-mode-map)
   (fast-kill-kill-file-kill-by-subject . gnus-kill-file-mode-map)
   (fast-kill-kill-file-kill-by-thread . gnus-kill-file-mode-map)
   (fast-kill-expire-kill-file-kill-by-subject . gnus-kill-file-mode-map)
   (fast-kill-expire-kill-file-kill-by-thread . gnus-kill-file-mode-map)))

;; add-hook would be fine, but fast-kill needs to be run before any
;; subject tree builders/sorters
(defadvice gnus-summary-read-group (around hack-in-fast-kill first activate)
  (let ((gnus-select-group-hook gnus-select-group-hook))
    (add-hook 'gnus-select-group-hook 'fast-kill-apply)
    ad-do-it))

(if (fboundp 'expire-summary-kill-using)
    (defun fast-kill-flush-buffer ()
      "flush fast-kill modified buffers using expire flush method."
      (push (current-buffer) expire-modified-buffers)
      (bury-buffer)
      (expire-possibly-flush 'always))
  (defun fast-kill-flush-buffer ()
    "always flush fast-kill modified buffers."
    (bury-buffer)
    (if (buffer-modified-p)
	(save-buffer))))


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Internal functions
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(defun fast-kill (field patt &optional timestamp)
  "dummy function to make functions in gnus-apply-kill-hook ignore our
kill commands.")

(defun fast-kill-execute (field patt &optional timestamp)
  (let ((hold fast-kill-nh)
	(headers (cdr fast-kill-nh))
	(case-fold-search nil)
	h (killed nil) string)
    (while headers
      (setq h (car headers))
      (if (and (vectorp h)
	       (and
		(stringp
		 (setq string (eval (cdr
				     (assoc field fast-kill-header-alist)))))
		(string-match patt string)))
	  (progn
	    (gnus-mark-article-as-read (nntp-header-number h))
	    (setq killed t
		  headers (cdr headers))
	    (setcdr hold headers))
	(setq hold headers
	      headers (cdr headers))))
    killed))

(defun fast-kill-apply ()
  (if show-all nil
    (message "fast kill...")
    (let ((kill-file (gnus-newsgroup-kill-file gnus-newsgroup-name))
	  buffer last-kill)
      (or fast-kill-high-alist
	  (progn
	    (load (expand-file-name fast-kill-high-filename
				    (or gnus-kill-files-directory "~/News"))
		  t nil t)))
      (setq last-kill (or (cdr (assoc gnus-newsgroup-name
				      fast-kill-high-alist)) 0))
      (and (or (file-readable-p kill-file)
	       (get-file-buffer kill-file))
	   (save-excursion
	     (setq buffer (find-file kill-file))
	     (goto-char (point-min))
	     (let ((original-syntax-table (syntax-table)))
	       (set-syntax-table emacs-lisp-mode-syntax-table)
	       (condition-case nil
		   (fast-kill-eval-buffer buffer last-kill)
		 (end-of-file (set-syntax-table original-syntax-table))))
	     (fast-kill-flush-buffer)))
      (if (assoc gnus-newsgroup-name fast-kill-high-alist)
	  (setcdr (assoc gnus-newsgroup-name fast-kill-high-alist)
		  gnus-newsgroup-end)
	(push (cons gnus-newsgroup-name gnus-newsgroup-end)
	      fast-kill-high-alist))
      (save-excursion
	(set-buffer (get-buffer-create " *FAST-KILL.high*"))
	(buffer-disable-undo (current-buffer))
	(erase-buffer)
	(insert ";; this file records per newsgroup the last article\n")
	(insert ";; to which fast-kill's have been applied.\n")
	(insert (concat
		 "(setq fast-kill-high-alist '"
		 (prin1-to-string (symbol-value 'fast-kill-high-alist))
		 ")\n"))
	(let ((make-backup-files nil)
	      (version-control nil)
	      (require-final-newline t)) ;Don't ask even if requested.
	  (write-file (expand-file-name "FAST-KILL.high"
					(or gnus-kill-files-directory
					    "~/News"))))
	(kill-buffer (current-buffer)))
      )
    (message "fast kill... Done.")))

(defun fast-kill-eval-buffer (buffer high)
  (let ((fast-kill-nh gnus-newsgroup-headers))
    (while (and fast-kill-nh
		(< (nntp-header-number (car fast-kill-nh)) high))
      (setq fast-kill-nh (cdr fast-kill-nh)))
    (if (cdr fast-kill-nh)
	(progn
	  (if (eq fast-kill-nh gnus-newsgroup-headers)
	      (setq gnus-newsgroup-headers nil
		    fast-kill-nh (nconc (list nil) fast-kill-nh))
	    (setq fast-kill-nh (nconc (list nil)
				      (prog1
					  (cdr fast-kill-nh)
					(setcdr fast-kill-nh nil)))))
	  (while (re-search-forward "(fast-kill " nil t)
	    (goto-char (match-beginning 0))
	    (let* ((command (cons 'fast-kill-execute (cdr (read buffer))))
		   (timestamp (nth 3 command)))
	      (if timestamp
		  ;; expire-kill mode
		  (if (eval command)
		      (progn
			;; update timestamp
			(delete-region (point) (progn (backward-sexp)
						      (point)))
			(fast-kill-insert-kill (nth 1 (nth 1 command))
					       (nth 2 command) nil t))
		    (and (> (expire-days-since
			     (expire-convert-timestamp timestamp))
			    expire-maximum-age)
			 (progn
			   (delete-region (point) (progn (backward-sexp)
							 (point)))
			   (delete-blank-lines)
			   (delete-blank-lines)
			   (and (eobp)
				(insert ?\n)))))
		;; just kill
		(eval command))))
	  (setq gnus-newsgroup-headers (nconc gnus-newsgroup-headers
					      (cdr fast-kill-nh)))))))

(defun fast-kill-insert-kill (field pattern ask &optional timestamp)
  (insert (format (concat "(fast-kill '%s %s" (if timestamp " %s") ")")
		  field
		  (prin1-to-string
		   (if ask
		       (read-from-minibuffer (concat (symbol-name field)
						     ":  ") pattern)
		     pattern))
		  (cond ((eq timestamp t)
			 (expire-current-date))
			(timestamp
			 (prin1-to-string timestamp))
			(t
			 nil))))
  (or (looking-at "\n")
      (insert ?\n)))

(defun fast-kill-summary-kill-using (kill-mode-function
				     ask &optional timestamp)
  "In the *Summary* buffer, add a new expiring kill pattern.
First argument FUNCTION should be the name of a function to be called,
with no arguments, in the local kill file to actually insert the new
pattern.  If second argument ASK is non-nil, allow the user to edit
the kill pattern before it is inserted."
  (let* ((gnus-current-kill-article (gnus-summary-article-number))
	 (kill-file (gnus-newsgroup-kill-file gnus-newsgroup-name))
	 (kill-directory (file-name-directory kill-file)))
    (save-window-excursion
      (or (file-exists-p kill-directory)
	  (make-directory kill-directory t))
      (find-file kill-file)
      (goto-char (if fast-kill-append-kills
		     (point-max)
		   (point-min)))
      (funcall kill-mode-function ask timestamp)
      (fast-kill-flush-buffer))))


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Interactive functions
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(defun fast-kill-summary-kill-same-subject (ask &optional timestamp)
  "Add a local, expiring kill pattern for the current subject.
Also, mark all articles with this subject in the current buffer as
read, but do not select the next unread article.  Argument ASK non-nil
(C-u if called interactively) allows the user to edit the pattern
before it is inserted."
  (interactive "P")
  (fast-kill-summary-kill-using
   'fast-kill-kill-file-kill-by-subject ask timestamp)
  (gnus-summary-kill-same-subject nil))

(defun fast-kill-summary-kill-same-subject-and-select (ask &optional timestamp)
  "Add a local, expiring kill pattern for the current subject.
Also, mark all articles with this subject in the current buffer as
read and select the next unread article.  Argument ASK non-nil (C-u if
called interactively) allows the user to edit the pattern before it is
inserted."
  (interactive "P")
  (fast-kill-summary-kill-using
   'fast-kill-kill-file-kill-by-subject ask timestamp)
  (setq this-command 'gnus-summary-kill-same-subject-and-select)
  (gnus-summary-kill-same-subject-and-select nil))

(defun fast-kill-summary-kill-thread (ask &optional timestamp)
  "Add a local, expiring kill pattern for the current thread.  Also,
mark all articles in the current thread as read.  Argument ASK non-nil
(C-u if called interactively) allows the user to edit the pattern
before it is inserted."
  (interactive "P")
  (fast-kill-summary-kill-using
   'fast-kill-kill-file-kill-by-thread ask timestamp)
  (gnus-summary-kill-thread nil))

(defun fast-kill-summary-kill-thread-and-select (ask &optional timestamp)
  "Add a local, expiring kill pattern for the current thread.  Also,
mark all articles in the current thread as read and select the next
unread article.  Argument ASK non-nil (C-u if called interactively)
allows the user to edit the pattern before it is inserted."
  (interactive "P")
  (fast-kill-summary-kill-using
   'fast-kill-kill-file-kill-by-thread ask timestamp)
  (gnus-summary-kill-thread nil)
  (if (memq (gnus-summary-article-number)
	    gnus-newsgroup-unreads)
      (gnus-summary-select-article)
    (gnus-summary-next-unread-article)))

(defun fast-kill-expire-summary-kill-same-subject (ask)
  "Add a local, expiring kill pattern for the current subject.
Also, mark all articles with this subject in the current buffer as
read, but do not select the next unread article.  Argument ASK non-nil
(C-u if called interactively) allows the user to edit the pattern
before it is inserted."
  (interactive "P")
  (fast-kill-summary-kill-same-subject ask t))

(defun fast-kill-expire-summary-kill-same-subject-and-select (ask)
  "Add a local, expiring kill pattern for the current subject.
Also, mark all articles with this subject in the current buffer as
read and select the next unread article.  Argument ASK non-nil (C-u if
called interactively) allows the user to edit the pattern before it is
inserted."
  (interactive "P")
  (fast-kill-summary-kill-same-subject-and-select ask t))

(defun fast-kill-expire-summary-kill-thread (ask)
  "Add a local, expiring kill pattern for the current thread.  Also,
mark all articles in the current thread as read.  Argument ASK non-nil
(C-u if called interactively) allows the user to edit the pattern
before it is inserted."
  (interactive "P")
  (fast-kill-summary-kill-thread ask t))

(defun fast-kill-expire-summary-kill-thread-and-select (ask)
  "Add a local, expiring kill pattern for the current thread.  Also,
mark all articles in the current thread as read and select the next
unread article.  Argument ASK non-nil (C-u if called interactively)
allows the user to edit the pattern before it is inserted."
  (interactive "P")
  (fast-kill-summary-kill-thread-and-select ask t))

(defun fast-kill-kill-file-kill-by-subject (ask &optional timestamp)
  "Insert expiring KILL command for current subject.
Argument ASK non-nil (C-u if called interactively) allows the user to
edit the pattern before it is inserted."
  (interactive "P")
  (let* ((subject (if gnus-current-kill-article
		      (gnus-header-subject
		       (gnus-find-header-by-number
			gnus-newsgroup-headers
			gnus-current-kill-article))
		    ""))
	 (cutoff (and (numberp fast-kill-truncate-subject)
		      (min fast-kill-truncate-subject
			   (length subject)))))
    (fast-kill-insert-kill 'subject
			   (regexp-quote
			    (substring (if fast-kill-simplify-subject
					   (gnus-simplify-subject subject)
					 subject)
				       0 cutoff))
			   ask timestamp)))

(defun fast-kill-kill-file-kill-by-thread (ask &optional timestamp)
  "Insert expiring KILL command for current thread.
Argument ASK non-nil (C-u if called interactively) allows the user
to edit the pattern before it is inserted."
  (interactive "P")
  (fast-kill-insert-kill 'references
		      (if gnus-current-kill-article
			  (regexp-quote
			   (gnus-header-id
			    (gnus-find-header-by-number
			     gnus-newsgroup-headers
			     gnus-current-kill-article)))
			"")
		      ask timestamp))

(defun fast-kill-expire-kill-file-kill-by-subject (ask)
  "Insert expiring KILL command for current subject.
Argument ASK non-nil (C-u if called interactively) allows the user to
edit the pattern before it is inserted."
  (interactive "P")
  (fast-kill-kill-file-kill-by-subject ask t))

(defun fast-kill-expire-kill-file-kill-by-thread (ask)
  "Insert expiring KILL command for current thread.
Argument ASK non-nil (C-u if called interactively) allows the user
to edit the pattern before it is inserted."
  (interactive "P")
  (fast-kill-kill-file-kill-by-thread ask t))



(provide 'fast-kill)

;;; fast-kill.el ends here
