1;;;; -*-emacs-lisp-*- 2;;;;--------------------------------------------------------------------------- 3;;;; EMACS interface for send-pr (by Heinz G. Seidl, hgs@cygnus.com) 4;;;; Slightly hacked by Brendan Kehoe (brendan@cygnus.com). 5;;;; 6;;;; This file is part of the Problem Report Management System (GNATS) 7;;;; Copyright 1992, 1993 Cygnus Support 8;;;; 9;;;; This program is free software; you can redistribute it and/or 10;;;; modify it under the terms of the GNU General Public 11;;;; License as published by the Free Software Foundation; either 12;;;; version 2 of the License, or (at your option) any later version. 13;;;; 14;;;; This program is distributed in the hope that it will be useful, 15;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of 16;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 17;;;; General Public License for more details. 18;;;; 19;;;; You should have received a copy of the GNU Library General Public 20;;;; License along with this program; if not, write to the Free 21;;;; Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 22;;;; 23;;;;--------------------------------------------------------------------------- 24;;;; 25;;;; This file contains the EMACS interface to the Problem Report Management 26;;;; System (GNATS): 27;;;; 28;;;; - The `send-pr' command and the `send-pr-mode' for sending 29;;;; Problem Reports (PRs). 30;;;; 31;;;; For more information about how to send a PR see send-pr(1). 32;;;; 33;;;;--------------------------------------------------------------------------- 34;;;; 35;;;; Configuration: the symbol `DEFAULT-RELEASE' can be replaced by 36;;;; site/release specific strings during the configuration/installation 37;;;; process. 38;;;; 39;;;; Install this file in your EMACS library directory. 40;;;; 41;;;;--------------------------------------------------------------------------- 42 43(provide 'send-pr) 44 45;;;;--------------------------------------------------------------------------- 46;;;; Customization: put the following forms into your default.el file 47;;;; (or into your .emacs) 48;;;;--------------------------------------------------------------------------- 49 50;(autoload 'send-pr-mode "send-pr" 51; "Major mode for sending problem reports." t) 52 53;(autoload 'send-pr "send-pr" 54; "Command to create and send a problem report." t) 55 56;;;;--------------------------------------------------------------------------- 57;;;; End of Customization Section 58;;;;--------------------------------------------------------------------------- 59 60(autoload 'server-buffer-done "server") 61(defvar server-buffer-clients nil) 62(defvar mail-self-blind nil) 63(defvar mail-default-reply-to nil) 64 65(defconst send-pr::version "xVERSIONx") 66 67(defvar gnats:root "xGNATS_ROOTx" 68 "*The top of the tree containing the GNATS database.") 69 70;;;;--------------------------------------------------------------------------- 71;;;; hooks 72;;;;--------------------------------------------------------------------------- 73 74(defvar text-mode-hook nil) ; we define it here in case it's not defined 75(defvar send-pr-mode-hook text-mode-hook "Called when send-pr is invoked.") 76 77;;;;--------------------------------------------------------------------------- 78;;;; Domains and default values for (some of) the Problem Report fields; 79;;;; constants and definitions. 80;;;;--------------------------------------------------------------------------- 81 82(defconst gnats::emacs-19p 83 (not (or (and (boundp 'epoch::version) epoch::version) 84 (string-lessp emacs-version "19"))) 85 "Is this emacs v19?") 86 87;;; This has to be here rather than at the bottom of this file with 88;;; the other utility functions because it is used by 89;;; gnats::get-config, which is called when send-pr.el is being 90;;; loaded (see the "defconst" below), before the whole file has been 91;;; loaded. 92 93(defun gnats::find-safe-default-directory (&optional buffer) 94"If the directory referred to by `default-directory' for the current 95buffer (or for optional argument BUFFER) does not exist, set it to the home 96directory of the current user if that exists, or to `/'. 97 98Returns the final value of default-directory in the buffer." 99 (let ((homedir (expand-file-name "~/"))) 100 (save-excursion 101 (if buffer (set-buffer buffer)) 102 (if (not (file-exists-p default-directory)) 103 (if (file-exists-p homedir) 104 (setq default-directory homedir) 105 (setq default-directory "/"))) 106 default-directory))) 107 108;;; These may be changed during configuration/installation or by the individual 109;;; user in his/her .emacs file. 110;;; 111(defun gnats::get-config (var) 112 (let ((shell-file-name "/bin/sh") 113 (buf (generate-new-buffer " *GNATS config*")) 114 ret) 115 (save-excursion 116 (set-buffer buf) 117 (shell-command-on-region 118 (point-min) (point-max) 119 (concat ". " gnats:root "/gnats-adm/config; echo $" var ) t) 120 (goto-char (point-min)) 121 ; We have to use get-buffer, since shell-command-on-region will wipe 122 ; out the buffer if there's no output from the command. 123 (if (or (not (get-buffer "*Shell Command Output*")) 124 (looking-at "/bin/sh:\\|\.:\\|\n")) 125 (setq ret nil) 126 (setq ret (buffer-substring (point-min) (- (point-max) 1))))) 127 (kill-buffer buf) 128 ret)) 129 130;; const because it must match the script's value 131(defconst send-pr:datadir (or (gnats::get-config "DATADIR") "xDATADIRx") 132 "*Where the `gnats' subdirectory containing category lists lives.") 133 134(defvar send-pr::sites nil 135 "List of GNATS support sites; computed at runtime.") 136(defvar send-pr:default-site 137 (or (gnats::get-config "GNATS_SITE") "xGNATS_SITEx") 138 "Default site to send bugs to.") 139(defvar send-pr:::site send-pr:default-site 140 "The site to which a problem report is currently being submitted, or NIL 141if using the default site (buffer local).") 142 143(defvar send-pr:::categories nil 144 "Buffer local list of available categories, derived at runtime from 145send-pr:::site and send-pr::category-alist.") 146(defvar send-pr::category-alist nil 147 "Alist of GNATS support sites and the categories supported at each; computed 148at runtime.") 149 150;;; Ideally we would get all the following values from a central database 151;;; during runtime instead of having them here in the code. 152;;; 153(defconst send-pr::fields 154 `(("Category" send-pr::set-categories 155 ,(or (gnats::get-config "DEFAULT_CATEGORY") nil) enum) 156 ("Class" (("sw-bug") ("doc-bug") ("change-request") ("support")) 157 ,(or (gnats::get-config "DEFAULT_CLASS") 0) enum) 158 ("Confidential" (("yes") ("no")) 159 ,(or (gnats::get-config "DEFAULT_CONFIDENTIAL") 1) enum) 160 ("Severity" (("non-critical") ("serious") ("critical")) 161 ,(or (gnats::get-config "DEFAULT_SEVERITY") 1) enum) 162 ("Priority" (("low") ("medium") ("high")) 163 ,(or (gnats::get-config "DEFAULT_PRIORITY") 1) enum) 164 ("Release" nil 165 ,(or (gnats::get-config "DEFAULT_RELEASE") "`uname -s` `uname -r`") 166 text) 167 ("Submitter-Id" nil 168 ,(or (gnats::get-config "SUBMITTER") "net") text) 169 ("Synopsis" nil nil text 170 (lambda (a b c) (gnats::set-mail-field "Subject" c)))) 171 "AList, keyed on the name of the field, of: 1721) The field name. 1732) The list of completions. This can be a list, a function to call, or nil. 1743) The default value. 1754) The type of the field. 1765) A sub-function to call when changed.") 177 178(defvar gnats::fields nil) 179 180(defmacro gnats::push (i l) 181 `(setq ,l (cons ,@ (list i l)))) 182 183(defun send-pr::set-categories (&optional arg) 184 "Get the list of categories for the current site out of 185send-pr::category-alist if there or from send-pr if not. With arg, force 186update." 187 ;; 188 (let ((entry (assoc send-pr:::site send-pr::category-alist))) 189 (or (and entry (null arg)) 190 (let ((oldpr (getenv "GNATS_ROOT")) cats) 191 (send-pr::set-sites arg) 192 (setenv "GNATS_ROOT" gnats:root) 193 (setq cats (gnats::get-value-from-shell 194 "send-pr" "-CL" send-pr:::site)) 195 (setenv "GNATS_ROOT" oldpr) 196 (if entry (setcdr entry cats) 197 (setq entry (cons send-pr:::site cats)) 198 (gnats::push entry send-pr::category-alist)))) 199 (setq send-pr:::categories (cdr entry)))) 200 201(defun send-pr::set-sites (&optional arg) 202 "Get the list of sites (by listing the contents of DATADIR/gnats) and assign 203it to send-pr::sites. With arg, force update." 204 (or (and (null arg) send-pr::sites) 205 (progn 206 (setq send-pr::sites nil) 207 (mapcar 208 (function 209 (lambda (file) 210 (or (memq t (mapcar (function (lambda (x) (string= x file))) 211 '("." ".." "pr-edit" "pr-addr"))) 212 (not (file-readable-p file)) 213 (gnats::push (list (file-name-nondirectory file)) 214 send-pr::sites)))) 215 (directory-files (format "%s/gnats" send-pr:datadir) t)) 216 (setq send-pr::sites (reverse send-pr::sites))))) 217 218(defconst send-pr::pr-buffer-name "*send-pr*" 219 "Name of the temporary buffer, where the problem report gets composed.") 220 221(defconst send-pr::err-buffer-name "*send-pr-error*" 222 "Name of the temporary buffer, where send-pr error messages appear.") 223 224(defvar send-pr:::err-buffer nil 225 "The error buffer used by the current PR buffer.") 226 227(defvar send-pr:::spawn-to-send nil 228 "Whether or not send-pr-mode should spawn a send-pr process to send the PR.") 229 230(defconst gnats::indent 16 "Indent for formatting the value.") 231 232;;;;--------------------------------------------------------------------------- 233;;;; `send-pr' - command for creating and sending of problem reports 234;;;;--------------------------------------------------------------------------- 235 236(fset 'send-pr 'send-pr:send-pr) 237(defun send-pr:send-pr (&optional site) 238 "Create a buffer and read in the result of `send-pr -P'. 239When finished with editing the problem report use \\[send-pr:submit-pr] 240to send the PR with `send-pr -b -f -'." 241 ;; 242 (interactive 243 (if current-prefix-arg 244 (list (completing-read "Site: " (send-pr::set-sites 'recheck) nil t 245 send-pr:default-site)))) 246 (or site (setq site send-pr:default-site)) 247 (let ((buf (get-buffer send-pr::pr-buffer-name))) 248 (if (or (not buf) 249 (progn (switch-to-buffer buf) 250 (cond ((or (not (buffer-modified-p buf)) 251 (y-or-n-p "Erase previous problem report? ")) 252 (erase-buffer) t) 253 (t nil)))) 254 (send-pr::start-up site)))) 255 256(defun send-pr::start-up (site) 257 (switch-to-buffer (get-buffer-create send-pr::pr-buffer-name)) 258 (setq default-directory (expand-file-name "~/")) 259 (auto-save-mode auto-save-default) 260 (let ((oldpr (getenv "GNATS_ROOT")) 261 (case-fold-search nil)) 262 (setenv "GNATS_ROOT" gnats:root) 263 (send-pr::insert-template site) 264 (setenv "GNATS_ROOT" oldpr) 265 (goto-char (point-min)) 266 (if (looking-at "send-pr:") 267 (cond ((looking-at "send-pr: .* does not have a categories list") 268 (setq send-pr::sites nil) 269 (error "send-pr: the GNATS site %s does not have a categories list" site)) 270 (t (error (buffer-substring (point-min) (point-max))))) 271 (save-excursion 272 ;; Clear cruft inserted by bdamaged .cshrcs 273 (goto-char 1) 274 (re-search-forward "^SEND-PR:") 275 (delete-region 1 (match-beginning 0))))) 276 (set-buffer-modified-p nil) 277 (send-pr:send-pr-mode) 278 (setq send-pr:::site site) 279 (setq send-pr:::spawn-to-send t) 280 (send-pr::set-categories) 281 (if (null send-pr:::categories) 282 (progn 283 (and send-pr:::err-buffer (kill-buffer send-pr:::err-buffer)) 284 (kill-buffer nil) 285 (message "send-pr: no categories found")) 286 (if (eq mail-default-reply-to t) 287 (setq mail-default-reply-to (getenv "REPLYTO"))) 288 (and mail-default-reply-to 289 (gnats::set-mail-field "Reply-To" mail-default-reply-to)) 290 (and mail-self-blind 291 (gnats::set-mail-field "BCC" (user-login-name))) 292 (mapcar 'send-pr::maybe-change-field send-pr::fields) 293 (gnats::position-on-field "Description") 294 (message (substitute-command-keys 295 "To send the problem report use: \\[send-pr:submit-pr]")))) 296 297(defvar send-pr::template-alist nil 298 "An alist containing the output of send-pr -P <sitename> for various sites.") 299 300(defun send-pr::insert-template (site) 301 (let ((elt (assoc site send-pr::template-alist))) 302 (if elt 303 (save-excursion (insert (cdr elt))) 304 (shell-command (concat "send-pr -P " site) t) 305 (save-excursion 306 (setq send-pr::template-alist 307 (cons (cons site (buffer-substring (point-min) (point-max))) 308 send-pr::template-alist)))))) 309 310(fset 'do-send-pr 'send-pr:submit-pr) ;backward compat 311(defun send-pr:submit-pr () 312 "Pipe the contents of the buffer *send-pr* to `send-pr -f -.' unless this 313buffer was loaded with emacsclient, in which case save the buffer and exit." 314 ;; 315 (interactive) 316 (cond 317 ((and (boundp 'server-buffer-clients) 318 server-buffer-clients) 319 (let ((buffer (current-buffer)) 320 (version-control nil) (buffer-backed-up nil)) 321 (save-buffer buffer) 322 (kill-buffer buffer) 323 (server-buffer-done buffer))) 324 (send-pr:::spawn-to-send 325 (or (and send-pr:::err-buffer 326 (buffer-name send-pr:::err-buffer)) 327 (setq send-pr:::err-buffer 328 (get-buffer-create send-pr::err-buffer-name))) 329 (let ((err-buffer send-pr:::err-buffer) mesg ok) 330 (save-excursion (set-buffer err-buffer) (erase-buffer)) 331 (message "running send-pr...") 332 (let ((oldpr (getenv "GNATS_ROOT"))) 333 (setenv "GNATS_ROOT" gnats:root) 334 (call-process-region (point-min) (point-max) "send-pr" 335 nil err-buffer nil send-pr:::site 336 "-b" "-f" "-") 337 (setenv "GNATS_ROOT" oldpr)) 338 (message "running send-pr...done") 339 ;; stupidly we cannot check the return value in EMACS 18.57, thus we need 340 ;; this kluge to find out whether send-pr succeeded. 341 (if (save-excursion 342 (set-buffer err-buffer) 343 (goto-char (point-min)) 344 (setq mesg (buffer-substring (point-min) (- (point-max) 1))) 345 (search-forward "problem report sent" nil t)) 346 (progn (message mesg) 347 (kill-buffer err-buffer) 348 (delete-auto-save-file-if-necessary) 349 (set-buffer-modified-p nil) 350 (bury-buffer)) 351 (pop-to-buffer err-buffer)) 352 )) 353 (t 354 (save-buffer) 355 (message "Exit emacs to send the PR.")))) 356 357;;;;--------------------------------------------------------------------------- 358;;;; send-pr:send-pr-mode mode 359;;;;--------------------------------------------------------------------------- 360 361(defvar send-pr-mode-map 362 (let ((map (make-sparse-keymap))) 363 (define-key map "\C-c\C-c" 'send-pr:submit-pr) 364 (define-key map "\C-c\C-f" 'gnats:change-field) 365 (define-key map "\M-n" 'gnats:next-field) 366 (define-key map "\M-p" 'gnats:previous-field) 367 (define-key map "\C-\M-f" 'gnats:forward-field) 368 (define-key map "\C-\M-b" 'gnats:backward-field) 369 map) 370 "Keymap for send-pr mode.") 371 372(defconst gnats::keyword "^>\\([-a-zA-Z]+\\):") 373(defconst gnats::before-keyword "[ \t\n\f]*[\n\f]+>\\([-a-zA-Z]+\\):") 374(defconst gnats::after-keyword "^>\\([-a-zA-Z]+\\):[ \t\n\f]+") 375 376(fset 'send-pr-mode 'send-pr:send-pr-mode) 377(defun send-pr:send-pr-mode () 378 "Major mode for submitting problem reports. 379For information about the form see gnats(1) and send-pr(1). 380Special commands: \\{send-pr-mode-map} 381Turning on send-pr-mode calls the value of the variable send-pr-mode-hook, 382if it is not nil." 383 (interactive) 384 (gnats::patch-exec-path) 385 (put 'send-pr:send-pr-mode 'mode-class 'special) 386 (kill-all-local-variables) 387 (setq major-mode 'send-pr:send-pr-mode) 388 (setq mode-name "send-pr") 389 (use-local-map send-pr-mode-map) 390 (set-syntax-table text-mode-syntax-table) 391 (setq local-abbrev-table text-mode-abbrev-table) 392 (setq buffer-offer-save t) 393 (make-local-variable 'send-pr:::site) 394 (make-local-variable 'send-pr:::categories) 395 (make-local-variable 'send-pr:::err-buffer) 396 (make-local-variable 'send-pr:::spawn-to-send) 397 (make-local-variable 'paragraph-separate) 398 (setq paragraph-separate (concat (default-value 'paragraph-separate) 399 "\\|" gnats::keyword "[ \t\n\f]*$")) 400 (make-local-variable 'paragraph-start) 401 (setq paragraph-start (concat (default-value 'paragraph-start) 402 "\\|" gnats::keyword)) 403 (run-hooks 'send-pr-mode-hook) 404 t) 405 406;;;;--------------------------------------------------------------------------- 407;;;; Functions to read and replace field values. 408;;;;--------------------------------------------------------------------------- 409 410(defun gnats::position-on-field (field &optional quiet) 411 (goto-char (point-min)) 412 (if (not (re-search-forward (concat "^>" field ":") nil t)) 413 (if quiet 414 nil 415 (error "Field `>%s:' not found." field)) 416 (re-search-forward "[ \t\n\f]*") 417 (if (looking-at gnats::keyword) 418 (backward-char 1)) 419 t)) 420 421(defun gnats::mail-position-on-field (field) 422 (let (end 423 (case-fold-search t)) 424 (goto-char (point-min)) 425 (re-search-forward "^$") 426 (setq end (match-beginning 0)) 427 (goto-char (point-min)) 428 (if (not (re-search-forward (concat "^" field ":") end 'go-to-end)) 429 (insert field ": \n") 430 (re-search-forward "[ \t\n\f]*")) 431 (skip-chars-backward "\n") 432 t)) 433 434(defun gnats::field-contents (field &optional elem move) 435 (let (pos) 436 (unwind-protect 437 (save-excursion 438 (if (not (gnats::position-on-field field t)) 439 nil 440 (setq pos (point-marker)) 441 (if (or (looking-at "<.*>$") (eolp)) 442 t 443 (looking-at ".*$") ; to set match-{beginning,end} 444 (gnats::nth-word 445 (buffer-substring (match-beginning 0) (match-end 0)) 446 elem)))) 447 (and move pos (goto-char pos))))) 448 449(defun gnats::functionp (thing) 450 (or (and (symbolp thing) (fboundp thing)) 451 (and (listp thing) (eq (car thing) 'lambda)))) 452 453(defun gnats::field-values (field) 454 "Return the possible (known) values for field FIELD." 455 (let* ((fields (if (eq major-mode 'gnats:gnats-mode) gnats::fields 456 send-pr::fields)) 457 (thing (elt (assoc field fields) 1))) 458 (cond ((gnats::functionp thing) (funcall thing)) 459 ((listp thing) thing) 460 (t (error "ACK"))))) 461 462(defun gnats::field-default (field) 463 "Return the default value for field FIELD." 464 (let* ((fields (if (eq major-mode 'gnats:gnats-mode) gnats::fields 465 send-pr::fields)) 466 (thing (elt (assoc field fields) 2))) 467 (cond ((stringp thing) thing) 468 ((null thing) "") 469 ((numberp thing) (car (elt (gnats::field-values field) thing))) 470 ((gnats::functionp thing) 471 (funcall thing (gnats::field-contents field))) 472 ((eq thing t) (gnats::field-contents field)) 473 (t (error "ACK"))))) 474 475(defun gnats::field-type (field) 476 "Return the type of field FIELD." 477 (let* ((fields (if (eq major-mode 'gnats:gnats-mode) gnats::fields 478 send-pr::fields)) 479 (thing (elt (assoc field fields) 3))) 480 thing)) 481 482(defun gnats::field-action (field) 483 "Return the extra handling function for field FIELD." 484 (let* ((fields (if (eq major-mode 'gnats:gnats-mode) gnats::fields 485 send-pr::fields)) 486 (thing (elt (assoc field fields) 4))) 487 (cond ((null thing) 'ignore) 488 ((gnats::functionp thing) thing) 489 (t (error "ACK"))))) 490 491;;;;--------------------------------------------------------------------------- 492;;;; Point movement functions 493;;;;--------------------------------------------------------------------------- 494 495(or (fboundp 'defsubst) (fset 'defsubst 'defun)) 496 497(defun send-pr::maybe-change-field (field) 498 (setq field (car field)) 499 (let ((thing (gnats::field-contents field))) 500 (and thing (eq t thing) 501 (not (eq 'multi-text (gnats::field-type field))) 502 (gnats:change-field field)))) 503 504(defun gnats:change-field (&optional field default) 505 "Change the value of the field containing the cursor. With arg, ask the 506user for the field to change. From a program, the function takes optional 507arguments of the field to change and the default value to use." 508 (interactive) 509 (or field current-prefix-arg (setq field (gnats::current-field))) 510 (or field 511 (setq field 512 (completing-read "Field: " 513 (if (eq major-mode 'gnats:gnats-mode) 514 gnats::fields 515 send-pr::fields) 516 nil t))) 517 (gnats::position-on-field field) 518 (sit-for 0) 519 (let* ((old (gnats::field-contents field)) 520 new) 521 (if (null old) 522 (error "ACK") 523 (if (or (interactive-p) t) 524 (let ((prompt (concat ">" field ": ")) 525 (domain (gnats::field-values field)) 526 (type (gnats::field-type field))) 527 (or default (setq default (gnats::field-default field))) 528 (setq new 529 (if (eq type 'enum) 530 (completing-read prompt domain nil t 531 (if gnats::emacs-19p (cons default 0) 532 default)) 533 (read-string prompt (if gnats::emacs-19p (cons default 1) 534 default))))) 535 (setq new default)) 536 (gnats::set-field field new) 537 (funcall (gnats::field-action field) field old new) 538 new))) 539 540(defun gnats::set-field (field value) 541 (save-excursion 542 (gnats::position-on-field field) 543 (delete-horizontal-space) 544 (looking-at ".*$") 545 (replace-match 546 (concat (make-string (- gnats::indent (length field) 2) ?\40 ) value) t))) 547 548(defun gnats::set-mail-field (field value) 549 (save-excursion 550 (gnats::mail-position-on-field field) 551 (delete-horizontal-space) 552 (looking-at ".*$") 553 (replace-match (concat " " value) t))) 554 555(defun gnats::before-keyword (&optional where) 556 "Returns t if point is in some white space before a keyword. 557If where is nil, then point is not changed; if where is t then point is moved 558to the beginning of the keyword, otherwise it is moved to the beginning 559of the white space it was in." 560 ;; 561 (if (looking-at gnats::before-keyword) 562 (prog1 t 563 (cond ((eq where t) 564 (re-search-forward "^>") (backward-char)) 565 ((not (eq where nil)) 566 (re-search-backward "[^ \t\n\f]") (forward-char)))) 567 nil)) 568 569(defun gnats::after-keyword (&optional where) 570 "Returns t if point is in some white space after a keyword. 571If where is nil, then point is not changed; if where is t then point is moved 572to the beginning of the keyword, otherwise it is moved to the end of the white 573space it was in." 574 ;; 575 (if (gnats::looking-after gnats::after-keyword) 576 (prog1 t 577 (cond ((eq where t) 578 (re-search-backward "^>")) 579 ((not (eq where nil)) 580 (re-search-forward "[^ \t\n\f]") (backward-char)))) 581 nil)) 582 583(defun gnats::in-keyword (&optional where) 584 "Returns t if point is within a keyword. 585If where is nil, then point is not changed; if where is t then point is moved 586to the beginning of the keyword." 587 ;; 588 (let ((old-point (point-marker))) 589 (beginning-of-line) 590 (cond ((and (looking-at gnats::keyword) 591 (< old-point (match-end 0))) 592 (prog1 t 593 (if (eq where t) 594 t 595 (goto-char old-point)))) 596 (t (goto-char old-point) 597 nil)))) 598 599(defun gnats::forward-bofield () 600 "Moves point to the beginning of a field. Assumes that point is in the 601keyword." 602 ;; 603 (if (re-search-forward "[ \t\n\f]+[^ \t\n\f]" (point-max) '-) 604 (backward-char) 605 t)) 606 607(defun gnats::backward-eofield () 608 "Moves point to the end of a field. Assumes point is in the keyword." 609 ;; 610 (if (re-search-backward "[^ \t\n\f][ \t\n\f]+" (point-min) '-) 611 (forward-char) 612 t)) 613 614(defun gnats::forward-eofield () 615 "Moves point to the end of a field. Assumes that point is in the field." 616 ;; 617 ;; look for the next field 618 (if (re-search-forward gnats::keyword (point-max) '-) 619 (progn (beginning-of-line) (gnats::backward-eofield)) 620 (re-search-backward "[^ \t\n\f][ \t\n\f]*" (point-min) '-) 621 (forward-char))) 622 623(defun gnats::backward-bofield () 624 "Moves point to the beginning of a field. Assumes that point is in the 625field." 626 ;; 627 ;;look for previous field 628 (if (re-search-backward gnats::keyword (point-min) '-) 629 (gnats::forward-bofield) 630 t)) 631 632 633(defun gnats:forward-field () 634 "Move point forward to the end of the field or to the beginning of the next 635field." 636 ;; 637 (interactive) 638 (if (or (gnats::before-keyword t) (gnats::in-keyword t) 639 (gnats::after-keyword t)) 640 (gnats::forward-bofield) 641 (gnats::forward-eofield))) 642 643(defun gnats:backward-field () 644 "Move point backward to the beginning/end of a field." 645 ;; 646 (interactive) 647 (backward-char) 648 (if (or (gnats::before-keyword t) (gnats::in-keyword t) 649 (gnats::after-keyword t)) 650 (gnats::backward-eofield) 651 (gnats::backward-bofield))) 652 653(defun gnats:next-field () 654 "Move point to the beginning of the next field." 655 ;; 656 (interactive) 657 (if (or (gnats::before-keyword t) (gnats::in-keyword t) 658 (gnats::after-keyword t)) 659 (gnats::forward-bofield) 660 (if (re-search-forward gnats::keyword (point-max) '-) 661 (gnats::forward-bofield) 662 t))) 663 664(defun gnats:previous-field () 665 "Move point to the beginning of the previous field." 666 ;; 667 (interactive) 668 (backward-char) 669 (if (or (gnats::after-keyword t) (gnats::in-keyword t) 670 (gnats::before-keyword t)) 671 (progn (re-search-backward gnats::keyword (point-min) '-) 672 (gnats::forward-bofield)) 673 (gnats::backward-bofield))) 674 675(defun gnats:beginning-of-field () 676 "Move point to the beginning of the current field." 677 (interactive) 678 (cond ((gnats::in-keyword t) 679 (gnats::forward-bofield)) 680 ((gnats::after-keyword 0)) 681 (t 682 (gnats::backward-bofield)))) 683 684(defun gnats::current-field () 685 (save-excursion 686 (if (cond ((or (gnats::in-keyword t) (gnats::after-keyword t)) 687 (looking-at gnats::keyword)) 688 ((re-search-backward gnats::keyword nil t))) 689 (buffer-substring (match-beginning 1) (match-end 1)) 690 nil))) 691 692;;;;--------------------------------------------------------------------------- 693;;;; Support functions 694;;;;--------------------------------------------------------------------------- 695 696(defun gnats::looking-after (regex) 697 "Returns t if point is after regex." 698 ;; 699 (let* ((old-point (point)) 700 (start (if (eobp) 701 old-point 702 (forward-char) (point)))) 703 (cond ((re-search-backward regex (point-min) t) 704 (goto-char old-point) 705 (cond ((eq (match-end 0) start) 706 t)))))) 707 708(defun gnats::nth-word (string &optional elem) 709 "Returns the elem-th word of the string. 710If elem is nil, then the first wort is returned, if elem is 0 then 711the whole string is returned." 712 ;; 713 (if (integerp elem) 714 (cond ((eq elem 0) string) 715 ((eq elem 1) (gnats::first-word string)) 716 ((equal string "") "") 717 ((>= elem 2) 718 (let ((i 0) (value "")) 719 (setq string ; strip leading blanks 720 (substring string (or (string-match "[^ \t]" string) 0))) 721 (while (< i elem) 722 (setq value 723 (substring string 0 724 (string-match "[ \t]*$\\|[ \t]+" string))) 725 (setq string 726 (substring string (match-end 0))) 727 (setq i (+ i 1))) 728 value))) 729 (gnats::first-word string))) 730 731(defun gnats::first-word (string) 732 (setq string 733 (substring string (or (string-match "[^ \t]" string) 0))) 734 (substring string 0 (string-match "[ \t]*$\\|[ \t]+" string))) 735 736;;;;--------------------------------------------------------------------------- 737 738(defun gnats::patch-exec-path () 739 ;; 740 "Replaces `//' by `/' in `exec-path'." 741 ;; 742 ;(make-local-variable 'exec-path) 743 (let ((err-buffer (get-buffer-create " *gnats::patch-exec-path*")) 744 (ret)) 745 (setq exec-path (save-excursion (set-buffer err-buffer) 746 (prin1 exec-path err-buffer) 747 (goto-char (point-min)) 748 (replace-string "//" "/") 749 (goto-char (point-min)) 750 (setq ret (read err-buffer)) 751 (kill-buffer err-buffer) 752 ret 753 )))) 754 755(defun gnats::get-value-from-shell (&rest command) 756 "Execute shell command to get a list of valid values for `variable'." 757 ;; 758 (let ((err-buffer (get-buffer-create " *gnats::get-value-from-shell*"))) 759 (save-excursion 760 (set-buffer err-buffer) 761 (unwind-protect 762 (condition-case var 763 (progn 764 (apply 'call-process 765 (car command) nil err-buffer nil (cdr command)) 766 (goto-char (point-min)) 767 (if (looking-at "[-a-z]+: ") 768 (error (buffer-substring (point-min) (point-max)))) 769 (read err-buffer)) 770 (error nil)) 771 (kill-buffer err-buffer))))) 772 773(or (fboundp 'setenv) 774 (defun setenv (variable &optional value) 775 "Set the value of the environment variable named VARIABLE to VALUE. 776VARIABLE should be a string. VALUE is optional; if not provided or is 777`nil', the environment variable VARIABLE will be removed. 778This function works by modifying `process-environment'." 779 (interactive "sSet environment variable: \nsSet %s to value: ") 780 (if (string-match "=" variable) 781 (error "Environment variable name `%s' contains `='" variable) 782 (let ((pattern (concat "\\`" (regexp-quote (concat variable "=")))) 783 (case-fold-search nil) 784 (scan process-environment)) 785 (while scan 786 (cond 787 ((string-match pattern (car scan)) 788 (if (eq nil value) 789 (setq process-environment (delq (car scan) 790 process-environment)) 791 (setcar scan (concat variable "=" value))) 792 (setq scan nil)) 793 ((null (setq scan (cdr scan))) 794 (setq process-environment 795 (cons (concat variable "=" value) 796 process-environment))))))))) 797 798;;;; end of send-pr.el 799