mirror of
https://git.FreeBSD.org/src.git
synced 2024-12-23 11:18:54 +00:00
fa99586d3a
- update: For submitting non-maintainer updates/changes - maintainer-update: For submitting maintainer updates/changes The intent is to make it easier to spot maintainer sactioned or submitted updates to ports though it might also be useful for userland code that is maintained by someone that is not a FreeBSD committer. Submitted by: nbm and many others
814 lines
29 KiB
Plaintext
814 lines
29 KiB
Plaintext
;;;; -*-emacs-lisp-*-
|
|
;;;;---------------------------------------------------------------------------
|
|
;;;; EMACS interface for send-pr (by Heinz G. Seidl)
|
|
;;;; Slightly hacked by Brendan Kehoe (brendan@cygnus.com).
|
|
;;;;
|
|
;;;; This file is part of the Problem Report Management System (GNATS)
|
|
;;;; Copyright 1992, 1993, 1997 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.
|
|
;;;;
|
|
;;;;---------------------------------------------------------------------------
|
|
;;;;
|
|
;;;; $FreeBSD$
|
|
|
|
(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 "3.113")
|
|
|
|
(defvar gnats:root "/home/gnats"
|
|
"*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)))))
|
|
(if (and ret (string-equal ret "")) (setq ret nil))
|
|
(kill-buffer buf)
|
|
ret))
|
|
|
|
;; const because it must match the script's value
|
|
(defconst send-pr:datadir (or (gnats::get-config "DATADIR") "@DATADIR@")
|
|
"*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") "freefall")
|
|
"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") ("update") ("maintainer-update"))
|
|
(, (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") "@DEFAULT_RELEASE@"))
|
|
text)
|
|
("Submitter-Id" nil
|
|
(, (or (gnats::get-config "DEFAULT_SUBMITTER") "unknown")) 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 17 "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"))
|
|
(or (stringp mail-default-reply-to)
|
|
(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)))
|
|
(call-process "send-pr" nil t nil "-P" site)
|
|
(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
|
|
(if (or (buffer-modified-p)
|
|
(not send-pr:::sent)
|
|
(y-or-n-p "PR already sent; resend? "))
|
|
(progn
|
|
(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)
|
|
;; ensure that a final newline is present:
|
|
(if (not (equal (char-after (1- (point-max))) ?\n))
|
|
(save-excursion (goto-char (point-max))
|
|
(insert ?\n)))
|
|
(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)
|
|
(setq send-pr:::sent t)
|
|
(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 'send-pr:::sent)
|
|
(setq send-pr:::sent nil)
|
|
(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))
|
|
(while (search-forward "//" nil t)
|
|
(replace-match "/" nil t))
|
|
(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
|