Commits

Anonymous committed f8f481f

created.

  • Participants
  • Tags xemacs

Comments (0)

Files changed (27)

+1998-01-12  SL Baur  <steve@altair.xemacs.org>
+
+	* Makefile: Update to newer package interface.
+
+1997-12-29  SL Baur  <steve@altair.xemacs.org>
+
+	* Makefile: Created.
+# Makefile for GNATS support lisp code
+
+# This file is part of XEmacs.
+
+# XEmacs 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, or (at your option) any
+# later version.
+
+# XEmacs 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.
+
+# You should have received a copy of the GNU General Public License
+# along with XEmacs; see the file COPYING.  If not, write to
+# the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+# Boston, MA 02111-1307, USA.
+
+VERSION = 1.01
+PACKAGE = gnats
+PKG_TYPE = regular
+REQUIRES = mail-lib xemacs-base
+CATEGORY = comm
+
+ELCS = gnats-admin.elc gnats.elc send-pr.elc
+
+include ../../XEmacs.rules
+
+.PHONY: texi
+
+all:: $(ELCS) auto-autoloads.elc texi
+
+clean::
+	rm -f $(ELCS) auto-autoloads.elc texi/*.info*
+
+texi:
+	$(MAKE) -C texi $(MAKEFLAGS)
+
+srckit: srckit-std
+
+binkit: all
+	rm -rf $(STAGING)/lisp/$(PACKAGE)
+	rm -rf $(STAGING)/info/gnats.info* \
+		$(STAGING)/info/send-pr.info*
+	rm -rf $(STAGING)/etc/categories $(STAGING)/etc/$(PACKAGE)
+	mkdir -p $(STAGING)/lisp/$(PACKAGE)
+	mkdir -p $(STAGING)/etc/$(PACKAGE)
+	mkdir -p $(STAGING)/info
+	cp -a ChangeLog *.el* $(STAGING)/lisp/$(PACKAGE)
+	cp -a texi/gnats.info* $(STAGING)/info
+	cp -a texi/send-pr.info* $(STAGING)/info
+	cp -a etc/categories $(STAGING)/etc
+	cp -a etc/xemacs.org $(STAGING)/etc/$(PACKAGE)
+	(cd $(STAGING); \
+	rm -f $(PACKAGE)-$(VERSION)-pkg.tar*; \
+	tar cf $(PACKAGE)-$(VERSION)-pkg.tar lisp/$(PACKAGE) \
+		info/gnats.info* info/send-pr.info* \
+		etc/categories etc/$(PACKAGE); \
+	gzip -v9 $(PACKAGE)-$(VERSION)-pkg.tar)
+#		    Possible categories for a PR.
+#
+# Any line which begins with a `#' is considered a comment, and GNATS
+# will ignore it. 
+#
+# Each entry has the format:
+#
+# 	category:description:responsible:notify
+#
+# * `category' is the name of the classification for the PR.
+# * `description' can be a normal text description for the
+#   category, like "Development Tools" for the `tools' category.
+# * `responsible' gives the name (which can be found in the remote
+#   file) of the person who will be given responsibility for any PR
+#   appearing in this category.
+# * `notify' are other email addresses which should be given copies of
+#    any PR in this category.
+#
+# The following category is mandatory for GNATS to work.
+#
+pending:Category for faulty PRs:gnats-admin:
+#
+# Sample categories:
+#
+auxiliary:Auxiliary Programs:gnats-admin:
+configuration:Configuration:gnats-admin:mrb@eng.sun.com
+documentation:Documentation Bug:gnats-admin:weiner@infodock.com
+frames:X11 Frames:gnats-admin:
+i18n:I18n Internationalization:martin:
+lisp:Emacs Lisp code:gnats-admin:
+menubars:X11 menubars:gnats-admin:
+misc:Miscellaneous:gnats-admin:
+mule:MULE Internationalization stuffs:jhod:
+performance:Performance Issues:dmoore:
+redisplay:Redisplay Issues:gnats-admin:cthomp@xemacs.org
+scrollbars:X11 scrollbars:gnats-amdin:mrb@eng.sun.com
+subprocesses:All Subprocess stuff:dmoore:
+toolbars:X11 toolbars:gnats-admin:
+gnus:Gnus newsreader:larsi:
+vm:VM Mailreader:kyle:
+W3:W3 Browser:wmperry:
+pending
+auxiliary
+configuration
+documentation
+frames
+i18n
+lisp
+menubars
+misc
+mule
+performance
+redisplay
+scrollbars
+subprocesses
+toolbars
+gnus
+vm
+w3
+test
+;; gnats administration code
+;; display the pr's in a buffer, 
+;; dired-style commands to edit & view them.
+
+;; this version is known to work in XEmacs.
+
+;; author: Roger Hayes, roger.hayes@sun.com
+
+;; copyright: You are welcome to use this software as you see fit.
+;; Neither the author nor his employer make any representation about
+;; the suitability of this software for any purpose whatsoever.
+
+(defconst gnats-admin-copyright 
+"Copyright (c) 1996 Roger Hayes.
+
+Permission to use, copy, modify and distribute this software and
+documentation for any purpose and without fee is hereby granted in
+perpetuity, provided that this COPYRIGHT AND LICENSE NOTICE appears in
+its entirety in all copies of the software and supporting
+documentation.
+
+The names of the author or Sun Microsystems, Inc. shall not be used in
+advertising or publicity pertaining to distribution of the software
+and documentation without specific, written prior permission.
+
+ANY USE OF THE SOFTWARE AND DOCUMENTATION SHALL BE GOVERNED BY
+CALIFORNIA LAW.  THE AUTHOR AND SUN MICROSYSTEMS, INC. MAKE NO
+REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY OF THE SOFTWARE OR
+DOCUMENTATION FOR ANY PURPOSE.  THEY ARE PROVIDED *AS IS* WITHOUT
+EXPRESS OR IMPLIED WARRANTY OF ANY KIND.  THE AUTHOR AND SUN
+MICROSYSTEMS, INC. SEVERALLY AND INDIVIDUALLY DISCLAIM ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE AND DOCUMENTATION, INCLUDING THE
+WARRANTIES OF MERCHANTABILITY, DESIGN, FITNESS FOR A PARTICULAR
+PURPOSE AND NON-INFRINGEMENT OF THIRD PARTY RIGHTS. IN NO EVENT SHALL
+THE AUTHOR OR SUN MICROSYSTEMS, INC. BE LIABLE FOR ANY SPECIAL,
+INDIRECT, INCIDENTAL OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA, OR PROFITS, WHETHER IN
+ACTION ARISING OUT OF CONTRACT, NEGLIGENCE, PRODUCT LIABILITY, OR
+OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+PERFORMANCE OF THIS SOFTWARE OR DOCUMENTATION."
+
+"Copyright and disclaimer notice")
+
+;; magic words are highlighted using font-lock
+
+;; data structures: a pr is represented as an alist
+;; the list of known pr's is represented as a vector
+;; pr references are often done by number
+;; (pr N) takes either a number, and returns the pr assoc list,
+;; or the pr assoc list, returning the same.
+;; regions are tagged with the number of the pr; the indirection lets
+;; updates to pr's happen without disturbing the region.
+
+(defvar pr-list nil "Vector of parsed problem reports.")
+
+(require 'cl)
+
+;;; (require 'match-string)
+;;; inline match-string
+(if (not (fboundp 'match-string))
+    (defun match-string (n &optional target)
+      "Return the text of the NTH match in optional TARGET."
+      (let* ((m-data (match-data))
+	     (idx  	(* 2 n))
+	     (m-beg (elt m-data idx))
+	     (m-end (elt m-data (1+ idx))))
+	(cond 
+	 ((markerp m-beg)
+	  (buffer-substring m-beg m-end))
+	 ((integerp m-beg)
+	  (substring target m-beg m-end))
+	 (t
+	  (error "Bad argument N to match-string"))))))
+
+;;; (require 'edit-expr)
+;; edit-expr -- pop up a buffer to edit an expression
+(if (not (fboundp 'edit-expr))
+    (defun edit-expr (e &optional explain)
+      "Pop up a buffer to edit the EXPRESSION; return the edited value.
+Buffer gets optional EXPLANATION."
+      (with-output-to-temp-buffer "*expr-buffer*"
+	(let ((buffer standard-output)
+	      (val nil)
+	      emark)
+	  
+	  (save-excursion
+	    (pop-to-buffer (buffer-name buffer))
+	    (emacs-lisp-mode)
+	    (delete-region (point-min) (point-max))
+	    (insert (or explain ";; Edit this value"))
+	    (insert "\n")
+	    (setq emark (point-marker))
+	    (prin1 e) 
+	    (goto-char emark)
+	    (message "recursive edit -- M-C-c when done")
+	    (recursive-edit)
+	    (goto-char emark)
+	    (setq val (read buffer)))
+	  val))))
+    
+;; (require 'regret)    ;; creates regression tests in my environment [rh]
+
+(provide 'gnats-admin)
+
+;; add stuff for font-lock; harmless if you don't use font-lock.
+;; beware -- font-lock uses the first match; hence longer words must
+;; precede shorter words, if they both match.
+(defconst gnats-admin::font-lock-keywords
+  '(;; severity
+    ("non-critical" . non-critical)
+    ("critical" . critical)
+    ("serious" .  serious)
+
+    ;; priority
+    ("high" . high)
+    ("medium" . medium)
+    ("low" . low)
+
+    ;; state
+    ("open" . open)
+    ("analyzed" . analyzed)
+    ("suspended" . suspended)
+    ("feedback" . feedback)
+    ("closed\\*" . closed*)
+    ("closed\\?" . closed?)
+    ("closed" . closed)
+
+    ;; class
+    ("sw-bug" . sw-bug) 
+    ("doc-bug" . doc-bug)
+    ("support" . support)
+    ("change-request" . change-request) 
+    ("mistaken" . mistaken) 
+    ("duplicate" . duplicate)))
+
+(defvar gnats-admin::popup-menu
+  '("Gnats-Admin"
+    ["Edit"	'gnats-admin:pr-edit t]
+    ["View"	'gnats-admin:pr-view t]
+    )
+  "Local popup menu.")
+
+(defvar gnats-admin-query-hook nil
+  "Hooklist for post-query processing in gnats-admin mode.")
+(defvar gnats-admin:query-list nil
+  "Results of one query -- may be altered by query hook to change results of query.")
+(defvar gnats-admin-refresh-hook nil
+  "Hooklist for post-refresh processing in gnats-admin mode.
+Run after a refresh -- gnats-admin::dirty-list may contain list of re-read pr numbers")
+
+(defvar gnats-admin::selector nil
+  "Function of one argument that governs if a PR should be diplayed.")
+
+(add-hook 'gnats-admin-refresh-hook
+	  (function (lambda ()
+		      (font-lock-fontify-buffer))))
+
+;;;
+;; first, how do we get & parse the pr's?
+(defun gnats-admin::run-query ()
+  "Run a default query, setting pr-list to the result."
+  (setq pr-list (apply 'vector (gnats-admin::query)))
+  (length pr-list))
+
+(defun trim (s)
+  "trim the leading & trailing blanks from a string"
+  (if (string-match "^\\s-*\\(\\S-.*\\S-\\|\\S-\\)\\s-*$" s)
+      (substring s (match-beginning 1) (match-end 1))
+    "")
+  )
+
+;; parse one pr -- moves point.
+(defun gnats-admin::parse-pr ()
+  "parse one pr, moving point"
+  (let ((pr nil))
+    (let ((flv (make-vector 13 nil))
+	  (lim 0)
+	  (bol 0)
+	  (index 0))
+      (end-of-line)
+      (setq lim (point))
+      (beginning-of-line)
+      (setq bol (point))
+      (while (and (< (point) lim)
+		  (re-search-forward "\\([^|]*\\)|" lim t))
+	(aset flv index (trim (match-string 1)))
+	(setq index (1+ index)))
+      (if (not (= index 13))
+	  (error "Bad PR inquiry: %s" (buffer-substring bol lim)))
+      (setq pr (gnats-admin::vec->pr flv)))
+    (if (not (bolp))
+	(forward-line 1))
+    pr))
+
+(defun gnats-admin::query (&rest args)
+  (let ((prl (list))
+	(buf (get-buffer-create "**gnats-query*")))
+    (save-excursion
+      (set-buffer buf)
+      (delete-region (point-min) (point-max))
+      (message "Running query")
+      (setq args
+	    (mapcar (function (lambda (x) (format "%s" x))) args))
+      (apply 'call-process "query-pr" nil t nil "--sql" args)
+      (message "Query completed")
+      ;; now parse the output
+      (goto-char (point-min))
+      (while (not (eobp))
+	(setq prl (cons (gnats-admin::parse-pr) prl)))
+      )
+    (message "Result parsed")
+    ;; lots of stuff to apply the proper hook
+    (setq gnats-admin:query-list prl)
+    (run-hooks 'gnats-admin-query-hook)
+    (setq prl gnats-admin:query-list)
+    (setq gnats-admin:query-list nil)
+    (nreverse prl)))
+
+;; 
+;; 
+(defun gnats-admin::vec->pr (v)
+  "massage a 13-element vector into the internal pr representation.
+fields are as described in query-pr documentation."
+  (if (not (and (vectorp v)
+		(= (length v) 13)))
+      (error "Not a valid PR intermediate form"))
+
+  ;;; 0 - pr number
+  (aset v 0 (read (aref v 0)))
+  ;;; 1 - category
+  (aset v 1 (read (aref v 1)))
+  ;;; 2 - synopsis
+  ; leave as string
+  ;;; 3 - confidential
+  (aset v 3 (if (equal "no" (aref v 3)) nil (aref v 3)))
+  ;;; 4 - severity
+  (let ((num (read (aref v 4))))
+    (aset v 4 (aref 
+	       [null critical serious non-critical] 
+	       num)))
+  ;;; 5 - priority
+  (let ((num (read (aref v 5))))
+    (aset v 5 (aref 
+	       [null high medium low]
+	       num)))
+  ;;; 6 - responsible
+  ; leave as string
+  ;;; 7 - state
+  (let ((num (read (aref v 7))))
+    (aset v 7 (aref
+	       [null open analyzed suspended feedback closed]
+	       num)))
+  ;;; 8 - class
+  (let ((num (read (aref v 8))))
+    (aset v 8 (aref
+	       [null sw-bug doc-bug support change-request mistaken duplicate]
+	       num)))
+  ;;; 9 - submitter-id
+  ; leave as string
+  ;;; 10 - arrival-date
+  ; leave as string
+  ;;; 11 - originator
+  ; leave as string
+  ;;; 12 - release
+  ; leave as string
+
+  ;; the fields of v have been transformed; now map them into an alist
+  (do
+      ((vx 0 (1+ vx))			; v index
+       (an				; field names (in order!)
+	(gnats-admin::pr-field-names)
+	(cdr an))
+       (al nil)				; assoc list
+       )
+      ((null an) (nreverse al))		; <- here's where the result comes from
+    (setq al (cons (cons (car an) (aref v vx)) al))
+    ))
+
+(defun gnats-admin::pr-get (pr field)
+  "Get, from PR, value of slot named FIELD (a symbol)."
+  (let ((p (assq field (gnats-admin:pr pr))))
+    (if p
+	(cdr p)
+      nil)))
+
+(defun nset-assq (alist key val)
+  "destructively set key'v association in the alist to val.  returns the original
+list, unless it was null"
+  (let ((p (assq key alist)))
+    (if p
+	(progn 
+	  (setcdr p val)
+	  alist)
+      (nconc alist (list (cons key val))))))
+
+(defun gnats-admin::pr-set! (pr field val)
+  "Set, in PR, slot named FIELD to VAL.  Slot name is a symbol."
+  (nset-assq pr field val))
+
+;; fast version of field name->index mapper
+;; also tests if a symbol is a field name present in sql report.
+(defun gnats-admin::pr-field-index (feild)
+  (case feild
+    (Number 0)
+    (Category 1)
+    (Synopsis 2)
+    (Confidential  3)
+    (Severity  4)
+    (Priority 5)
+    (Responsible 6)
+    (State 7)
+    (Class 8)
+    (Submitter-Id 9)
+    (Arrival-Date 10)
+    (Originator 11) 
+    (Release 12)))
+
+;; next is for completing-read
+;; order must be the same as indices
+;; content is (name index type width)
+;; width is field width, not counting space
+(defconst gnats-admin::pr-field-alist
+  '(("Number" 0 integer 3)
+    ("Category" 1 symbol 15)
+    ("Synopsis" 2 string 80)
+    ("Confidential"  3 boolean 1)
+    ("Severity"  4 symbol 12)
+    ("Priority" 5 symbol 6)
+    ("Responsible" 6 string 7)
+    ("State" 7 symbol 8)
+    ("Class" 8 symbol 14)
+    ("Submitter-Id" 9 string 7)
+    ("Arrival-Date" 10 string 14)
+    ("Originator" 11 string 32) 
+    ("Release" 12 string 48))
+  "Alist that maps field-name->(name index type width)")
+
+(defun gnats-admin::pr-field-names ()
+  "List of symbols that are field keys in pr.  Must be in order."
+  (mapcar (function (lambda (pr) (intern (car pr))))
+	  gnats-admin::pr-field-alist))
+
+;; format control template for PR display
+(defconst gnats-admin::pr-long-format
+  '((4 Category Class) (35 Priority Severity) (60 Responsible) (70 State ) nl
+    4 "Synopsis:" Synopsis 
+    ))
+(defconst gnats-admin::pr-short-format 
+  '((4 Category Class) (35 Priority Severity) (60 Responsible) (70 State )))
+
+(defvar gnats-admin::pr-format gnats-admin::pr-short-format
+  "Format list for printing a pr.")
+
+;; hook that sets extent etc for Lucid emacs
+(defun gnats-admin::pr-display-hook (b e pr buf)
+  "Set extent around pr."
+  (let ((ext (make-extent b e buf)))
+    (set-extent-layout ext 'outside-margin)
+    (set-extent-property ext 'pr pr)
+    (set-extent-property ext 'start-open t)
+    (set-extent-property ext 'end-open t)
+    (set-extent-property ext 'highlight t)))
+
+;; gnats uses one face for each element of the enumerated fields,
+;; to give maximum flexibility in display.
+
+;;; symbol->(face foreground background) alist
+(defvar gnats-admin::face-color
+  '((critical "firebrick" nil)
+    (serious "goldenrod" nil)
+    (non-critical "blue3" nil)
+
+    (high "firebrick" nil) 
+    (medium "goldenrod" nil) 
+    (low "blue3" nil)
+
+    (open "firebrick" nil) 
+    (analyzed "goldenrod" nil) 
+    (suspended "turquoise" nil) 
+    (feedback "blue3" nil) 
+    (closed "ForestGreen" nil)
+    (closed* "HotPink" nil)
+    (closed? "blue3" nil)
+
+    (sw-bug nil nil) 
+    (doc-bug nil nil) 
+    (support nil nil) 
+    (change-request nil nil) 
+    (mistaken nil nil) 
+    (duplicate nil nil))
+  "Alist of font properties")
+
+(defun gnats-admin::field-display (pr fld buf)
+  "Display value field on specified stream."
+  (let
+      ((fv (gnats-admin::pr-get pr fld)))
+    (princ fv buf)))
+
+(defun gnats-admin::pr-print-func (f pr buf)
+  "Printer for pr.  Depends on free variable pr-did-indent."
+  (cond
+   ((eq 'nl f)
+    (princ "\n " buf)
+    (setq pr-did-indent t))
+   ((listp f)
+    (do
+	((fmt f (cdr fmt)))
+	((null fmt))
+      (gnats-admin::pr-print-func (car fmt) pr buf)))
+   ((numberp f)
+    (indent-to-column f 1)
+    (setq pr-did-indent t))
+   ((symbolp f)
+    (if (not pr-did-indent)
+	(princ " " buf))
+    (gnats-admin::field-display pr f buf)
+    (setq pr-did-indent nil))
+   ((stringp f)
+    (if (not pr-did-indent)
+	(princ " " buf))
+    (princ f buf)
+    (setq pr-did-indent nil))
+   )
+  t)
+  
+(defun gnats-admin::display-pr (pr &optional buf)
+  ;; always print the number first
+  (if (not buf)
+      (setq buf (current-buffer)))
+  (let 
+      ((b (point))
+       (buffer-read-only nil)
+       (pr-did-indent nil))
+    (princ (gnats-admin::pr-number pr) buf)
+    ;; now print according to the pr-format list
+    (do
+	((fmt gnats-admin::pr-format (cdr fmt)))
+	((null fmt))
+      (gnats-admin::pr-print-func (car fmt) pr buf))
+    (gnats-admin::pr-display-hook 
+     b 
+     (point) 
+     (gnats-admin::pr-number pr)
+     buf)
+    (newline)))
+
+(defun gnats-admin::pr-buffer-extent (pr)
+  "Find the extent for this PR."
+  (extent-at (1+ (gnats-admin::pr-buffer-begin pr))
+	     (gnats-admin::pr-buffer pr)
+	     'pr))
+  
+(defun gnats-admin::pr-reread (pr)
+  "Rerun query for one PR in the pr-list."
+  (let
+      ((num (gnats-admin::pr-number pr)))
+    (let
+	((repl (gnats-admin::query num)))
+      (if (and (listp repl)
+	       (= (length repl) 1)
+	       (= (gnats-admin::pr-get (car repl) 'Number) num))
+	  (gnats-admin::pr-replace! num (car repl))
+	(error "Query failed for pr %s" num)))
+    ))
+
+(defun gnats-admin::selection (loprs)
+  "Return the elements of LOPRS (list of PR's) which satify PREDicate."
+  (let ((pred gnats-admin::selector))
+    (if (not pred)
+	loprs
+      ;; else use the common-lisp loop appropriate to the type of loprs
+      (cond
+       ((arrayp loprs)
+	(loop
+	 for pr across loprs
+	 if (apply pred pr '())
+	   collect pr
+	 ))
+       ((listp loprs)
+	(loop
+	 for pr in loprs
+	 if (apply pred pr '())
+	   collect pr
+	 ))
+       (t
+	(error "Bad type for PR collection")))
+      )))
+
+(defun gnats-admin::selected? (pr)
+  "Test to see if one pr meets the selection criterion."
+  (or (null gnats-admin::selector)
+      (apply gnats-admin::selector pr '())))
+
+(defun gnats-admin::pr-replace! (oldpr newpr)
+  "Replace the old pr with the new one"
+  (if (not (consp newpr))
+      (error "Replacement pr must be a full PR value"))
+  (let ((pr-num (gnats-admin::pr-number newpr)))
+    (if (not (= (gnats-admin::pr-number oldpr)
+		pr-num))
+	(error "Cannot replace PR with one of different number"))
+    (if (< (length pr-list) pr-num)
+	(setq pr-list
+	      (vconcat pr-list (make-vector (- pr-num (length pr-list)) nil))))
+    (aset pr-list (1- pr-num) newpr)))
+
+(defvar gnats-admin::dirty-list nil
+  "List of PRs which may be out of date and need refreshing.")
+
+(defun gnats-admin:reset () 
+  "Reset the cached data for gnats-admin."
+  (setq pr-list nil)
+  (setq gnats-admin::dirty-list nil)	; it's everything now
+  )
+
+(defun gnats-admin:regret ()
+  "Create or edit the regression test for the current problem report."
+  (interactive)
+  (let* ((pr (gnats-admin::pr-at (point)))
+	 (num (gnats-admin::pr-number pr)))
+    (pr-regret num)))
+
+(defun gnats-admin:refresh (&optional force)
+  (interactive "P")
+  (if force
+      (gnats-admin:reset))
+  (if (not pr-list)
+      (progn
+	(gnats-admin::run-query))
+    (progn
+      (mapc (function (lambda (p) (gnats-admin::pr-reread p)))
+	    gnats-admin::dirty-list)))
+  (setq gnats-admin::dirty-list nil)
+  (set-buffer (gnats-admin::buffer))
+  (let ((standard-output (current-buffer))
+	(buffer-read-only nil)
+	(this-pr-num (gnats-admin::pr-num-at (point))))
+    ; is this overkill; could we save our extents unless force?
+    (if nil
+	(map-extents (function (lambda (ext data) (delete-extent ext) nil))
+		     (current-buffer)))
+    (delete-region (point-min) (point-max))
+    (beginning-of-buffer)
+    (message "Redisplay")
+    (mapc (function (lambda (pr) 
+		      (if (gnats-admin::selected? pr)
+			  (gnats-admin::display-pr pr))))
+	  pr-list)
+    (message nil)
+    ;; catch search errors in case the current pr no longer exists--
+    ;; if so, go to end of buffer
+    (condition-case err
+	(if (numberp this-pr-num)
+	    (gnats-admin:goto-pr this-pr-num))
+      (search-failed
+       (goto-char (point-max)))))
+  (run-hooks 'gnats-admin-refresh-hook)
+  t)
+
+(defun gnats-admin::pr-number (pr)
+  "Get the number of this pr -- which can be either a pr datum or a number."
+  (or (and (numberp pr) pr)
+      (gnats-admin::pr-get pr 'Number)))
+
+;; this must not depend on pr-at, because that depends on this.
+(defun gnats-admin::pr-num-at (pos)
+  "Find the pr number for the pr at POS"
+  (let*
+      ((ext (extent-at pos nil 'pr))
+       (pr-prop (and ext (extent-property ext 'pr))))
+    (if pr-prop
+	(gnats-admin::pr-number pr-prop)
+      (save-excursion
+	(goto-char pos)
+	(if (not (looking-at "^\\s-*[0-9]"))
+	    (backward-paragraph))
+	(if (looking-at "\\s-*[0-9]+[^0-9]")
+	    (read (match-string 0))
+	  nil)))))
+
+(defun gnats-admin::pr-by-number (num)
+  "Find the pr numbered N."
+  ;; first try the easy way
+  (let
+      ((pr (elt pr-list (1- num))))
+    (if (and pr (= num (gnats-admin::pr-number pr)))
+	pr
+      ;; easy way didnt work; scan the list
+      (loop
+       for prx across pr-list
+       if (= num (gnats-admin::pr-number prx))
+       return prx))))
+
+;; use face alist to set face colors
+(defun gnats-admin::setup-faces ()
+  "Set up the faces for gnats admin mode."
+  (mapc
+   (function (lambda (l) (make-face (car l))))
+   gnats-admin::face-color)
+  (if (memq (device-class) '(color grayscale))
+      (mapc
+       (function (lambda (l) 
+		   (if (cadr l)
+		       (set-face-foreground (car l) (cadr l)))
+		   (if (caddr l)
+		       (set-face-background (car l) (caddr l)))))
+       gnats-admin::face-color))
+  (setq font-lock-keywords gnats-admin::font-lock-keywords)
+  ;; this is too slow -- instead, do explicit fontification after modify
+  ; (turn-on-font-lock)
+  )
+
+(defvar gnats-admin::pr-mark-glyph nil
+  "Glyph used to mark the current PR in display.")
+
+(defun gnats-admin::buffer ()
+  "Find or create gnats admin buffer."
+  (or (get-buffer "*gnats*")
+      (let ((buf (get-buffer-create "*gnats*")))
+	(set-buffer buf)
+	(make-local-variable 'paragraph-start)
+	(make-local-variable 'paragraph-separate)
+	(setq paragraph-start "^\\(\\S-\\|[ \t\n]*$\\)")
+	(setq paragraph-separate "^[ \t\n]*$")
+	(setq buffer-read-only t)
+	(setq buffer-undo-list t)		; disable undo info
+	(setq gnats-admin::pr-mark-glyph (make-pixmap "target"))
+	(gnats-admin::setup-faces)
+	buf)
+      ))
+    
+(defun gnats-admin:pr (pr-or-num)
+  "If PR-OR-NUM is a pr, return it; if it's a number, 
+return the pr with that number."
+  (cond
+   ((numberp pr-or-num)
+    (gnats-admin::pr-by-number pr-or-num))
+   ((consp pr-or-num)
+    pr-or-num)
+   (t
+    (error "Not a valid PR: %s" pr-or-num))
+   ))
+    
+(defun gnats-admin::pr-at (pos)
+  "PR at POSITION"
+  (or
+   (let ((ext (extent-at pos nil 'pr)))
+     (if ext (extent-property ext 'pr)))
+   (gnats-admin::pr-num-at pos)))
+
+;; next should, ideally, run a 1-pr query then splice that into
+;; pr-list to update the current pr.
+;; however, there's a race condition with gnats; so put the edited
+;; pr on the dirty list to be inquired later.
+(defun gnats-admin:pr-edit ()
+  (interactive)
+  (let*
+      ((pr (gnats-admin::pr-at (point)))
+       (num (gnats-admin::pr-number pr))
+       (num-str (format "%d" num)))
+    (pr-edit num-str)
+    (setq gnats-admin::dirty-list (cons pr gnats-admin::dirty-list))))
+
+(defun gnats-admin:pr-view ()
+  (interactive)
+  (pr-view (format "%d" (gnats-admin::pr-num-at (point)))))
+(defun gnats-admin:pr-synopsis ()
+  (interactive)
+  (let*
+      ((pr (gnats-admin::pr-at (point)))
+       (syn (gnats-admin::pr-get pr 'Synopsis)))
+    (message "Synopsis: %s" syn)))
+
+(defun gnats-admin:pr-originator ()
+  (interactive)
+  (let*
+      ((pr (gnats-admin::pr-at (point)))
+       (syn (gnats-admin::pr-get pr 'Originator)))
+    (message "Originator: %s" syn)))
+
+(defun gnats-admin:pr-field (fld)
+  "Show any pr field FLD of current pr."
+  (interactive
+   (list (completing-read "Field: " gnats-admin::pr-field-alist
+			   nil t)))
+  (let*
+      ((pr (gnats-admin::pr-at (point)))
+       (val (gnats-admin::pr-get pr (intern fld))))
+    (message "%s: %s" fld val)))
+
+(defvar gnats-admin::current-pr nil "Current pr")
+(defun gnats-admin::highlight (pr)
+  "Hilight the current PR -- may unhilight the previous."
+  (condition-case err
+      (progn
+	(if gnats-admin::current-pr
+	    (highlight-extent
+	     (gnats-admin::pr-buffer-extent gnats-admin::current-pr)
+	     nil))
+	(highlight-extent (gnats-admin::pr-buffer-extent pr) t)
+	(setq gnats-admin::current-pr pr))
+    (error (setq gnats-admin::current-pr nil))))
+
+(defun gnats-admin::highlight-point ()
+  "Highlight the pr at point"
+  ;; make point visible
+  (or
+   (pos-visible-in-window-p)
+   (recenter '(t)))
+  (gnats-admin::highlight (gnats-admin::pr-at (point))))
+
+(defun gnats-admin:next ()
+  "Next pr"
+  (interactive)
+  (forward-paragraph)
+  (gnats-admin::highlight-point))
+(defun gnats-admin:prev ()
+  "Prev pr."
+  (interactive)
+  (backward-paragraph)
+  (gnats-admin::highlight-point))
+(defun gnats-admin:this ()
+  "Activate pr at point."
+  (interactive)
+  (end-of-line)
+  (backward-paragraph)
+  (gnats-admin::highlight-point))
+
+(defun gnats-admin:mouse-set (ev)
+  (interactive "e")
+  (mouse-set-point ev)
+  (gnats-admin::highlight-point))
+
+(defun gnats-admin:mouse-synopsis (ev)
+  (interactive "e")
+  (gnats-admin:mouse-set ev)
+  (gnats-admin:pr-synopsis))
+
+(defun gnats-admin:mouse-menu (ev)
+  (interactive "e")
+  (gnats-admin:mouse-set ev)
+  (popup-mode-menu))
+
+(defun gnats-admin:refresh-this-pr ()
+  "Reread and refresh the display of the current PR."
+  (interactive)
+  (let* ((pr (gnats-admin::pr-at (point)))
+	 (buffer-read-only nil)
+	 (b (gnats-admin::pr-buffer-begin pr))
+	 (e (gnats-admin::pr-buffer-end pr)))
+    (goto-char b)
+    (save-excursion
+      (gnats-admin::pr-buffer-delete pr)
+      (gnats-admin::pr-reread pr)
+      (gnats-admin::display-pr pr (current-buffer))
+      ;; had to dive pretty deep into font-lock to get this one...
+      (let ((font-lock-mode t))
+	(font-lock-after-change-function b e 1)))
+    (gnats-admin::highlight-point)))
+
+(defun gnats-admin:goto-pr (n)
+  "Make pr number N the current pr."
+  (interactive "nPR: ")
+  (goto-char (gnats-admin::pr-n-pos n))
+  (gnats-admin::highlight-point))
+
+(defun gnats-admin::pr-n-pos (n)
+  "Find the buffer position of pr numbered N in current buffer."
+  (save-excursion
+    (goto-char (point-min))
+    (let ((re (format "^%s\\s-" n)))
+      (re-search-forward re)
+      (point)
+      )))
+
+(defun gnats-admin::pr-buffer-delete (pr)
+  "Delete the display of the PR."
+  (let* ((b (gnats-admin::pr-buffer-begin pr))
+	 (e (gnats-admin::pr-buffer-end pr))
+	 (mbuf (gnats-admin::pr-buffer pr))
+	 (pr-ext (gnats-admin::pr-buffer-extent pr)))
+    (set-buffer mbuf)
+    (let ((buffer-read-only nil))
+      (delete-region b e)
+      (if (extentp pr-ext)
+	  (delete-extent pr-ext)))
+    ))
+
+(defun gnats-admin:quit ()
+  "Quit out of gnats admin mode."
+  (interactive)
+  (if (get-buffer "**gnats-query*")
+      (kill-buffer "**gnats-query*"))
+  (kill-buffer nil))
+  
+(defvar gnats-admin-mode-map nil "Key map for gnats admin mode.")
+
+(defun gnats-admin::setup-keymap ()
+  (if (not (keymapp gnats-admin-mode-map))
+      (progn
+	(setq gnats-admin-mode-map (make-keymap))
+	(suppress-keymap gnats-admin-mode-map)
+	(define-key gnats-admin-mode-map "e" 'gnats-admin:pr-edit)
+	(define-key gnats-admin-mode-map "v" 'gnats-admin:pr-view)
+	(define-key gnats-admin-mode-map "s" 'gnats-admin:pr-synopsis)
+	(define-key gnats-admin-mode-map "o" 'gnats-admin:pr-originator)
+	(define-key gnats-admin-mode-map "f" 'gnats-admin:pr-field)
+	(define-key gnats-admin-mode-map "\C-l" 'gnats-admin:refresh)
+	(define-key gnats-admin-mode-map "n" 'gnats-admin:next)
+	(define-key gnats-admin-mode-map "p" 'gnats-admin:prev)
+	(define-key gnats-admin-mode-map " " 'gnats-admin:this)
+	(define-key gnats-admin-mode-map "q" 'gnats-admin:quit)
+	(define-key gnats-admin-mode-map "g" 'gnats-admin:goto-pr)
+	(define-key gnats-admin-mode-map "r" 'gnats-admin:refresh-this-pr)
+	(define-key gnats-admin-mode-map "S" 'gnats-admin:edit-selection)
+	(define-key gnats-admin-mode-map "R" 'gnats-admin:regret)
+	(define-key gnats-admin-mode-map 'button1 'gnats-admin:mouse-set)
+	(define-key gnats-admin-mode-map 'button2 'gnats-admin:mouse-synopsis)
+	(define-key gnats-admin-mode-map 'button3 'gnats-admin:mouse-menu)
+	))
+  )
+
+(defun gnats-admin-mode ()
+  "Major mode for looking at a summary of gnats reports.
+Stomps to gnats admin buffer!
+Commands: \\{gnats-admin-mode-map}."
+  (interactive)
+  (switch-to-buffer (gnats-admin::buffer))
+  (setq major-mode 'gnats-admin-mode)
+  (setq mode-name "Gnats Admin")
+  (gnats-admin::setup-keymap)
+  (use-local-map gnats-admin-mode-map)
+  (setq mode-popup-menu gnats-admin::popup-menu)
+  (gnats-admin:refresh)
+  )
+
+(put 'gnats-admin-mode 'mode-class 'special)
+
+(defvar gnats-admin::pr-mark-glyph nil "marker for current PR.")
+           
+(defun gnats-admin::pr-buffer (pr)
+  "The buffer in which this pr is displayed."
+  (gnats-admin::buffer))
+
+(defun gnats-admin::pr-buffer-begin (pr)
+  "Return the position of beginning of this PR."
+  (let 
+      ((pr-num (gnats-admin::pr-number pr)))
+    (save-excursion 
+      (set-buffer (gnats-admin::pr-buffer pr))
+      ;; first try locally, then thru whole buffer
+      (or
+       (and (progn (backward-paragraph 1)
+		   (re-search-forward (format "^%d " pr-num) nil t))
+	    (progn 
+	      (beginning-of-line 1)
+	      (point)))
+       (and (progn (goto-char (point-min))
+		   (re-search-forward (format "^%d " pr-num) nil t))
+	    (progn 
+	      (beginning-of-line 1)
+	      (point)))))))
+
+(defun gnats-admin::pr-buffer-end (pr)
+  "Return the position of the end of the PR."
+  (save-excursion
+    (set-buffer (gnats-admin::pr-buffer pr))
+    (goto-char (gnats-admin::pr-buffer-begin pr))
+    (forward-paragraph 1)
+    (point)))
+
+(defun gnats-admin::unclosed (pr)
+  "A selector that chooses unclosed PR's."
+  (not (eq 'closed (gnats-admin::pr-get pr 'State))))
+
+(defvar gnats-admin:selexpr nil
+  "Selection expression -- see eval-selexpr.")
+
+(defun gnats-admin::eval-selexpr (pr)
+  "Evaluate the selection expression, in an environment with
+pr bound to the pr, and the field names bound to their value."
+  (let
+      ((Number (gnats-admin::pr-get pr 'Number))
+       (Category (gnats-admin::pr-get pr 'Category))
+       (Synopsis (gnats-admin::pr-get pr 'Synopsis))
+       (Confidential (gnats-admin::pr-get pr 'Confidential))
+       (Severity (gnats-admin::pr-get pr 'Severity))
+       (Priority (gnats-admin::pr-get pr 'Priority))
+       (Responsible (gnats-admin::pr-get pr 'Responsible))
+       (State (gnats-admin::pr-get pr 'State))
+       (Class (gnats-admin::pr-get pr 'Class))
+       (Submitter-Id (gnats-admin::pr-get pr 'Submitter-Id))
+       (Arrival-Date (gnats-admin::pr-get pr 'Arrival-Date))
+       (Originator (gnats-admin::pr-get pr 'Originator))
+       (Release (gnats-admin::pr-get pr 'Release)))
+    (or (not gnats-admin:selexpr)
+	(eval gnats-admin:selexpr))))
+
+(defun gnats-admin:edit-selection ()
+  "Edit the selection criteria."
+  (interactive)
+  (setq gnats-admin::selector 'gnats-admin::eval-selexpr)
+  (setq gnats-admin:selexpr 
+	(edit-expr gnats-admin:selexpr
+";; Selection expression.  This is evaluated with Number, Category, Synopsis,
+;; Confidential, Severity, Priority, Responsible, State, Class, Submitter-Id,
+;; Arrival-Date, Originator, and Release set from the PR; if it's a non-null
+;; expression that evaluates true, then that record is displayed.  Free-form
+;; fields are strings, others are symbols or other atoms.
+"
+))
+  (gnats-admin:refresh))
+;;;; -*-emacs-lisp-*-
+;;;; EMACS interface for GNATS.
+;;;;  Copyright (C) 1992, 1993, 1994, 1995, 1996 Free Software Foundation, Inc.
+;;;;  Contributed by Brendan Kehoe (brendan@cygnus.com)
+;;;;   based on an original version by Heinz G. Seidl (hgs@ide.com).
+;;;;
+;;;; This file is part of GNU GNATS.
+;;;;
+;;;; GNU GNATS 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, or (at your option)
+;;;; any later version.
+;;;;
+;;;; GNU GNATS 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.
+;;;;
+;;;; You should have received a copy of the GNU General Public License
+;;;; along with GNU GNATS; see the file COPYING.  If not, write to
+;;;; the Free Software Foundation, 59 Temple Place - Suite 330,
+;;;; Boston, MA 02111, USA.  */
+
+;;;; This file provides `edit-pr', `view-pr' `query-pr', for changing and
+;;;; searching problem reports that are part of the GNATS database.  See the
+;;;; gnats(1) man page or the GNATS documentation for further information.
+
+(provide 'gnats)
+(require 'send-pr)			;Shared stuff defined there
+
+;;;;---------------------------------------------------------------------------
+;;;; Customization: put the following forms into your default.el file
+;;;; (or into your .emacs) and the whole file into your EMACS library.
+;;;;---------------------------------------------------------------------------
+
+;(autoload 'edit-pr "gnats"
+;	  	  "Command to edit a problem report." t)
+
+;(autoload 'view-pr "gnats"
+;	  	  "Command to view a problem report." t)
+
+;(autoload 'gnats-mode "gnats"
+;	  "Major mode for editing of problem reports." t)
+
+;(autoload 'query-pr "gnats"
+;	  	  "Command to query information about problem reports." t)
+
+;(autoload 'summ-pr "gnats"
+;  "Command to display a summary listing of problem reports." t)
+
+;;;;---------------------------------------------------------------------------
+;;;; To reply by mail within gnats-mode
+;;;;---------------------------------------------------------------------------
+
+(defvar gnats-mailer 'mail
+  "*Specifiles either `mail' or `mhe' as mailer for GNATS")
+  
+;; Provides mail reply and mail other window command using usual mail
+;; interface and mh-e interface.
+;;
+;; To use MAIL: set the variable
+;; `gnats-mailer' to `mail'
+;;
+;; To use MH-E: set the variable 
+;; `gnats-mailer' to  `mhe'
+
+(autoload 'mail "sendmail")
+(autoload 'mail-other-window "sendmail")
+(autoload 'mail-fetch-field "mail-utils")
+(autoload 'rmail-dont-reply-to "mail-utils")
+(autoload 'mail-strip-quoted-names "mail-utils")
+(autoload 'mail-send "sendmail")
+(autoload 'mail-send-and-exit "sendmail")
+
+(autoload 'mh-send "mh-e")
+(autoload 'mh-send-other-window "mh-e")
+(autoload 'mh-find-path "mh-e")
+(autoload 'mh-get-field "mh-e")
+(autoload 'mh-insert-fields "mh-e")
+(defvar mh-show-buffer nil)
+(defvar mh-sent-from-folder nil)
+(defvar mh-sent-from-msg nil)
+
+;;; User options
+
+(defvar gnats:keep-edited-buffers t
+  "*If non-nil, then PR buffers are kept with distinct names after
+editing.  Otherwise, only the the most recently edited PR is kept.")
+
+(defvar gnats:keep-sent-messages 1
+  "*Non-nil value N causes GNATS to keep the last N messages sent from GNATS.
+A value of 0 or nil causes GNATS never to keep such buffers.  A value of t
+causes GNATS to keep all such buffers.")
+
+(defvar gnats:network-server nil
+  "*If non-nil, names the GNATS network server for remote queries and editing.")
+
+(defvar gnats:run-in-background t
+  "*If non-nil, make GNATS programs run in the background allowing the emacs to continue to other things.")
+
+(defvar gnats:bury-edited-prs t
+  "*If non-nil, then PR buffers are buried after editing.  Otherwise, they are left at the top of the buffer list.")
+
+;;; emacs 19 uses compile-internal, emacs 18 uses compile1
+(if gnats::emacs-19p
+    (autoload 'compile-internal "compile")
+  (autoload 'compile1 "compile")
+  (fset 'compile-internal 'compile1))
+
+;;; Misc constants.
+
+;;(defvar gnats:root "/usr/share/gnats/gnats-db"
+;;  "*The top of the tree containing the GNATS database.")
+
+(defvar gnats:libdir (or (gnats::get-config "LIBDIR") "/usr/lib")
+  "*Where the `gnats' subdirectory lives for things like pr-edit.")
+
+(defvar gnats:addr (or (gnats::get-config "GNATS_ADDR") "bugs")
+  "*Where bug reports are sent.")
+
+(defvar gnats::version
+  (concat "Version " (or (gnats::get-config "VERSION") "3.101")))
+
+(defvar gnats::diffopt (or (gnats::get-config "DIFFOPT") "-u")
+  "How to get human-friendly output from diff(1).")
+
+(defvar gnats::categories nil
+  "List of GNATS categories; computed at runtime.")
+
+(defvar gnats::responsibles nil
+  "List of GNATS responsibles; Computed at runtime.")
+
+(defvar gnats::submitters nil
+  "List of GNATS submitters; Computed at runtime.")
+
+;;;###autoload
+(defvar gnats::mode-name nil
+  "Name of the GNATS mode.")
+
+(defconst gnats::err-buffer "*gnats-error*"
+  "Name of the temporary buffer, where gnats error messages appear.")
+
+;;(defconst gnats::indent 17 "Indent for formatting the value.")
+
+(defvar gnats:::pr-locked nil
+  "Buffer local flag representing whether the associated pr is locked.")
+
+(defvar gnats:::pr-errors nil
+  "Buffer local buffer holding any errors from attempting to file this pr.")
+
+(defvar gnats:::buffer-pr nil
+  "Buffer local name of this pr.")
+
+(defvar gnats:::current-pr nil
+  "Buffer local value of the current pr.")
+
+(defvar gnats:::do-file-pr nil
+  "Buffer local value; if T, file the current pr.")
+
+(defvar gnats:::force nil
+  "Buffer local value; if T, ignore errors unlocking the current pr.")
+
+(defvar gnats:::pr-buffer nil
+  "Buffer local value of the buffer containing the pr.")
+
+(defvar gnats:::audit-trail nil
+  "Buffer local audit trail for the current pr.")
+
+(defvar gnats:::backupname nil
+  "Buffer local name of the backup file for this pr.")
+
+(defvar gnats:::start-of-PR-fields nil
+  "Buffer position of the beginning of the PR fields.")
+
+(defvar gnats:::newfile nil
+  "File used to store the results of npr-edit.")
+
+(defvar gnats:::query-pr "query-pr"
+  "The program name used to query problem reports.")
+
+(defvar gnats:::nquery-pr "nquery-pr"
+  "The program name used to query problem reports over the network.")
+
+(defvar gnats:::query-regexp "n?query-pr:"
+  "The regular expression to use to recognize a message from the query program.")
+
+;; For example:
+;;  (setq gnats:::types '( ( "Games" ( "/gnats/games"  "/usr/gamesdb/H-sun4/lib ")
+;;                        ( "Tools" ( "/usr/toolsdb" "/usr/local/lib" ))))
+(defvar gnats:::types nil
+  "Alist of each type of GNATS database and its root and libdir settings.")
+
+(defconst gnats::fields
+  (let (fields)
+    (setq
+     fields
+     ;; Duplicate send-pr::fields, don't just include it.
+     ;; is there a better way than this?
+     (append (read (prin1-to-string send-pr::fields))
+	     '(("Arrival-Date" nil nil text)
+	       ("Customer-Id")
+	       ("Number" nil nil number)
+	       ("Responsible" gnats::set-responsibles nil enum
+		gnats::update-audit-trail)
+	       ("State"
+		(("open") ("analyzed") ("feedback") ("suspended") ("closed"))
+		(lambda (x) (or (cdr (assoc x gnats::state-following)) ""))
+		enum gnats::update-audit-trail))))
+    ;; (setf (second (assoc "Category" fields)) 'gnats::set-categories)
+    (setcar (cdr (assoc "Category" fields)) 'gnats::set-categories)
+    (setcdr (nthcdr 3 (assoc "Category" fields))
+	    '(gnats::update-responsible))
+    (setcar (cdr (assoc "Class" fields))
+	    '(("sw-bug") ("doc-bug") ("change-request") ("support")
+	      ("mistaken") ("duplicate")))
+    (setcdr (assoc "Submitter-Id" fields) '(gnats::set-submitters t enum))
+    (setcdr (assoc "Customer-Id" fields) (cdr (assoc "Submitter-Id" fields)))
+    fields)
+  "AList of one-line PR fields and their possible values.")
+
+(defconst gnats::state-following 
+  '(("open"      . "analyzed")
+    ("analyzed"  . "feedback")
+    ("feedback"  . "closed")
+    ("suspended" . "analyzed"))
+  "A list of states and possible following states (does not describe all
+possibilities).")
+
+(defvar gnats::query-pr-history nil
+  "Past arguments passed to the query-pr program.")
+
+(defvar gnats::tmpdir (or (getenv "TMPDIR") "/tmp/")
+  "Directory to use for temporary files.")
+
+;;;;---------------------------------------------------------------------------
+;;;; hooks
+;;;;---------------------------------------------------------------------------
+
+;; we define it here in case it's not defined
+(or (boundp 'text-mode-hook) (setq text-mode-hook nil))
+(defvar gnats-mode-hook text-mode-hook "Called when gnats mode is switched on.")
+
+;;;;---------------------------------------------------------------------------
+;;;; Error conditions
+;;;;---------------------------------------------------------------------------
+
+(put 'gnats::error 'error-conditions '(error gnats::error))
+(put 'gnats::error 'error-message "GNATS error")
+
+;; pr-edit --check was unhappy
+(put 'gnats::invalid-fields 'error-conditions
+     '(error gnats::error gnats::invalid-fields))
+(put 'gnats::invalid-fields 'error-message "invalid fields in PR")
+(put 'gnats::invalid-date 'error-conditions
+     '(error gnats::error gnats::invalid-date))
+(put 'gnats::invalid-date 'error-message "invalid date value")
+
+;; pr-addr couldn't find an appropriate address
+(put 'gnats::invalid-name 'error-conditions
+     '(error gnats::error gnats::invalid-name))
+(put 'gnats::invalid-name 'error-message "could not find the requested address")
+
+;; what pr?
+(put 'gnats::no-such-pr 'error-conditions '(error gnats::error gnats::no-such-pr))
+(put 'gnats::no-such-pr 'error-message "PR does not exist")
+
+;;
+(put 'gnats::no-such-category 'error-conditions
+     '(error gnats::error gnats::no-such-category))
+(put 'gnats::no-such-category 'error-message "No such category")
+
+;; there is no lock on that pr
+(put 'gnats::pr-not-locked 'error-conditions
+     '(error gnats::error gnats::pr-not-locked))
+(put 'gnats::pr-not-locked 'error-message "No one is locking the PR")
+
+;; there is a lock on that pr
+(put 'gnats::locked-pr 'error-conditions '(error gnats::error gnats::locked-pr))
+(put 'gnats::locked-pr 'error-message "PR locked by")
+
+;; GNATS is locked
+(put 'gnats::locked 'error-conditions '(error gnats::error gnats::locked))
+(put 'gnats::locked 'error-message "GNATS is locked by another process---try again later.")
+
+;; We can't lock GNATS.
+(put 'gnats::cannot-lock 'error-conditions '(error gnats::error gnats::locked))
+(put 'gnats::cannot-lock 'error-message "cannot lock GNATS; try again later.")
+
+;;;;---------------------------------------------------------------------------
+;;;; GNATS mode
+;;;;---------------------------------------------------------------------------
+
+(defvar gnats-mode-map
+  (let ((map (make-sparse-keymap)))
+    (define-key map "\M-n" 'gnats:next-field)
+    (define-key map "\M-p" 'gnats:previous-field)
+    (define-key map "\C-\M-b" 'gnats:backward-field)
+    (define-key map "\C-\M-f" 'gnats:forward-field)
+    (define-key map "\C-c\C-a" 'gnats:mail-reply)
+    (define-key map "\C-c\C-c" 'gnats:submit-pr)
+    (define-key map "\C-c\C-e" 'gnats:edit-pr)
+    (define-key map "\C-c\C-f" 'gnats:change-field)
+    (define-key map "\C-c\C-m" 'gnats:mail-other-window)
+    (define-key map "\C-c\C-q" 'gnats:unlock-buffer-force)
+    (define-key map "\C-c\C-r" 'gnats:responsible-change-from-to)
+    (define-key map "\C-c\C-s" 'gnats:state-change-from-to)
+    (define-key map "\C-c\C-t" 'gnats:category-change-from-to)
+    (define-key map "\C-c\C-u" 'gnats:unlock-pr)
+    (or gnats::emacs-19p
+	(define-key map "\C-xk" 'gnats:kill-buffer))
+    map)
+  "Keymap for gnats mode.")
+
+(defsubst gnats::get-header (field)
+  "Fetch the contents of mail header FIELD."
+  (funcall (nth 4 (assoc gnats-mailer gnats::mail-functions)) field))
+
+(defun gnats:submit-pr ()
+  "Save the current PR into the database and unlock it.
+
+This function has no effect if the PR is not locked.
+
+Three cases:
+      State change
+      Responsibility change
+      Other change (only interesting if the PR was changed by somebody 
+                    other than the Reponsible person)
+
+State changes are sent to the originator
+Responsibility changes are sent to the new responsible person
+Other changes are sent to the responsible person."
+  ;;
+  (interactive)
+  (cond ((not (eq major-mode 'gnats:gnats-mode))
+	 (error "edit-pr: not in GNATS mode.")) 
+	(gnats:::pr-locked
+	 (gnats::check-pr-background t)
+	 (if gnats:run-in-background (bury-buffer)))
+	  ;; If not locked, do nothing
+	(t
+	 (message "edit-pr: PR not locked."))))
+
+;;;###autoload
+(setq gnats::mode-name 'gnats:gnats-mode)
+
+(defun gnats::rename-buffer ()
+  (let ((category (gnats::field-contents "Category"))
+	(number   (gnats::field-contents "Number"))
+	buf)
+    (setq gnats:::buffer-pr (format "%s/%s" category number))
+    (and (setq buf (get-buffer gnats:::buffer-pr))
+	 (save-excursion
+	   (set-buffer buf)
+	   (set-buffer-modified-p nil)
+	   (kill-buffer buf)))
+    (rename-buffer gnats:::buffer-pr)))
+
+;; FIXME allow re-lock of unlocked PR
+;; FIXME too many assumptions -- make more independent of edit-pr
+;;;###autoload
+(fset 'gnats-mode gnats::mode-name)
+;;;###autoload
+(defun gnats:gnats-mode ()
+  "Major mode for editing problem reports.
+For information about the form see gnats(1) and pr_form(5).
+
+When you are finished editing the buffer, type \\[gnats:submit-pr] to commit
+your changes to the PR database.  To abort the edit, type
+\\[gnats:unlock-buffer].
+
+Special commands:
+\\{gnats-mode-map}
+Turning on gnats-mode calls the value of the variable gnats-mode-hook,
+if it is not nil."
+  (gnats::patch-exec-path)		;Why is this necessary? --jason
+  (gnats::set-categories)
+  (gnats::set-responsibles)
+  (gnats::set-submitters)
+  (put gnats::mode-name 'mode-class 'special)
+  (kill-all-local-variables)
+  (setq major-mode gnats::mode-name)
+  (setq mode-name "gnats")
+  (use-local-map gnats-mode-map)
+  (set-syntax-table text-mode-syntax-table)
+  (setq local-abbrev-table text-mode-abbrev-table)
+  (make-local-variable 'gnats:::start-of-PR-fields)
+  (make-local-variable 'gnats:::pr-errors)
+  (make-local-variable 'gnats:::buffer-pr)
+  (gnats::rename-buffer)
+
+  ;; we do this in gnats:edit-pr for the network version
+  (if (not gnats:network-server)
+      (progn
+	(setq gnats:::backupname (gnats::make-temp-name))
+	(copy-file (format "%s/%s" gnats:root gnats:::buffer-pr)
+		   gnats:::backupname)))
+  
+  ;; edit-pr locks it for us
+  (make-local-variable 'gnats:::pr-locked)
+  (setq gnats:::pr-locked t)
+
+  (cond (gnats::emacs-19p
+	 (make-local-variable 'kill-buffer-hook)
+	 (add-hook 'kill-buffer-hook 'gnats::kill-buffer-hook)))
+
+  ; If they do C-x C-c, unlock all of the PRs they've edited.
+  (if (fboundp 'add-hook)
+      (add-hook 'kill-emacs-hook 'gnats::unlock-all-buffers)
+    (setq kill-emacs-hook 'gnats::unlock-all-buffers))
+
+  (make-local-variable 'paragraph-separate)
+  (setq paragraph-separate (concat (default-value 'paragraph-separate)
+				    "\\|" gnats::keyword "$"))
+  (make-local-variable 'paragraph-start)
+  (setq paragraph-start (concat (default-value 'paragraph-start)
+				"\\|" gnats::keyword))
+  (make-local-variable 'gnats:::audit-trail)
+  (goto-char (point-min))
+  (search-forward "\n>Number:")
+  (beginning-of-line)
+  (setq gnats:::start-of-PR-fields (point-marker))
+  (run-hooks 'gnats-mode-hook))
+
+;;;;---------------------------------------------------------------------------
+;;;; Mail customization
+;;;;---------------------------------------------------------------------------
+
+(or (string-match mail-yank-ignored-headers "X-mode:")
+    (setq mail-yank-ignored-headers 
+	  (concat "^X-mode:" "\\|" mail-yank-ignored-headers)))
+
+(defconst gnats::mail-functions
+  '((mail gnats::mail-other-window-using-mail
+	  gnats::mail-reply-using-mail
+	  gnats::mail-PR-changed-mail-setup
+	  gnats::get-header-using-mail-fetch-field)
+    (mhe  gnats::mail-other-window-using-mhe
+	  gnats::mail-reply-using-mhe
+	  gnats::mail-PR-changed-mhe-setup
+	  gnats::get-header-using-mhe))
+  "An association list of mailers and the functions that use them.
+The functions are supposed to implement, respectively:
+gnats::mail-other-window
+gnats::mail-reply
+gnats::mail-PR-changed-setup
+gnats::get-header")
+
+;;;;---------------------------------------------------------------------------
+;;;; Toplevel functions and vars, to reply with mail within gnats-mode
+;;;;---------------------------------------------------------------------------
+
+(defun gnats:mail-other-window ()
+  "Compose mail in other window.
+Customize the variable `gnats-mailer' to use another mailer."
+  ;;
+  (interactive)
+  (funcall (nth 1 (assoc gnats-mailer gnats::mail-functions))))
+
+(defun gnats:mail-reply (&optional just-sender)
+  "Reply mail to PR Originator.
+Customize the variable `gnats-mailer' to use another mailer.
+If optional argument JUST-SENDER is non-nil, send response only to
+original submitter of problem report."
+  ;;
+  (interactive "P")
+  (funcall (nth 2 (assoc gnats-mailer gnats::mail-functions)) just-sender))
+
+;;;; common (and suppport) functions 
+
+(defun gnats::isme (addr)
+  (setq addr (mail-strip-quoted-names addr))
+  (or (string= addr (user-login-name))
+      (string= addr (concat (user-login-name) "@" (system-name)))))
+  
+(defsubst gnats::mail-PR-changed-setup (to subject cc buffer)
+  (funcall (nth 3 (assoc gnats-mailer gnats::mail-functions))
+	   to subject cc buffer))
+
+(defun gnats::mail-PR-changed-mail-setup (to subject cc buffer)
+  (or (gnats::vmish-mail
+       (format "notification of changes to PR %s" gnats:::buffer-pr)
+       nil to subject nil cc buffer)
+      (error "Submit aborted; PR is still locked.")))
+
+(defun gnats::mail-PR-changed-mhe-setup (to subject cc buffer)
+  (let ((config (current-window-configuration))
+	(pop-up-windows t)
+	draft)
+    (mh-find-path)
+    (let ((pop-up-windows t))
+      (mh-send-sub to (or cc "") subject config))
+    (switch-to-buffer (current-buffer))
+    (setq mh-sent-from-folder buffer
+	  mh-sent-from-msg 1
+	  mh-show-buffer buffer)))
+
+(defun gnats::mail-PR-changed (user responsible resp-change state-change notify)
+  "- Send mail to the responsible person if the PR has been changed
+  by someone else
+- Send mail to the originator when the state is changed.
+- Send mail to old and new responsible people when the responsibility is
+  transferred.
+  `resp-change' is the list (old-resp new-resp start end)
+- Send mail to any other parties in NOTIFY."
+  ;; This function is really ugly !
+  ;;
+  (let ((to nil)
+	(cc nil)
+	(subn nil) (subm nil)
+	(subject (gnats::get-reply-subject))
+	(buffer  (current-buffer))
+	(pr-change (not (or resp-change state-change)))
+	(pr-backupname gnats:::backupname)
+	)
+    ;; Here we find out where to send the mail to
+    (let (to-resp to-new-resp to-submitter to-bugs resp-addr new-resp-addr)
+      (if pr-change (setq to-resp t to-bugs t)
+	(if resp-change (setq to-resp t to-new-resp t))
+	(if state-change (setq to-submitter t to-resp t)))
+      (cond (to-new-resp
+	     (setq new-resp-addr (gnats::pr-addr (car resp-change)))
+	     (if (gnats::isme new-resp-addr)
+		 (setq to-new-resp nil))))
+      (cond (to-resp
+	     (setq resp-addr (gnats::pr-addr responsible))
+	     (if (gnats::isme resp-addr)
+		 (setq to-resp nil))))
+      (cond (to-submitter
+	     (setq cc to)
+	     (setq to (list (gnats::get-reply-to)))))
+      (if to-resp (gnats::push resp-addr to))
+      (if to-new-resp (gnats::push new-resp-addr to))
+      (setq subm (or (gnats::field-contents "Customer-Id")
+		     (gnats::field-contents "Submitter-Id")))
+      (if subm
+	  (progn
+	    (setq subn (nth 5 (assoc subm gnats::submitters)))
+	    (if (not (string= subn ""))
+		(gnats::push subn cc))))
+      (if to-bugs (gnats::push gnats:addr cc))
+      (if notify (gnats::push notify cc))
+      (setq to (mapconcat 'identity to ", ")
+	    cc (mapconcat 'identity cc ", "))
+      (if (string= cc "") (setq cc nil)))
+    (gnats::mail-PR-changed-setup to subject cc buffer)
+    ;; now we assume that the current buffer is the mail buffer
+    (goto-char (point-max))
+    (if pr-change
+	(progn
+	  (insert 
+	   (format "\n\t`%s' made changes to this PR.\n\n" (user-full-name)))
+	  (if (and pr-backupname (file-readable-p pr-backupname))
+	      (let ((file (gnats::make-temp-name))
+		    (default-directory (gnats::find-safe-default-directory)))
+		(save-excursion
+		  (set-buffer buffer)
+		  (write-region (point-min) (point-max) file))
+		(call-process "diff" nil t t gnats::diffopt
+			      pr-backupname file)
+		(delete-file file))))
+      (if resp-change
+	  (progn
+	    (insert (format "\n\t`%s' changed the responsibility to `%s'.\n" 
+			    (user-full-name) responsible))
+	    (insert-buffer-substring buffer 
+				     (nth 2 resp-change) 
+				     (nth 3 resp-change)))
+	(if state-change
+	    (progn
+	      (insert (format "\n\t`%s' changed the state to `%s'.\n" 
+			      (user-full-name) (nth 1 state-change)))
+	      (insert-buffer-substring buffer 
+				       (nth 2 state-change)
+				       (nth 3 state-change))))))
+    ))
+
+(defsubst gnats::bm (num)
+  (buffer-substring (match-beginning num) (match-end num)))
+
+(defun gnats::real-pr-addr (name)
+  (if (zerop (length name))
+      nil
+    (let ((buf (generate-new-buffer gnats::err-buffer)))
+      (unwind-protect
+	  (save-excursion
+	    (let ((default-directory (gnats::find-safe-default-directory)))
+	      (call-process (format "%s/gnats/pr-addr" gnats:libdir)
+			    nil buf nil "-F" name))
+	    (set-buffer buf)
+	    (goto-char (point-min))
+	    (cond ((looking-at "pr-addr: could not find the requested address")
+		   nil)
+		  ((looking-at "^\\([^:]*\\):\\([^:]*\\):\\([^:]*\\)\n")
+		   (list (gnats::bm 1) (gnats::bm 2) (gnats::bm 3)))
+		  (t (signal 'gnats::error
+			     (list (buffer-substring (point-min)
+						     (1- (point-max))))))))
+	(kill-buffer buf)))))
+
+(defun gnats::pr-addr (name)
+  "Find the e-mail address corresponding to maintainer NAME."
+  (let (entry addr)
+    (or (setq entry (assoc name gnats::responsibles))
+	(and (setq entry (gnats::real-pr-addr name))
+	     (gnats::push entry gnats::responsibles))
+	(signal 'gnats::invalid-name (list name)))
+    (setq addr (if (zerop (length (nth 2 entry)))
+		   name
+		 (nth 2 entry)))
+    (if (zerop (length (nth 1 entry)))
+	addr
+      (concat (nth 1 entry) " <" addr ">"))))
+
+(defun gnats::get-header-using-mail-fetch-field (field)
+  (save-excursion
+    (save-restriction
+      (goto-char (point-min))
+      (re-search-forward "^$" nil 'move)
+      (narrow-to-region (point-min) (point))
+      (mail-fetch-field field))))
+      
+(defun gnats::get-header-using-mhe (field)
+  (save-excursion
+    (let ((ret (mh-get-field (concat field ":"))))
+      (if (string= ret "")
+	  nil
+	ret))))
+
+(defun gnats::get-reply-to ()
+  (or (gnats::get-header "Reply-To")
+      (gnats::get-header "From")))
+
+(defun gnats::get-reply-subject ()
+  (save-excursion
+    (save-restriction
+      (widen)
+      (let ((category (gnats::field-contents "Category"))
+	    (number   (gnats::field-contents "Number"))
+	    (synopsis (gnats::field-contents "Synopsis" 0))
+	    (subject))
+	(goto-char (point-min))
+	(narrow-to-region (point-min)
+			  (progn (search-forward "\n\n" nil 'move)
+				 (point-marker)))
+	(setq subject (mail-fetch-field "subject"))
+	(if (and synopsis (not (equal synopsis "")))
+	    (format "Re: %s/%s: %s" category number synopsis)
+	  (format "Re: %s/%s: %s" category number subject))))))
+
+(defun gnats::make-in-reply-to-field (from date msg-id)
+  (concat
+   (substring from 0 (string-match "  *at \\|  *@ \\| *(\\| *<" from))
+   "'s message of " date
+   (if (not (equal msg-id ""))
+       (concat "\n             " msg-id)
+     "")))
+
+;;;; Send mail using sendmail mail mode.
+
+(defun gnats::mail-reply-using-mail (just-sender)
+  ;;
+   "Mail a reply to the originator of the PR.
+Normally include CC: to all other recipients of original message;
+argument means ignore them.
+While composing the reply, use \\[mail-yank-original] to yank the
+original message into it."
+   ;;
+   (let (from cc subject date to reply-to msg-id)
+     (save-excursion
+       (save-restriction
+	 (widen)
+	 (narrow-to-region (point-min) (progn (goto-char (point-min))
+					      (search-forward "\n\n")
+					      (- (point) 1)))
+	 (setq from       (mail-fetch-field "from" nil t)
+	       subject    (gnats::get-reply-subject)
+	       reply-to   (or (mail-fetch-field "reply-to" nil t)
+			      from)
+	       date       (mail-fetch-field "date" nil t)
+	       cc         (cond (just-sender nil)
+				(t (mail-fetch-field "cc" nil t)))
+	       to         (or (mail-fetch-field "to" nil t)
+			      (mail-fetch-field "apparently-to" nil t)
+			      "")
+	       msg-id     (mail-fetch-field "message-id")
+	       )))
+     (gnats::vmish-mail-other-window
+      (format "reply to PR %s" gnats:::buffer-pr)
+      nil (mail-strip-quoted-names reply-to) subject
+      (gnats::make-in-reply-to-field from date msg-id)
+      (if just-sender
+	  nil
+	(let* ((cc-list (rmail-dont-reply-to (mail-strip-quoted-names
+					      (if (null cc) to 
+						(concat to ", " cc))))))
+	  (if (string= cc-list "") nil cc-list)))
+      (current-buffer))))
+
+(defun gnats::mail-other-window-using-mail ()
+  "Send mail in another window.
+While composing the message, use \\[mail-yank-original] to yank the
+original message into it."
+  (gnats::vmish-mail-other-window 
+   (format "mail regarding PR %s" gnats:::buffer-pr)
+   nil nil (gnats::get-reply-subject) nil nil (current-buffer)))
+
+;; This must be done in two toplevel forms because of a 19.19 byte-compiler
+;; bug.
+(defun gnats::generate-new-buffer-name (prefix)
+  (let ((name prefix) (n 1))
+    (while (get-buffer name)
+      (setq name (format "%s<%d>" prefix n))
+      (setq n (1+ n)))
+    name))
+
+(if (fboundp 'generate-new-buffer-name)
+    (fset 'gnats::generate-new-buffer-name 'generate-new-buffer-name))
+
+(defvar gnats::kept-mail-buffers nil
+  "Sent mail buffers waiting to be killed.")
+
+(defun gnats::vmish-rename-after-send ()
+  (or (string-match "^sent " (buffer-name))
+      (rename-buffer (gnats::generate-new-buffer-name
+		      (format "sent %s" (buffer-name)))))
+
+  ;; Mostly lifted from vm-reply.el 5.35
+  (setq gnats::kept-mail-buffers
+	(cons (current-buffer) gnats::kept-mail-buffers))
+  (if (not (eq gnats:keep-sent-messages t))
+      (let ((extras (nthcdr (or gnats:keep-sent-messages 0)
+			    gnats::kept-mail-buffers)))
+	(mapcar (function (lambda (b) (and (buffer-name b) (kill-buffer b))))
+		extras)
+	(and gnats::kept-mail-buffers extras
+	     (setcdr (memq (car extras) gnats::kept-mail-buffers) nil)))))
+
+(if gnats::emacs-19p
+    (defun gnats::vmish-mail-bindings ())
+  (defun gnats::vmish-mail-send ()
+    (interactive)
+    (gnats::vmish-rename-after-send)
+    (mail-send))
+  (defun gnats::vmish-mail-send-and-exit (arg)
+    (interactive "P")
+    (gnats::vmish-rename-after-send)
+    (mail-send-and-exit arg))
+  (defun gnats::vmish-mail-bindings ()
+    (use-local-map (copy-keymap (current-local-map)))
+    (local-set-key "\C-c\C-s" 'gnats::vmish-mail-send)
+    (local-set-key "\C-c\C-c" 'gnats::vmish-mail-send-and-exit))
+  (defun string-to-number (str) (string-to-int str)))
+
+;; ignore 'free variable' warnings about buf.
+(defsubst gnats::vmish-rename-mail-buffer (buf)
+  (save-excursion
+    (set-buffer buf)
+    (setq buf (gnats::generate-new-buffer-name "*not mail*"))
+    (rename-buffer buf)))
+
+;; ignore 'free variable' warnings about buf.
+(defsubst gnats::vmish-restore-mail-buffer (buf)
+  (save-excursion
+    (let ((mbuf (get-buffer "*mail*")))
+      (cond (mbuf			;maybe left over from m-o-w failure
+	     (set-buffer mbuf)
+	     (set-buffer-modified-p nil)
+	     (kill-buffer mbuf))))
+    (cond (buf
+	   (set-buffer buf)
+	   (rename-buffer "*mail*")))))
+
+(defun gnats::vmish-mail-other-window
+  (&optional buffer-name noerase to subject in-reply-to cc replybuffer actions)
+  (let ((buf (get-buffer "*mail*")))
+    (if buf (gnats::vmish-rename-mail-buffer buf))
+    (or buffer-name (setq buffer-name "GNATS mail"))
+    (unwind-protect
+	(prog1
+	    (if gnats::emacs-19p
+		(mail-other-window
+		 noerase to subject in-reply-to cc replybuffer
+		 (cons '(gnats::vmish-rename-after-send) actions))
+	      (prog1
+		  (mail-other-window noerase to subject in-reply-to
+				     cc replybuffer)
+		(gnats::vmish-mail-bindings)))
+	  (rename-buffer (gnats::generate-new-buffer-name buffer-name)))
+      (gnats::vmish-restore-mail-buffer buf))))
+
+(defun gnats::vmish-mail
+  (&optional buffer-name noerase to subject in-reply-to cc replybuffer actions)
+  (let (buf (get-buffer "*mail*"))
+    (if buf (gnats::vmish-rename-mail-buffer buf))
+    (or buffer-name (setq buffer-name "GNATS mail"))
+    (unwind-protect
+	(prog1
+	    (if gnats::emacs-19p
+		(mail noerase to subject in-reply-to cc replybuffer
+		      (cons '(gnats::vmish-rename-after-send) actions))
+	      (prog1
+		  (mail noerase to subject in-reply-to cc replybuffer)
+		(gnats::vmish-mail-bindings)))
+	  (rename-buffer (gnats::generate-new-buffer-name buffer-name)))
+      (gnats::vmish-restore-mail-buffer buf))))
+
+;;;; Send mail using mh-e.
+
+(defun gnats::mail-other-window-using-mhe ()
+  "Compose mail other window using mh-e.
+While composing the message, use \\[mh-yank-cur-msg] to yank the
+original message into it."
+  (let ((subject (gnats::get-reply-subject)))
+    (setq mh-show-buffer (current-buffer))
+    (mh-find-path)
+    (mh-send-other-window "" "" subject)
+    (setq mh-sent-from-folder (current-buffer))
+    (setq mh-sent-from-msg 1)))
+
+
+(defun gnats::mail-reply-using-mhe (just-sender)
+  "Compose reply mail using mh-e.
+The command \\[mh-yank-cur-msg] yanks the original message into current buffer.
+If optional argument JUST-SENDER is non-nil, send response only to
+original submitter of problem report."
+  ;; First of all, prepare mhe mail buffer.
+  (let (from cc subject date to reply-to (buffer (current-buffer)) msg-id)
+    (save-restriction
+      (setq from     (mh-get-field "From:")
+	    subject  (gnats::get-reply-subject)
+	    reply-to (or (mh-get-field "Reply-To:") from)
+	    to	     (or (mh-get-field "To:")
+			 (mh-get-field "Apparently-To:")
+			 "")
+	    cc       (mh-get-field "Cc:")
+	    date     (mh-get-field "Date:")
+	    msg-id   (mh-get-field "Message-Id:")
+	    )
+      (setq mh-show-buffer buffer)
+      (mh-find-path)
+      (mh-send reply-to (or (and just-sender "")
+			    (if (null cc) to
+			      (concat to ", " cc)))
+	       subject)
+      (save-excursion
+	(mh-insert-fields
+	 "In-Reply-To:" (gnats::make-in-reply-to-field from date msg-id)))
+      (setq mh-sent-from-folder buffer)
+      (setq mh-sent-from-msg 1)
+      )))
+
+
+;;;;---------------------------------------------------------------------------
+;;;; Functions to change specific fields
+;;;;---------------------------------------------------------------------------
+
+(defun gnats:state-change-from-to ()
+  "Change the value of the `>State:' field and update the audit trail."
+  (interactive)
+  (gnats:change-field "State"))
+
+(defun gnats:responsible-change-from-to ()
+  "Change the value of the `>Responsible:' field and update the audit trail."
+  (interactive)
+  (gnats:change-field "Responsible"))
+
+(defun gnats:category-change-from-to ()
+  "Change the value of the `>Category:' field and the responsible party."
+  (interactive)
+  (gnats:change-field "Category"))
+
+(defun gnats::update-audit-trail (field old new)
+  (if (gnats::position-on-field "Audit-Trail")
+      (let (start end)
+	(gnats::forward-eofield)
+	(setq start (point-marker))
+	(if (eq old t) (setq old "????"))
+	(if (string= field "Responsible")
+	    (insert (format "\n\n%s-Changed-From-To: %s->%s" field
+			    (gnats::nth-word old)
+			    (gnats::nth-word new)))
+	  (insert (format "\n\n%s-Changed-From-To: %s-%s" field
+			  (gnats::nth-word old)
+			  (gnats::nth-word new))))
+	(insert (format "\n%s-Changed-By: %s" field (user-login-name)))
+	(insert (format "\n%s-Changed-When: %s" field (current-time-string)))
+	(insert (format "\n%s-Changed-Why:\n" field))
+	(save-excursion
+	  (gnats::before-keyword t)
+	  (setq end (point-marker)))
+	;; here we record the changes in a assoc list
+	(setq gnats:::audit-trail (cons (list field 
+					     (gnats::nth-word old) 
+					     (gnats::nth-word new)
+					     start end) 
+				       gnats:::audit-trail)))
+    (error "Field `>Audit-Trail:' missing.")))
+
+(defun gnats::category-responsible (category)
+  (let ((entry (assoc category gnats::categories)))
+    (if entry
+	(nth 2 entry)
+      (signal 'gnats::no-such-category (list category)))))
+	      
+(defun gnats::update-responsible (ignore1 ignore2 new)
+  "Modify the responsible field of the current PR to match the new category."
+  (and (y-or-n-p "Update the >Responsible: field? ")
+       (gnats:change-field "Responsible" (gnats::category-responsible new))))
+
+;;;;---------------------------------------------------------------------------
+
+(defsubst gnats::rw (buf retval)
+  (or
+   retval				; call-process is broken under 19.19.2
+   (save-excursion (set-buffer buf) (buffer-size))))
+
+(defun gnats::handle-results (pr exit-status)
+  "Handle the results of running pr-edit or npr-edit, giving a signal
+if needed."
+  (cond
+   ((looking-at "n?pr-edit: cannot create lock file")
+    (signal 'gnats::cannot-lock nil))
+   ((looking-at "n?pr-edit: lock file exists")
+    (signal 'gnats::locked nil))
+   ((or (looking-at "n?pr-edit: no such PR")
+	(looking-at "n?pr-edit: couldn.t find PR.*"))
+    (signal 'gnats::no-such-pr nil))
+   ((looking-at "n?pr-edit: PR \\(.*\\) locked by \\(.*\\)")
+    (let* ((msg (gnats::bm 2))
+	   (pr-path
+	    (buffer-substring-no-properties (match-beginning 1) (match-end 1)))
+	   (pr-name (progn (if (string-match gnats:root pr-path)
+			       (substring pr-path (1+ (match-end 0)))
+			     pr-path)))
+	   (buf (get-buffer pr-name))
+	   win)
+      (if buf
+	  ;; If we're already editing the PR, just go to that
+	  ;; buffer and be done with it.
+	  (progn
+	    (if (setq win (get-buffer-window buf))
+		(select-window win)
+	      (switch-to-buffer buf))
+	    (message "Already editing PR %s." pr-name))
+	;; kick it to the next level
+	(signal 'gnats::locked-pr (list msg)))))
+   ((looking-at "n?pr-edit: PR is not locked")
+    (if (not gnats:::force) (signal 'gnats::pr-not-locked nil)
+      t))
+   ((looking-at "n?pr-edit: invalid fields")
+    (signal 'gnats::invalid-fields nil))
+   ((looking-at "n?pr-edit: cannot parse the date")
+    (signal 'gnats::invalid-date nil))
+   ((looking-at "n?pr-edit: lock file .* does not exist"))
+   (t (signal 'gnats::error
+	      (list (if (eq (point-min) (point-max))
+			(format "unknown error (exit status %d)"
+				exit-status)
+		      (buffer-substring (point-min) (- (point-max) 1))))))))
+
+(if gnats::emacs-19p
+    (require 'env))
+(defun gnats::start-background (pr proctype sentinel &optional outfile filep args)
+  (let ((buf (get-buffer-create gnats::err-buffer))
+	inbuf proc-name proc-action proc-send-buffer)
+    (save-excursion
+      (setq inbuf (current-buffer))
+      (set-buffer buf)
+      (erase-buffer)
+      (make-variable-buffer-local 'gnats:::force)
+      (setq gnats:::force nil)
+      (cond ((eq proctype 'check)
+	     (progn
+	       (setq proc-name "check-pr"
+		     proc-action "Checking"
+		     proc-send-buffer t)
+	       (setq args (append (list "--check") args))
+	       (make-variable-buffer-local 'gnats:::pr-buffer)
+	       (setq gnats:::pr-buffer inbuf)
+	       (make-variable-buffer-local 'gnats:::do-file-pr)
+	       (setq gnats:::do-file-pr filep)))
+	    ((eq proctype 'file)
+	     (setq proc-name "file-pr"
+		   proc-action "Filing"
+		   proc-send-buffer t))
+	    ((eq proctype 'unlock)
+	     (progn
+	       (setq proc-name "unlock-pr"
+		     proc-action "Unlocking")
+	       (make-variable-buffer-local 'gnats:::current-pr)
+	       (setq gnats:::current-pr pr)
+	       (setq args (append (list "--unlock" pr) args))))
+	    ((eq proctype 'unlock-force)
+	     (progn
+	       (setq proc-name "unlock-pr"
+		     proc-action "Unlocking"
+		     gnats:::force t)
+	       (make-variable-buffer-local 'gnats:::current-pr)
+	       (setq gnats:::current-pr pr)
+	       (setq args (append (list "--unlock" pr) args))))
+	    ((eq proctype 'edit)
+	     (progn
+	       (setq proc-name "edit-pr"
+		     proc-action "Fetching")
+	       (make-variable-buffer-local 'gnats:::current-pr)
+	       (setq gnats:::current-pr pr)
+	       (make-variable-buffer-local 'gnats:::newfile)
+	       (setq gnats:::newfile outfile)))
+	    (t
+	     (error "Invalid PROCTYPE for background GNATS process.")))
+      (let ((process-environment 
+	     (if gnats::emacs-19p (copy-sequence process-environment)))
+	    proc)
+	(setq proc
+	      (apply 'start-process
+		     (concat " *" proc-name "-" (random t))
+		     buf
+		     (format (if gnats:network-server
+				 "%s/gnats/npr-edit"
+			       "%s/gnats/pr-edit")
+			     gnats:libdir)
+		     (if gnats:network-server 
+			(concat (format  "--host=%s" gnats:network-server) args)
+		       args
+		       )
+		     ))
+
+	;; Only set up the sentinel if they want stuff done in the background.
+	(if gnats:run-in-background
+	    (progn
+	      (set-process-sentinel proc sentinel)
+	      (message "%s PR %s in background." proc-action pr))
+	  (message "%s PR %s..." proc-action pr))
+	(if proc-send-buffer
+	    (progn
+	      (set-buffer inbuf)
+	      (goto-char (point-min))
+	      (process-send-region proc (point-min) (point-max))
+	      (if (and (/= (point-min) (point-max))
+		       (/= (char-after (- (point-max) 1)) ?\n))
+		  (process-send-string proc "\n"))
+	      (process-send-eof proc)))
+	;; if they don't want it in the background, just sit and twiddle...
+	(if (not gnats:run-in-background)
+	    (save-excursion
+	      (set-buffer (process-buffer proc))
+	      (while (memq (process-status proc) '(run open))
+		(accept-process-output proc))
+	      (funcall sentinel proc nil)))))))
+
+(defun gnats::handle-pr-edit (process event)
+  (let ((buf (process-buffer process))
+	result pr newfile nbuf)
+    (if (null (buffer-name buf)) ;; deleted buffer
+	(set-process-buffer process nil)
+      (save-excursion
+	(set-buffer buf)
+	(setq pr gnats:::current-pr)
+	(setq result (process-exit-status process))
+	(and (/= 0 result)
+	     (goto-char (point-min))
+	     (gnats::handle-results gnats:::current-pr result))
+	(setq nbuf (generate-new-buffer
+		    (concat "*edit-pr " gnats:::current-pr "*")))
+	(setq newfile gnats:::newfile)
+	(set-buffer nbuf)
+	(insert-file-contents newfile)
+	(make-local-variable 'gnats:::backupname)
+	(put 'gnats:::backupname 'permanent-local t)
+	;; we do this in gnats:gnats-mode for non-network
+	(if gnats:network-server (setq gnats:::backupname newfile))
+	(set-buffer-modified-p nil)
+	(setq buffer-undo-list nil) ;flush undo list
+	(gnats:gnats-mode)
+	(make-variable-buffer-local 'gnats:::current-pr)
+	(setq gnats:::current-pr pr)
+	(goto-char gnats:::start-of-PR-fields))
+      (message "Fetching PR %s done." pr)
+      (if gnats:run-in-background
+	  (display-buffer nbuf 'not-this-window)
+	(switch-to-buffer nbuf)))))
+
+(defun gnats::pr-edit-background (pr outfile args)
+  (gnats::start-background pr 'edit 'gnats::handle-pr-edit outfile nil args))
+
+(defun gnats::handle-check-pr (process event)
+  (let ((buf (process-buffer process))
+	result pr)
+    (if (null (buffer-name buf)) ;; deleted buffer
+	(set-process-buffer process nil)
+      (save-excursion
+	(set-buffer buf)
+	(setq result (process-exit-status process))
+	(and (/= 0 result)
+	     (goto-char (point-min))
+	     (gnats::handle-results gnats:::current-pr result))
+	(message "Checked PR %s." gnats:::current-pr)
+	(if gnats:::do-file-pr
+	    (progn
+	      (set-buffer gnats:::pr-buffer)
+	      (gnats::file-pr-background)))))))
+
+(defun gnats::check-pr-background (&optional filep)
+  (gnats::start-background gnats:::current-pr 'check
+			  'gnats::handle-check-pr nil filep))
+
+(defun gnats::finish-filing ()
+  (let (responsible user resp-change state-change buf)
+    (if gnats:network-server (setq gnats:::pr-locked nil))
+    (setq buf (current-buffer))
+    (set-buffer-modified-p nil)
+    (setq responsible  (gnats::field-contents "Responsible")
+	  user         (user-login-name)
+	  resp-change (cdr (assoc "Responsible" gnats:::audit-trail))
+	  state-change (cdr (assoc "State" gnats:::audit-trail)))
+    (if (or state-change
+	    resp-change
+	    (not (equal user responsible)))
+	(gnats::mail-PR-changed user responsible
+			       resp-change state-change
+			       (gnats::get-header "X-GNATS-Notify")))
+    (gnats:unlock-buffer buf)))
+
+(defun gnats::handle-file-pr (process event)
+  (let ((buf (process-buffer process))
+	result pr prbuf)
+    (if (null (buffer-name buf)) ;; deleted buffer
+	(set-process-buffer process nil)
+      (save-excursion
+	(set-buffer buf)
+	(setq result (process-exit-status process))
+	(and (/= 0 result)
+	     (goto-char (point-min))
+	     (gnats::handle-results gnats:::current-pr result))
+	(message "Filed PR %s." gnats:::current-pr)
+	(set-buffer gnats:::pr-buffer)
+	(gnats::finish-filing)))))
+
+(defun gnats::file-pr-background ()
+  (gnats::start-background gnats:::current-pr 'file 'gnats::handle-file-pr))
+
+(defun gnats::lock (pr &optional outfile)
+  (let ((lockl (list "--lock"
+		(format "%s@%s" (user-login-name) (system-name))
+		"-p"
+		(if (fboundp 'emacs-pid)
+		    (concat "emacs pid " (int-to-string (emacs-pid)))
+		  "emacs18")
+		pr)))
+    (if gnats:network-server
+	(setq lockl (append lockl (list "-o" outfile "--get-lists"
+					"--host" gnats:network-server))))
+    (gnats::pr-edit-background pr outfile lockl)))
+
+(fset 'unlock-pr 'gnats:unlock-pr)
+(fset 'gnats-unlock 'gnats:unlock-pr)	;backward compatibility
+(defun gnats::handle-unlock-pr (process event)
+  (let ((buf (process-buffer process))
+	result pr newfile nbuf)
+    (if (null (buffer-name buf)) ;; deleted buffer
+	(set-process-buffer process nil)
+      (save-excursion
+	(set-buffer buf)
+	(setq pr gnats:::current-pr)
+	(setq result (process-exit-status process))
+	(and (/= 0 result)
+	     (goto-char (point-min))