Article View: comp.emacs
Article #3534enhancements to compile.el
From: Michael Cook
Date: Sat, 23 Jan 1993 22:16
Date: Sat, 23 Jan 1993 22:16
463 lines
16694 bytes
16694 bytes
I've made some enhancements to compile.el to overcome some of its limitations.
- Added (previous-error)
- Added (rename-compilation-buffer) and made most variables buffer-local
(to the *compilation* buffer) so that you can have multiple compilation
processes active simultaneously.
- "No more errors yet" also moves window-start to EOF (so that it becomes
easier to see that new output is available).
- (grep) remembers its last command, just like (compile) always did.
Bug fixes:
- Won't try to read a file named "" (the null string).
- Gracefully handles the situation where a buffer that had markers in
it has been deleted.
Michael.
#!/bin/sh
# to extract, remove the header and type "sh filename"
if `test ! -s ./compile.el`
then
echo "writing ./compile.el"
cat > ./compile.el << '\End\Of\Shar\'
;; Run compiler as inferior of Emacs, and parse its error messages.
;; Copyright (C) 1985, 1986 Free Software Foundation, Inc.
;; This file is part of GNU Emacs.
;; GNU Emacs is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY. No author or distributor
;; accepts responsibility to anyone for the consequences of using it
;; or for whether it serves any particular purpose or works at all,
;; unless he says so in writing. Refer to the GNU Emacs General Public
;; License for full details.
;; Everyone is granted permission to copy, modify and redistribute
;; GNU Emacs, but only under the conditions described in the
;; GNU Emacs General Public License. A copy of this license is
;; supposed to have been given to you along with GNU Emacs so you
;; can know your rights and responsibilities. It should be in a
;; file named COPYING. Among other things, the copyright notice
;; and this notice must be preserved on all copies.
;;
;; mcook@merlin.dev.mot.com
;; January 22, 1993
;;
;; Enhancements:
;; - Added (previous-error)
;; - Added (rename-compilation-buffer) and made most variables buffer-local
;; (to the *compilation* buffer) so that you can have multiple compilation
;; processes active simultaneously.
;; - "No more errors yet" also moves window-start to EOF (so that it becomes
;; easier to see that new output is available).
;; - (grep) remembers its last command, just like (compile) always did.
;;
;; Bug fixes:
;; - Won't try to read a file named "" (the null string).
;; - Gracefully handles the situation where a buffer that had markers in
;; it has been deleted.
;;
(provide 'compile)
(defvar compilation-process nil
"Process created by compile command, or nil if none exists now.
Note that the process may have been \"deleted\" and still
be the value of this variable.")
(make-variable-buffer-local 'compilation-process)
(defvar compilation-error-list nil
"List of error message descriptors for visiting erring functions.
Each error descriptor is a list of length two.
Its car is a marker pointing to an error message.
Its cadr is a marker pointing to the text of the line the message is about,
or nil if that is not interesting.
The value may be t instead of a list;
this means that the buffer of error messages should be reparsed
the next time the list of errors is wanted.")
(make-variable-buffer-local 'compilation-error-list)
(defvar compilation-error-index nil
"Index into compilation-error-list of the current error.")
(make-variable-buffer-local 'compilation-error-index)
(defvar compilation-parsing-end nil
"Position of end of buffer when last error messages parsed.")
(make-variable-buffer-local 'compilation-parsing-end)
(defvar compilation-error-message nil
"Message to print when no more matches for compilation-error-regexp are found")
(make-variable-buffer-local 'compilation-error-message)
(defvar compilation-error-regexp
"\\([^ \n]+\\(: *\\|, line \\|(\\)[0-9]+\\)\\|\\([0-9]+ *of *[^ \n]+\\)"
"Regular expression for filename/linenumber in error in compilation log.")
(defun compile (command)
"Compile the program including the current buffer. Default: run make.
Runs COMMAND, a shell command, in a separate process asynchronously
with output going to the buffer *compilation*.
You can then use the command \\[next-error] to find the next error message
and move to the source code that caused it."
(interactive (list (read-string "Compile command: " compile-command)))
(setq compile-command command)
(compile1 compile-command "errors"))
(defvar grep-command "grep -n "
"*Last shell command used to do a grep class command;
default for next grep class command.")
(defun grep (command)
"Run grep, with user-specified args, and collect output in a buffer.
While grep runs asynchronously, you can use the \\[next-error] command
to find the text that grep hits refer to."
(interactive (list (read-string "Run grep (with args) " grep-command)))
(setq grep-command command)
(require 'compile)
(compile1 (concat command " /dev/null") "grep hits" "grep"))
(defun compile1 (command error-message &optional name-of-mode)
(save-some-buffers)
(let ((obuf (current-buffer))
(this-dir default-directory)
(cbuf (get-buffer-create "*compilation*")))
(unwind-protect
(progn
(set-buffer cbuf)
(if compilation-process
(if (or (not (eq (process-status compilation-process) 'run))
(yes-or-no-p
"A compilation process is running; kill it? "))
(condition-case ()
(let ((comp-proc compilation-process))
(interrupt-process comp-proc)
(sit-for 1)
(delete-process comp-proc))
(error nil))
(error "Cannot have two compilation processes")))
(setq compilation-process nil)
;;
;; Setup the *compilation* buffer.
;;
(buffer-flush-undo cbuf)
(compilation-forget-errors)
(setq compilation-error-list t)
(setq compilation-error-index nil)
(setq compilation-error-message error-message)
(setq mode-name (or name-of-mode "Compilation"))
(setq default-directory this-dir)
(with-output-to-temp-buffer "*compilation*"
(princ "cd ")
(princ this-dir)
(terpri)
(princ command)
(terpri))
;;
;; For all windows displaying the *compilation* buffer, set the
;; window-start and window-point attributes.
;;
(let* ((orig-win (selected-window))
(this-win orig-win)
(pmin (point-min))
(pmax (point-max)))
(while (progn
(if (eq (window-buffer this-win) cbuf)
(progn
(set-window-point this-win pmax)
(set-window-start this-win pmin)))
(setq this-win (next-window this-win t))
(not (eq this-win orig-win)))))
;;
;; Start the process.
;;
(setq compilation-process
(start-process "compilation" cbuf
shell-file-name
"-c" (concat "exec " command)))
(set-process-sentinel compilation-process 'compilation-sentinel)
;; Make log buffer's mode line show process state
(setq mode-line-process '(": %s")))
(set-buffer obuf))))
(defun compilation-sentinel (proc msg)
;;
;; Called when compilation process changes state.
;;
(cond ((null (buffer-name (process-buffer proc)))
;;
;; buffer killed
;;
(set-process-buffer proc nil))
((memq (process-status proc) '(signal exit))
(let* ((obuf (current-buffer))
(omax (point-max))
(opoint (point)))
(unwind-protect
(progn
;;
;; Write something in *compilation* and hack its mode line,
;;
(set-buffer (process-buffer proc))
(setq compilation-process nil)
(goto-char (point-max))
(insert ?\n mode-name " " msg)
(forward-char -1)
(insert " at "
(substring (current-time-string) 0 -5))
(forward-char 1)
(if (< opoint omax)
(goto-char opoint))
(setq mode-line-process
(concat ": "
(symbol-name (process-status proc))))
;; Force mode line redisplay soon
(set-buffer-modified-p (buffer-modified-p))
;;
;; If buffer and mode line will show that the process is
;; dead, we can delete it now. Otherwise it will stay around
;; until M-x list-processes.
;;
(delete-process proc))
(set-buffer obuf))))))
(defun kill-compilation ()
"Kill the process made by the \\[compile] command."
(interactive)
(save-excursion
(set-buffer "*compilation*")
(if compilation-process
(interrupt-process compilation-process))))
(defun kill-grep ()
"Kill the process made by the \\[grep] command."
(interactive)
(save-excursion
(set-buffer "*compilation*")
(if compilation-process
(interrupt-process compilation-process))))
(defun next-error (&optional argp)
"Visit next compilation error message and corresponding source code. This
operates on the output from the \\[compile] command.
If all preparsed error messages have been processed, the error message buffer
is checked for new ones. A non-nil argument (prefix arg, if interactive)
means reparse the error message buffer and start at the first error."
(interactive "P")
(compilation-next-error argp 1))
(defun previous-error (&optional argp)
"See \\[next-error]."
(interactive "P")
(compilation-next-error argp -1))
(defun compilation-next-error (restart direction)
"See \\[next-error]."
(let ((obuf (current-buffer)))
(unwind-protect
(progn
(set-buffer "*compilation*")
(if (or (eq compilation-error-list t)
restart)
(compilation-forget-errors))
(let ((i compilation-error-index))
(while (progn
(setq i (if i (+ i direction) 0))
(if (< i 0)
(error (concat "No previous "
compilation-error-message)))
(let ((n (length compilation-error-list)))
(if (< i n)
nil
(set-buffer-modified-p nil)
(compilation-parse-errors)
(or (> (length compilation-error-list) n)
(error (concat
"No more "
compilation-error-message
(if (and compilation-process
(eq (process-status
compilation-process) 'run))
(let ((w (get-buffer-window
(current-buffer))))
(if (not w)
nil
(set-window-point w (point-max))
(set-window-start w (point-max)))
" yet")))))))
(not (marker-buffer
(nth 1 (nth i compilation-error-list))))))
(setq compilation-error-index i))
(let ((e (nth compilation-error-index compilation-error-list)))
(if (car (cdr e))
(progn
(setq obuf (marker-buffer (car (cdr e))))
(switch-to-buffer obuf)
(goto-char (car (cdr e)))))
(let* ((pop-up-windows t)
(w (display-buffer (marker-buffer (car e)))))
(set-window-point w (car e))
(set-window-start w (car e)))))
(set-buffer obuf))))
;;
;; Set compilation-error-list to nil, and unchain the markers that point to
;; the error messages and their text, so that they no longer slow down gap
;; motion. This would happen anyway at the next garbage collection, but it is
;; better to do it right away.
;;
(defun compilation-forget-errors ()
(if (eq compilation-error-list t)
(setq compilation-error-list nil))
(while compilation-error-list
(let ((e (car compilation-error-list)))
(set-marker (car e) nil)
(if (car (cdr e))
(set-marker (car (cdr e)) nil)))
(setq compilation-error-list (cdr compilation-error-list)))
(setq compilation-error-index nil)
(setq compilation-parsing-end 1))
(defun compilation-parse-errors ()
"Parse the current buffer as error messages. This makes a list of error
descriptors, compilation-error-list. For each source-file, line-number pair
in the buffer, the source file is read in, and the text location is saved in
compilation-error-list. The function next-error, assigned to
\\[next-error], takes the next error off the list and visits its location."
(message "Parsing error messages...")
(let (text-buffer last-filename last-linenum error-list)
;;
;; Don't reparse messages already seen at last parse.
;;
(goto-char compilation-parsing-end)
;;
;; Don't parse the first two lines as error messages. This matters for
;; grep.
;;
(if (bobp)
(forward-line 2))
(while (re-search-forward compilation-error-regexp nil t)
(let (linenum filename error-marker)
;;
;; Extract file name and line number from error message.
;;
(save-restriction
(narrow-to-region (match-beginning 0) (match-end 0))
(goto-char (point-max))
(skip-chars-backward "[0-9]")
;;
;; If it's a lint message, use the last file(linenum) on the line.
;; Normally we use the first on the line.
;;
(if (= (preceding-char) ?\()
(progn
(narrow-to-region (point-min) (1+ (buffer-size)))
(end-of-line)
(re-search-backward compilation-error-regexp)
(skip-chars-backward "^ \t\n")
(narrow-to-region (point) (match-end 0))
(goto-char (point-max))
(skip-chars-backward "[0-9]")))
;;
;; Are we looking at a "filename-first" or "line-number-first" form?
;;
(if (looking-at "[0-9]")
(progn
(setq linenum (read (current-buffer)))
(goto-char (point-min)))
;;
;; Line number at start, file name at end.
;;
(goto-char (point-min))
(setq linenum (read (current-buffer)))
(goto-char (point-max))
(skip-chars-backward "^ \t\n"))
(setq filename (compilation-grab-filename)))
;; Locate the erring file and line.
(if (or (and (equal filename last-filename)
(= linenum last-linenum))
(zerop (length filename)))
nil
(beginning-of-line 1)
(setq error-marker (point-marker))
;; text-buffer gets the buffer containing this error's file.
(if (equal filename last-filename)
nil
(setq last-filename filename
last-linenum 0)
(setq text-buffer
(and (file-exists-p filename)
(find-file-noselect filename))))
(if (not text-buffer)
nil
;; Go to that buffer and find the erring line.
(setq error-list
(cons (save-excursion
(set-buffer text-buffer)
(if (zerop last-linenum)
(progn
(goto-char 1)
(setq last-linenum 1)))
(forward-line (- linenum last-linenum))
(setq last-linenum linenum)
(list error-marker (point-marker)))
error-list))
(message "Parsing error messages...%d" (length error-list))))
(forward-line 1)))
(setq compilation-parsing-end (point-max))
(setq compilation-error-list
(nconc compilation-error-list (nreverse error-list))))
(message "Parsing error messages...done"))
(defun compilation-grab-filename ()
"Return a string which is a filename, starting at point.
Ignore quotes and parentheses around it, as well as trailing colons."
(if (eq (following-char) ?\")
(save-restriction
(narrow-to-region (point)
(progn (forward-sexp 1) (point)))
(goto-char (point-min))
(read (current-buffer)))
(buffer-substring (point)
(progn
(skip-chars-forward "^ :,\n\t(")
(point)))))
(defun rename-compilation-buffer (arg)
"Rename the *compilation* buffer (e.g. to *compilation-1*) so that
a second compilation process may be started. If ARG is non-nil,
rename the current buffer to *compilation*."
(interactive "P")
(let ((buf (get-buffer "*compilation*")))
(if buf
(let ((i 1) new-name)
(while (get-buffer (setq new-name (concat "*compilation-" i "*")))
(setq i (1+ i)))
(save-excursion
(set-buffer buf)
(rename-buffer new-name))
(message "Buffer *compilation* has been renamed %s" new-name))
(or arg
(error "There is no *compilation* buffer."))))
(if (not arg)
nil
(or (not buffer-file-name)
(y-or-n-p (format
"Buffer \"%s\" is attached to a file; rename anyhow? "
(buffer-name)))
(error "Buffer \"%s\" is attached to a file." (buffer-name)))
(rename-buffer "*compilation*"))
(set-buffer-modified-p (buffer-modified-p))) ;force modeline update
(define-key ctl-x-map "`" 'next-error)
\End\Of\Shar\
else
echo "will not over write ./compile.el"
fi
echo "Finished archive 1 of 1"
exit
Technical Details
Message-ID:
<mcook.727827362@fendahl.dev.cdx.mot.com>Path:
arc1-ca.pugleaf.net!sparky!uunet!panther!mothost!merlin.dev.cdx.mot.com!fendahl.dev.cdx.mot.com!mcookShow Full Headers
Complete Article Headers
Newsgroups: comp.emacs Path: arc1-ca.pugleaf.net!sparky!uunet!panther!mothost!merlin.dev.cdx.mot.com!fendahl.dev.cdx.mot.com!mcook From: mcook@fendahl.dev.cdx.mot.com (Michael Cook) Subject: enhancements to compile.el Message-ID: <mcook.727827362@fendahl.dev.cdx.mot.com> Sender: news@merlin.dev.cdx.mot.com (Merlin News System) Nntp-Posting-Host: fendahl.dev.cdx.mot.com Organization: Motorola Codex, Canton, Massachusetts Date: Sat, 23 Jan 1993 22:16:02 GMT Lines: 463