;;;; -*-emacs-lisp-*-
;;;;---------------------------------------------------------------------------
;;;;    EMACS interface for send-pr (by Heinz G. Seidl, hgs@cygnus.com)
;;;;    Slightly hacked by Brendan Kehoe (brendan@cygnus.com).
;;;;
;;;;    This file is part of the Problem Report Management System (GNATS)
;;;;    Copyright 1992, 1993 Cygnus Support
;;;;
;;;;    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.
;;;;
;;;;    You should have received a copy of the GNU Library General Public
;;;;    License along with this program; if not, write to the Free
;;;;    Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
;;;;
;;;;---------------------------------------------------------------------------
;;;;
;;;;    This file contains the EMACS interface to the Problem Report Management
;;;;	System (GNATS):
;;;;
;;;;		- The `send-pr' command and the `send-pr-mode' for sending 
;;;;              Problem Reports (PRs).
;;;;
;;;;    For more information about how to send a PR see send-pr(1).
;;;;
;;;;---------------------------------------------------------------------------
;;;;
;;;;	Configuration: the symbol `DEFAULT-RELEASE' can be replaced by
;;;;	site/release specific strings during the configuration/installation
;;;;	process.
;;;;
;;;;    Install this file in your EMACS library directory.
;;;;
;;;;---------------------------------------------------------------------------

(provide 'send-pr)

;;;;---------------------------------------------------------------------------
;;;; Customization: put the following forms into your default.el file
;;;; (or into your .emacs)
;;;;---------------------------------------------------------------------------

;(autoload 'send-pr-mode "send-pr"
;	  "Major mode for sending problem reports." t)

;(autoload 'send-pr "send-pr"
;	  	  "Command to create and send a problem report." t)

;;;;---------------------------------------------------------------------------
;;;; End of Customization Section
;;;;---------------------------------------------------------------------------

(autoload 'server-buffer-done "server")
(defvar server-buffer-clients nil)
(defvar mail-self-blind nil)
(defvar mail-default-reply-to nil)

(defconst send-pr::version "xVERSIONx")

(defvar gnats:root "xGNATS_ROOTx"
  "*The top of the tree containing the GNATS database.")

;;;;---------------------------------------------------------------------------
;;;; hooks
;;;;---------------------------------------------------------------------------

(defvar text-mode-hook nil)   ; we define it here in case it's not defined
(defvar send-pr-mode-hook text-mode-hook "Called when send-pr is invoked.")

;;;;---------------------------------------------------------------------------
;;;; Domains and default values for (some of) the Problem Report fields;
;;;; constants and definitions.
;;;;---------------------------------------------------------------------------

(defconst gnats::emacs-19p
  (not (or (and (boundp 'epoch::version) epoch::version)
	   (string-lessp emacs-version "19")))
  "Is this emacs v19?")

;;; This has to be here rather than at the bottom of this file with
;;; the other utility functions because it is used by
;;; gnats::get-config, which is called when send-pr.el is being
;;; loaded (see the "defconst" below), before the whole file has been
;;; loaded.

(defun gnats::find-safe-default-directory (&optional buffer)
"If the directory referred to by `default-directory' for the current
buffer (or for optional argument BUFFER) does not exist, set it to the home
directory of the current user if that exists, or to `/'.

Returns the final value of default-directory in the buffer."
  (let ((homedir (expand-file-name "~/")))
    (save-excursion
      (if buffer (set-buffer buffer))
      (if (not (file-exists-p default-directory))
	  (if (file-exists-p homedir)
	      (setq default-directory homedir)
	    (setq default-directory "/")))
      default-directory)))

;;; These may be changed during configuration/installation or by the individual
;;; user in his/her .emacs file.
;;;
(defun gnats::get-config (var)
  (let ((shell-file-name "/bin/sh")
	(buf (generate-new-buffer " *GNATS config*"))
	ret)
    (save-excursion
      (set-buffer buf)
      (shell-command-on-region
       (point-min) (point-max)
       (concat ". " gnats:root "/gnats-adm/config; echo $" var ) t)
      (goto-char (point-min))
      ; We have to use get-buffer, since shell-command-on-region will wipe
      ; out the buffer if there's no output from the command.
      (if (or (not (get-buffer "*Shell Command Output*"))
	      (looking-at "/bin/sh:\\|\.:\\|\n"))
	  (setq ret nil)
	(setq ret (buffer-substring (point-min) (- (point-max) 1)))))
    (kill-buffer buf)
    ret))

;; const because it must match the script's value
(defconst send-pr:datadir (or (gnats::get-config "DATADIR") "xDATADIRx")
  "*Where the `gnats' subdirectory containing category lists lives.")

(defvar send-pr::sites nil
  "List of GNATS support sites; computed at runtime.")
(defvar send-pr:default-site
  (or (gnats::get-config "GNATS_SITE") "xGNATS_SITEx")
  "Default site to send bugs to.")
(defvar send-pr:::site send-pr:default-site
  "The site to which a problem report is currently being submitted, or NIL
if using the default site (buffer local).")

(defvar send-pr:::categories nil
  "Buffer local list of available categories, derived at runtime from
send-pr:::site and send-pr::category-alist.")
(defvar send-pr::category-alist nil
  "Alist of GNATS support sites and the categories supported at each; computed
at runtime.")

;;; Ideally we would get all the following values from a central database
;;; during runtime instead of having them here in the code.
;;;
(defconst send-pr::fields
  (` (("Category" send-pr::set-categories
       (, (or (gnats::get-config "DEFAULT_CATEGORY") nil)) enum)
      ("Class" (("sw-bug") ("doc-bug") ("change-request") ("support"))
       (, (or (gnats::get-config "DEFAULT_CLASS") 0)) enum)
      ("Confidential" (("yes") ("no"))
       (, (or (gnats::get-config "DEFAULT_CONFIDENTIAL") 1)) enum)
      ("Severity" (("non-critical") ("serious") ("critical"))
       (, (or (gnats::get-config "DEFAULT_SEVERITY") 1)) enum)
      ("Priority" (("low") ("medium") ("high"))
       (, (or (gnats::get-config "DEFAULT_PRIORITY") 1)) enum)
      ("Release" nil
       (, (or (gnats::get-config "DEFAULT_RELEASE") "xDEFAULT_RELEASEx"))
       text)
      ("Submitter-Id" nil
       (, (or (gnats::get-config "SUBMITTER") "xSUBMITTERx")) text)
      ("Synopsis" nil nil text
       (lambda (a b c) (gnats::set-mail-field "Subject" c)))))
  "AList, keyed on the name of the field, of:
1) The field name.
2) The list of completions.  This can be a list, a function to call, or nil.
3) The default value.
4) The type of the field.
5) A sub-function to call when changed.")

(defvar gnats::fields nil)

(defmacro gnats::push (i l)
  (` (setq (, l) (cons (,@ (list i l))))))

(defun send-pr::set-categories (&optional arg)
  "Get the list of categories for the current site out of
send-pr::category-alist if there or from send-pr if not.  With arg, force
update."
  ;;
  (let ((entry (assoc send-pr:::site send-pr::category-alist)))
    (or (and entry (null arg))
	(let ((oldpr (getenv "GNATS_ROOT")) cats)
	  (send-pr::set-sites arg)
	  (setenv "GNATS_ROOT" gnats:root)
	  (setq cats (gnats::get-value-from-shell
		      "send-pr" "-CL" send-pr:::site))
	  (setenv "GNATS_ROOT" oldpr)
	  (if entry (setcdr entry cats)
	    (setq entry (cons send-pr:::site cats))
	    (gnats::push entry send-pr::category-alist))))
    (setq send-pr:::categories (cdr entry))))

(defun send-pr::set-sites (&optional arg)
  "Get the list of sites (by listing the contents of DATADIR/gnats) and assign
it to send-pr::sites.  With arg, force update."
  (or (and (null arg) send-pr::sites)
      (progn
	(setq send-pr::sites nil)
	(mapcar
	 (function
	  (lambda (file)
	    (or (memq t (mapcar (function (lambda (x) (string= x file)))
				'("." ".." "pr-edit" "pr-addr")))
		(not (file-readable-p file))
		(gnats::push (list (file-name-nondirectory file))
			    send-pr::sites))))
	 (directory-files (format "%s/gnats" send-pr:datadir) t))
	(setq send-pr::sites (reverse send-pr::sites)))))

(defconst send-pr::pr-buffer-name "*send-pr*"
  "Name of the temporary buffer, where the problem report gets composed.")

(defconst send-pr::err-buffer-name "*send-pr-error*"
  "Name of the temporary buffer, where send-pr error messages appear.")

(defvar send-pr:::err-buffer nil
  "The error buffer used by the current PR buffer.")

(defvar send-pr:::spawn-to-send nil
  "Whether or not send-pr-mode should spawn a send-pr process to send the PR.")

(defconst gnats::indent 16 "Indent for formatting the value.")

;;;;---------------------------------------------------------------------------
;;;; `send-pr' - command for creating and sending of problem reports
;;;;---------------------------------------------------------------------------

(fset 'send-pr 'send-pr:send-pr)
(defun send-pr:send-pr (&optional site)
  "Create a buffer and read in the result of `send-pr -P'.
When finished with editing the problem report use \\[send-pr:submit-pr]
to send the PR with `send-pr -b -f -'."
  ;;
  (interactive
   (if current-prefix-arg
       (list (completing-read "Site: " (send-pr::set-sites 'recheck) nil t
			      send-pr:default-site))))
  (or site (setq site send-pr:default-site))
  (let ((buf (get-buffer send-pr::pr-buffer-name)))
    (if (or (not buf)
	    (progn (switch-to-buffer buf)
		   (cond ((or (not (buffer-modified-p buf))
			      (y-or-n-p "Erase previous problem report? "))
			  (erase-buffer) t)
			 (t nil))))
	(send-pr::start-up site))))

(defun send-pr::start-up (site)
  (switch-to-buffer (get-buffer-create send-pr::pr-buffer-name))
  (setq default-directory (expand-file-name "~/"))
  (auto-save-mode auto-save-default)
  (let ((oldpr (getenv "GNATS_ROOT"))
	(case-fold-search nil))
    (setenv "GNATS_ROOT" gnats:root)
    (send-pr::insert-template site)
    (setenv "GNATS_ROOT" oldpr)
    (goto-char (point-min))
    (if (looking-at "send-pr:")
	(cond ((looking-at "send-pr: .* does not have a categories list")
	       (setq send-pr::sites nil)
	       (error "send-pr: the GNATS site %s does not have a categories list" site))
	      (t (error (buffer-substring (point-min) (point-max)))))
      (save-excursion
	;; Clear cruft inserted by bdamaged .cshrcs
	(goto-char 1)
	(re-search-forward "^SEND-PR:")
	(delete-region 1 (match-beginning 0)))))
  (set-buffer-modified-p nil)
  (send-pr:send-pr-mode)
  (setq send-pr:::site site)
  (setq send-pr:::spawn-to-send t)
  (send-pr::set-categories)
  (if (null send-pr:::categories)
      (progn
	(and send-pr:::err-buffer (kill-buffer send-pr:::err-buffer))
	(kill-buffer nil)
	(message "send-pr: no categories found"))
    (if (eq mail-default-reply-to t)
	(setq mail-default-reply-to (getenv "REPLYTO")))
    (and mail-default-reply-to
	 (gnats::set-mail-field "Reply-To" mail-default-reply-to))
    (and mail-self-blind
	 (gnats::set-mail-field "BCC" (user-login-name)))
    (mapcar 'send-pr::maybe-change-field send-pr::fields)
    (gnats::position-on-field "Description")
    (message (substitute-command-keys
	      "To send the problem report use: \\[send-pr:submit-pr]"))))

(defvar send-pr::template-alist nil
  "An alist containing the output of send-pr -P <sitename> for various sites.")

(defun send-pr::insert-template (site)
  (let ((elt (assoc site send-pr::template-alist)))
    (if elt
	(save-excursion (insert (cdr elt)))
      (shell-command (concat "send-pr -P " site) t)
      (save-excursion
	(setq send-pr::template-alist
	      (cons (cons site (buffer-substring (point-min) (point-max)))
		    send-pr::template-alist))))))

(fset 'do-send-pr 'send-pr:submit-pr)	;backward compat
(defun send-pr:submit-pr ()
  "Pipe the contents of the buffer *send-pr* to `send-pr -f -.' unless this
buffer was loaded with emacsclient, in which case save the buffer and exit."
  ;;
  (interactive)
  (cond
   ((and (boundp 'server-buffer-clients)
	 server-buffer-clients)
    (let ((buffer (current-buffer))
	  (version-control nil) (buffer-backed-up nil))
      (save-buffer buffer)
      (kill-buffer buffer)
      (server-buffer-done buffer)))
   (send-pr:::spawn-to-send
    (or (and send-pr:::err-buffer
	     (buffer-name send-pr:::err-buffer))
	(setq send-pr:::err-buffer
	      (get-buffer-create send-pr::err-buffer-name)))
    (let ((err-buffer send-pr:::err-buffer) mesg ok)
      (save-excursion (set-buffer err-buffer) (erase-buffer))
      (message "running send-pr...")
      (let ((oldpr (getenv "GNATS_ROOT")))
	(setenv "GNATS_ROOT" gnats:root)
	(call-process-region (point-min) (point-max) "send-pr"
			     nil err-buffer nil send-pr:::site
			     "-b" "-f" "-")
	(setenv "GNATS_ROOT" oldpr))
      (message "running send-pr...done")
      ;; stupidly we cannot check the return value in EMACS 18.57, thus we need
      ;; this kluge to find out whether send-pr succeeded.
      (if (save-excursion
	    (set-buffer err-buffer)
	    (goto-char (point-min))
	    (setq mesg (buffer-substring (point-min) (- (point-max) 1)))
	    (search-forward "problem report sent" nil t))
	  (progn (message mesg)
		 (kill-buffer err-buffer)
		 (delete-auto-save-file-if-necessary)
		 (set-buffer-modified-p nil)
		 (bury-buffer))
	(pop-to-buffer err-buffer))
    ))
   (t
    (save-buffer)
    (message "Exit emacs to send the PR."))))
   
;;;;---------------------------------------------------------------------------
;;;; send-pr:send-pr-mode mode
;;;;---------------------------------------------------------------------------

(defvar send-pr-mode-map
  (let ((map (make-sparse-keymap)))
    (define-key map "\C-c\C-c" 'send-pr:submit-pr)
    (define-key map "\C-c\C-f" 'gnats:change-field)
    (define-key map "\M-n" 'gnats:next-field)
    (define-key map "\M-p" 'gnats:previous-field)
    (define-key map "\C-\M-f" 'gnats:forward-field)
    (define-key map "\C-\M-b" 'gnats:backward-field)
    map)
  "Keymap for send-pr mode.")

(defconst gnats::keyword "^>\\([-a-zA-Z]+\\):")
(defconst gnats::before-keyword "[ \t\n\f]*[\n\f]+>\\([-a-zA-Z]+\\):")
(defconst gnats::after-keyword "^>\\([-a-zA-Z]+\\):[ \t\n\f]+")

(fset 'send-pr-mode 'send-pr:send-pr-mode)
(defun send-pr:send-pr-mode ()
  "Major mode for submitting problem reports.
For information about the form see gnats(1) and send-pr(1).
Special commands: \\{send-pr-mode-map}
Turning on send-pr-mode calls the value of the variable send-pr-mode-hook,
if it is not nil."
  (interactive)
  (gnats::patch-exec-path)
  (put 'send-pr:send-pr-mode 'mode-class 'special)
  (kill-all-local-variables)
  (setq major-mode 'send-pr:send-pr-mode)
  (setq mode-name "send-pr")
  (use-local-map send-pr-mode-map)
  (set-syntax-table text-mode-syntax-table)
  (setq local-abbrev-table text-mode-abbrev-table)
  (setq buffer-offer-save t)
  (make-local-variable 'send-pr:::site)
  (make-local-variable 'send-pr:::categories)
  (make-local-variable 'send-pr:::err-buffer)
  (make-local-variable 'send-pr:::spawn-to-send)
  (make-local-variable 'paragraph-separate)
  (setq paragraph-separate (concat (default-value 'paragraph-separate)
				   "\\|" gnats::keyword "[ \t\n\f]*$"))
  (make-local-variable 'paragraph-start)
  (setq paragraph-start (concat (default-value 'paragraph-start)
				"\\|" gnats::keyword))
  (run-hooks 'send-pr-mode-hook)
  t)

;;;;---------------------------------------------------------------------------
;;;; Functions to read and replace field values.
;;;;---------------------------------------------------------------------------

(defun gnats::position-on-field (field &optional quiet)
  (goto-char (point-min))
  (if (not (re-search-forward (concat "^>" field ":") nil t))
      (if quiet
	  nil
	(error "Field `>%s:' not found." field))
    (re-search-forward "[ \t\n\f]*")
    (if (looking-at gnats::keyword)
	(backward-char 1))
    t))

(defun gnats::mail-position-on-field (field)
  (let (end
	(case-fold-search t))
    (goto-char (point-min))
    (re-search-forward "^$")
    (setq end (match-beginning 0))
    (goto-char (point-min))
    (if (not (re-search-forward (concat "^" field ":") end 'go-to-end))
	(insert field ": \n")
      (re-search-forward "[ \t\n\f]*"))
    (skip-chars-backward "\n")
    t))

(defun gnats::field-contents (field &optional elem move)
  (let (pos)
    (unwind-protect
	(save-excursion
	  (if (not (gnats::position-on-field field t))
	      nil
	    (setq pos (point-marker))
	    (if (or (looking-at "<.*>$") (eolp))
		t
	      (looking-at ".*$")	; to set match-{beginning,end}
	      (gnats::nth-word 
	       (buffer-substring (match-beginning 0) (match-end 0))
	       elem))))
      (and move pos (goto-char pos)))))

(defun gnats::functionp (thing)
  (or (and (symbolp thing) (fboundp thing))
      (and (listp thing) (eq (car thing) 'lambda))))

(defun gnats::field-values (field)
  "Return the possible (known) values for field FIELD."
  (let* ((fields (if (eq major-mode 'gnats:gnats-mode) gnats::fields
		   send-pr::fields))
	 (thing (elt (assoc field fields) 1)))
    (cond ((gnats::functionp thing) (funcall thing))
	  ((listp thing) thing)
	  (t (error "ACK")))))

(defun gnats::field-default (field)
  "Return the default value for field FIELD."
  (let* ((fields (if (eq major-mode 'gnats:gnats-mode) gnats::fields
		   send-pr::fields))
	 (thing (elt (assoc field fields) 2)))
    (cond ((stringp thing) thing)
	  ((null thing) "")
	  ((numberp thing) (car (elt (gnats::field-values field) thing)))
	  ((gnats::functionp thing)
	   (funcall thing (gnats::field-contents field)))
	  ((eq thing t) (gnats::field-contents field))
	  (t (error "ACK")))))

(defun gnats::field-type (field)
  "Return the type of field FIELD."
  (let* ((fields (if (eq major-mode 'gnats:gnats-mode) gnats::fields
		   send-pr::fields))
	 (thing (elt (assoc field fields) 3)))
    thing))

(defun gnats::field-action (field)
  "Return the extra handling function for field FIELD."
  (let* ((fields (if (eq major-mode 'gnats:gnats-mode) gnats::fields
		   send-pr::fields))
	 (thing (elt (assoc field fields) 4)))
    (cond ((null thing) 'ignore)
	  ((gnats::functionp thing) thing)
	  (t (error "ACK")))))

;;;;---------------------------------------------------------------------------
;;;; Point movement functions
;;;;---------------------------------------------------------------------------

(or (fboundp 'defsubst) (fset 'defsubst 'defun))

(defun send-pr::maybe-change-field (field)
  (setq field (car field))
  (let ((thing (gnats::field-contents field)))
    (and thing (eq t thing)
	 (not (eq 'multi-text (gnats::field-type field)))
	 (gnats:change-field field))))
    
(defun gnats:change-field (&optional field default)
  "Change the value of the field containing the cursor.  With arg, ask the
user for the field to change.  From a program, the function takes optional
arguments of the field to change and the default value to use."
  (interactive)
  (or field current-prefix-arg (setq field (gnats::current-field)))
  (or field
      (setq field
	    (completing-read "Field: "
			     (if (eq major-mode 'gnats:gnats-mode)
				 gnats::fields
			       send-pr::fields)
			     nil t)))
  (gnats::position-on-field field)
  (sit-for 0)
  (let* ((old (gnats::field-contents field))
	 new)
    (if (null old)
	(error "ACK")
      (if (or (interactive-p) t)
	  (let ((prompt (concat ">" field ": "))
		(domain (gnats::field-values field))
		(type (gnats::field-type field)))
	    (or default (setq default (gnats::field-default field)))
	    (setq new
		  (if (eq type 'enum)
		      (completing-read prompt domain nil t 
				       (if gnats::emacs-19p (cons default 0)
					 default))
		    (read-string prompt (if gnats::emacs-19p (cons default 1)
					  default)))))
	(setq new default))
      (gnats::set-field field new)
      (funcall (gnats::field-action field) field old new)
      new)))

(defun gnats::set-field (field value)
  (save-excursion
    (gnats::position-on-field field)
    (delete-horizontal-space)
    (looking-at ".*$")
    (replace-match
     (concat (make-string (- gnats::indent (length field) 2) ?\40 ) value) t)))

(defun gnats::set-mail-field (field value)
  (save-excursion
    (gnats::mail-position-on-field field)
    (delete-horizontal-space)
    (looking-at ".*$")
    (replace-match (concat " " value) t)))
  
(defun gnats::before-keyword (&optional where)
  "Returns t if point is in some white space before a keyword.
If where is nil, then point is not changed; if where is t then point is moved
to the beginning of the keyword, otherwise it is moved to the beginning
of the white space it was in."
  ;;
  (if (looking-at gnats::before-keyword)
      (prog1 t
	(cond  ((eq where t)
		(re-search-forward "^>") (backward-char))
	       ((not (eq where nil))
		(re-search-backward "[^ \t\n\f]") (forward-char))))
       nil))

(defun gnats::after-keyword (&optional where)
  "Returns t if point is in some white space after a keyword.
If where is nil, then point is not changed; if where is t then point is moved
to the beginning of the keyword, otherwise it is moved to the end of the white
space it was in."
  ;;
  (if (gnats::looking-after gnats::after-keyword)
      (prog1 t
	(cond  ((eq where t)
		(re-search-backward "^>"))
	       ((not (eq where nil))
		(re-search-forward "[^ \t\n\f]") (backward-char))))
       nil))

(defun gnats::in-keyword (&optional where)
  "Returns t if point is within a keyword.
If where is nil, then point is not changed; if where is t then point is moved
to the beginning of the keyword."
  ;;
  (let ((old-point (point-marker)))
    (beginning-of-line)
    (cond ((and (looking-at gnats::keyword)
	       (< old-point (match-end 0)))
	   (prog1 t
	     (if (eq where t) 
		 t
	       (goto-char old-point))))
	  (t (goto-char old-point)
	     nil))))

(defun gnats::forward-bofield ()
  "Moves point to the beginning of a field. Assumes that point is in the
keyword." 
  ;;
  (if (re-search-forward "[ \t\n\f]+[^ \t\n\f]" (point-max) '-)
      (backward-char)
    t))

(defun gnats::backward-eofield ()
  "Moves point to the end of a field. Assumes point is in the keyword."
  ;;
  (if (re-search-backward "[^ \t\n\f][ \t\n\f]+" (point-min) '-)
      (forward-char)
    t))

(defun gnats::forward-eofield ()
  "Moves point to the end of a field. Assumes that point is in the field." 
  ;;
  ;; look for the next field
  (if (re-search-forward gnats::keyword (point-max) '-) 
      (progn (beginning-of-line) (gnats::backward-eofield))
  (re-search-backward "[^ \t\n\f][ \t\n\f]*" (point-min) '-)
  (forward-char)))

(defun gnats::backward-bofield ()
  "Moves point to the beginning of a field. Assumes that point is in the
field." 
  ;;
  ;;look for previous field
  (if (re-search-backward gnats::keyword (point-min) '-)
      (gnats::forward-bofield)
    t))


(defun gnats:forward-field ()
  "Move point forward to the end of the field or to the beginning of the next
field."
  ;;
  (interactive)
  (if (or (gnats::before-keyword t) (gnats::in-keyword t)
	  (gnats::after-keyword t))
	(gnats::forward-bofield)
    (gnats::forward-eofield)))

(defun gnats:backward-field ()
  "Move point backward to the beginning/end of a field."
  ;;
  (interactive)
  (backward-char)
  (if (or (gnats::before-keyword t) (gnats::in-keyword t)
	  (gnats::after-keyword t))
      (gnats::backward-eofield)
    (gnats::backward-bofield)))

(defun gnats:next-field ()
  "Move point to the beginning of the next field."
  ;;
  (interactive)
  (if (or (gnats::before-keyword t) (gnats::in-keyword t)
	  (gnats::after-keyword t))
      (gnats::forward-bofield)
    (if (re-search-forward gnats::keyword (point-max) '-)
	(gnats::forward-bofield)
      t)))

(defun gnats:previous-field ()
  "Move point to the beginning of the previous field."
  ;;
  (interactive)
  (backward-char)
  (if (or (gnats::after-keyword t) (gnats::in-keyword t)
	  (gnats::before-keyword t))
      (progn (re-search-backward gnats::keyword (point-min) '-)
	     (gnats::forward-bofield))
    (gnats::backward-bofield)))

(defun gnats:beginning-of-field ()
  "Move point to the beginning of the current field."
  (interactive)
  (cond ((gnats::in-keyword t)
	 (gnats::forward-bofield))
	((gnats::after-keyword 0))
	(t
	 (gnats::backward-bofield))))

(defun gnats::current-field ()
  (save-excursion
    (if (cond ((or (gnats::in-keyword t) (gnats::after-keyword t))
	       (looking-at gnats::keyword))
	      ((re-search-backward gnats::keyword nil t)))
	(buffer-substring (match-beginning 1) (match-end 1))
      nil)))

;;;;---------------------------------------------------------------------------
;;;; Support functions
;;;;---------------------------------------------------------------------------

(defun gnats::looking-after (regex)
  "Returns t if point is after regex."
  ;;
  (let* ((old-point (point))
	 (start (if (eobp)
		   old-point
		 (forward-char) (point))))
    (cond ((re-search-backward regex (point-min) t)
	   (goto-char old-point)
	   (cond ((eq (match-end 0) start)
		  t))))))

(defun gnats::nth-word (string &optional elem)
  "Returns the elem-th word of the string.
If elem is nil, then the first wort is returned, if elem is 0 then
the whole string is returned."
   ;;
  (if (integerp elem)
      (cond ((eq elem 0) string)
	    ((eq elem 1) (gnats::first-word string))
	    ((equal string "") "")
	    ((>= elem 2) 
	     (let ((i 0) (value ""))
	       (setq string		; strip leading blanks
		     (substring string (or (string-match "[^ \t]" string) 0)))
	       (while (< i elem)
		 (setq value 
		       (substring string 0 
				  (string-match "[ \t]*$\\|[ \t]+" string)))
		 (setq string 
		       (substring string (match-end 0)))
		 (setq i (+ i 1)))
	       value)))
    (gnats::first-word string)))

(defun gnats::first-word (string)
  (setq string 
	(substring string (or (string-match "[^ \t]" string) 0)))
  (substring string 0 (string-match "[ \t]*$\\|[ \t]+" string)))

;;;;---------------------------------------------------------------------------

(defun gnats::patch-exec-path ()
  ;;
  "Replaces `//' by `/' in `exec-path'."
  ;;
  ;(make-local-variable 'exec-path)
  (let ((err-buffer (get-buffer-create " *gnats::patch-exec-path*"))
	(ret))
    (setq exec-path (save-excursion (set-buffer err-buffer)
				    (prin1 exec-path err-buffer)
				    (goto-char (point-min))
				    (replace-string "//" "/")
				    (goto-char (point-min))
				    (setq ret (read err-buffer))
				    (kill-buffer err-buffer)
				    ret
				    ))))

(defun gnats::get-value-from-shell (&rest command)
  "Execute shell command to get a list of valid values for `variable'."
  ;;
  (let ((err-buffer (get-buffer-create " *gnats::get-value-from-shell*")))
    (save-excursion
      (set-buffer err-buffer)
      (unwind-protect
	  (condition-case var
	      (progn
		(apply 'call-process
		       (car command) nil err-buffer nil (cdr command))
		(goto-char (point-min))
		(if (looking-at "[-a-z]+: ")
		    (error (buffer-substring (point-min) (point-max))))
		(read err-buffer))
	    (error nil))
	(kill-buffer err-buffer)))))

(or (fboundp 'setenv)
    (defun setenv (variable &optional value)
      "Set the value of the environment variable named VARIABLE to VALUE.
VARIABLE should be a string.  VALUE is optional; if not provided or is
`nil', the environment variable VARIABLE will be removed.  
This function works by modifying `process-environment'."
      (interactive "sSet environment variable: \nsSet %s to value: ")
      (if (string-match "=" variable)
	  (error "Environment variable name `%s' contains `='" variable)
	(let ((pattern (concat "\\`" (regexp-quote (concat variable "="))))
	      (case-fold-search nil)
	      (scan process-environment))
	  (while scan
	    (cond
	     ((string-match pattern (car scan))
	      (if (eq nil value)
		  (setq process-environment (delq (car scan)
						  process-environment))
		(setcar scan (concat variable "=" value)))
	      (setq scan nil))
	     ((null (setq scan (cdr scan)))
	      (setq process-environment
		    (cons (concat variable "=" value)
			  process-environment)))))))))

;;;; end of send-pr.el
