mirror of
https://git.savannah.gnu.org/git/emacs.git
synced 2025-01-18 18:05:07 +00:00
479 lines
18 KiB
EmacsLisp
479 lines
18 KiB
EmacsLisp
|
;; Run compiler as inferior of Emacs, and parse its error messages.
|
||
|
;; Copyright (C) 1985, 1986, 1988, 1989 Free Software Foundation, Inc.
|
||
|
|
||
|
;; This file is part of GNU Emacs.
|
||
|
|
||
|
;; GNU Emacs 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 1, or (at your option)
|
||
|
;; any later version.
|
||
|
|
||
|
;; GNU Emacs is distributed in the hope that it will be useful,
|
||
|
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
|
;; GNU General Public License for more details.
|
||
|
|
||
|
;; You should have received a copy of the GNU General Public License
|
||
|
;; along with GNU Emacs; see the file COPYING. If not, write to
|
||
|
;; the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
|
||
|
|
||
|
(provide 'compile)
|
||
|
|
||
|
(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.")
|
||
|
|
||
|
(defvar compilation-old-error-list nil
|
||
|
"Value of `compilation-error-list' after errors were parsed.")
|
||
|
|
||
|
(defvar compilation-last-error nil
|
||
|
"List describing the error found by last call to \\[next-error].
|
||
|
A list of two markers (ERROR-POS CODE-POS),
|
||
|
pointing to the error message and the erroneous code, respectively.
|
||
|
CODE-POS can be nil, if the error message has no specific source location.")
|
||
|
|
||
|
(defvar compilation-parse-errors-hook 'compilation-parse-errors
|
||
|
"Function to call (no args) to parse error messages from a compilation.
|
||
|
It should read in the source files which have errors
|
||
|
and set `compilation-error-list' to a list with an element
|
||
|
for each error message found. See that variable for more info.")
|
||
|
|
||
|
(defvar compilation-error-buffer nil
|
||
|
"Current compilation buffer for compilation error processing.")
|
||
|
|
||
|
(defvar compilation-parsing-end nil
|
||
|
"Position of end of buffer when last error messages parsed.")
|
||
|
|
||
|
(defvar compilation-error-message nil
|
||
|
"Message to print when no more matches for compilation-error-regexp are found")
|
||
|
|
||
|
;; The filename excludes colons to avoid confusion when error message
|
||
|
;; starts with digits.
|
||
|
(defvar compilation-error-regexp
|
||
|
"\\([^ :\n]+\\(: *\\|, line \\|(\\)[0-9]+\\)\\|\\([0-9]+ *of *[^ \n]+\\)\\|\\(\"[^ \n]+\",L[0-9]+\\)"
|
||
|
"Regular expression for filename/linenumber in error in compilation log.")
|
||
|
|
||
|
(defvar compile-window-height nil
|
||
|
"*Desired height of compilation window. nil means use Emacs default.")
|
||
|
|
||
|
(defvar compile-command "make -k "
|
||
|
"Last shell command used to do a compilation; default for next compilation.
|
||
|
|
||
|
Sometimes it is useful for files to supply local values for this variable.
|
||
|
You might also use mode hooks to specify it in certain modes, like this:
|
||
|
|
||
|
(setq c-mode-hook
|
||
|
'(lambda () (or (file-exists-p \"makefile\") (file-exists-p \"Makefile\")
|
||
|
(progn (make-local-variable 'compile-command)
|
||
|
(setq compile-command
|
||
|
(concat \"make -k \"
|
||
|
buffer-file-name))))))")
|
||
|
|
||
|
(defvar compilation-search-path '(nil)
|
||
|
"List of directories to search for source files named in error messages.
|
||
|
Elements should be directory names, not file names of directories.
|
||
|
nil as an element means to try the default directory.")
|
||
|
|
||
|
(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.
|
||
|
|
||
|
To run more than one compilation at once, start one and rename the
|
||
|
`*compilation*' buffer to some other name. Then start the next one."
|
||
|
(interactive (list (read-string "Compile command: " compile-command)))
|
||
|
(setq compile-command command)
|
||
|
(save-some-buffers nil nil)
|
||
|
(compile-internal compile-command "No more errors")
|
||
|
(and compile-window-height
|
||
|
(= (window-width) (screen-width))
|
||
|
(enlarge-window (- (- (screen-height) (window-height))
|
||
|
compile-window-height) nil)))
|
||
|
|
||
|
(defun grep (command-args)
|
||
|
"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. It is expected that `grep-command'
|
||
|
has a `-n' flag, so that line numbers are displayed for each match."
|
||
|
(interactive
|
||
|
(list (read-string (concat "Run "
|
||
|
(substring grep-command 0
|
||
|
(string-match "[\t ]+" grep-command))
|
||
|
" (with args): ")
|
||
|
(progn
|
||
|
(string-match "-n[\t ]+" grep-command)
|
||
|
(substring grep-command (match-end 0))))))
|
||
|
;; why a redundant string-match? It might not be interactive ...
|
||
|
(setq grep-command (concat (substring grep-command 0
|
||
|
(progn
|
||
|
(string-match "-n" grep-command)
|
||
|
(match-end 0)))
|
||
|
" " command-args))
|
||
|
(compile-internal (concat grep-command " /dev/null")
|
||
|
"No more grep hits" "grep"))
|
||
|
|
||
|
(defun compile-internal (command error-message
|
||
|
&optional name-of-mode parser regexp)
|
||
|
"Run compilation command COMMAND (low level interface).
|
||
|
ERROR-MESSAGE is a string to print if the user asks to see another error
|
||
|
and there are no more errors. Third argument NAME-OF-MODE is the name
|
||
|
to display as the major mode in the `*compilation*' buffer.
|
||
|
|
||
|
Fourth arg PARSER is the error parser function (nil means the default).
|
||
|
Fifth arg REGEXP is the error message regexp to use (nil means the default).
|
||
|
The defaults for these variables are the global values of
|
||
|
`compilation-parse-errors-hook' and `compilation-error-regexp'."
|
||
|
(save-excursion
|
||
|
(set-buffer (get-buffer-create "*compilation*"))
|
||
|
(setq buffer-read-only nil)
|
||
|
(let ((comp-proc (get-buffer-process (current-buffer))))
|
||
|
(if comp-proc
|
||
|
(if (or (not (eq (process-status comp-proc) 'run))
|
||
|
(yes-or-no-p "A compilation process is running; kill it? "))
|
||
|
(condition-case ()
|
||
|
(progn
|
||
|
(interrupt-process comp-proc)
|
||
|
(sit-for 1)
|
||
|
(delete-process comp-proc))
|
||
|
(error nil))
|
||
|
(error "Cannot have two processes in `*compilation*' at once"))))
|
||
|
;; In case *compilation* is current buffer,
|
||
|
;; make sure we get the global values of compilation-error-regexp, etc.
|
||
|
(kill-all-local-variables))
|
||
|
(compilation-forget-errors)
|
||
|
(start-process-shell-command "compilation" "*compilation*" command)
|
||
|
(with-output-to-temp-buffer "*compilation*"
|
||
|
(princ "cd ")
|
||
|
(princ default-directory)
|
||
|
(terpri)
|
||
|
(princ command)
|
||
|
(terpri))
|
||
|
(let* ((regexp (or regexp compilation-error-regexp))
|
||
|
(parser (or parser compilation-parse-errors-hook))
|
||
|
(thisdir default-directory)
|
||
|
(outbuf (get-buffer "*compilation*"))
|
||
|
(outwin (get-buffer-window outbuf)))
|
||
|
(if (eq outbuf (current-buffer))
|
||
|
(goto-char (point-max)))
|
||
|
(set-process-sentinel (get-buffer-process outbuf)
|
||
|
'compilation-sentinel)
|
||
|
(save-excursion
|
||
|
(set-buffer outbuf)
|
||
|
(if (or (eq compilation-error-buffer outbuf)
|
||
|
(eq compilation-error-list t)
|
||
|
(and (null compilation-error-list)
|
||
|
(not (and (get-buffer-process compilation-error-buffer)
|
||
|
(eq (process-status compilation-error-buffer)
|
||
|
'run)))))
|
||
|
(setq compilation-error-list t
|
||
|
compilation-error-buffer outbuf))
|
||
|
(setq default-directory thisdir)
|
||
|
(compilation-mode)
|
||
|
(set-window-start outwin (point-min))
|
||
|
(setq mode-name (or name-of-mode "Compilation"))
|
||
|
(setq buffer-read-only t)
|
||
|
(or (eq outwin (selected-window))
|
||
|
(set-window-point outwin (point-min))))))
|
||
|
|
||
|
(defvar compilation-mode-map
|
||
|
(let ((map (make-sparse-keymap)))
|
||
|
(define-key map "\C-c\C-c" 'compile-goto-error)
|
||
|
map)
|
||
|
"Keymap for compilation log buffers.")
|
||
|
|
||
|
(defun compilation-mode ()
|
||
|
"Major mode for compilation log buffers.
|
||
|
\\<compilation-mode-map>To visit the source for a line-numbered error,
|
||
|
move point to the error message line and type \\[compile-goto-error]."
|
||
|
(interactive)
|
||
|
(fundamental-mode)
|
||
|
(use-local-map compilation-mode-map)
|
||
|
(make-local-variable 'compilation-parse-errors-hook)
|
||
|
(setq compilation-parse-errors-hook parser)
|
||
|
(make-local-variable 'compilation-error-message)
|
||
|
(setq compilation-error-message error-message)
|
||
|
(make-local-variable 'compilation-error-regexp)
|
||
|
(setq compilation-error-regexp regexp)
|
||
|
(buffer-disable-undo (current-buffer))
|
||
|
(setq major-mode 'compilation-mode)
|
||
|
(setq mode-name "Compilation")
|
||
|
;; Make log buffer's mode line show process state
|
||
|
(setq mode-line-process '(": %s")))
|
||
|
|
||
|
;; Called when compilation process changes state.
|
||
|
|
||
|
(defun compilation-sentinel (proc msg)
|
||
|
(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 opoint)
|
||
|
;; save-excursion isn't the right thing if
|
||
|
;; process-buffer is current-buffer
|
||
|
(unwind-protect
|
||
|
(progn
|
||
|
;; Write something in *compilation* and hack its mode line,
|
||
|
(set-buffer (process-buffer proc))
|
||
|
(setq omax (point-max) opoint (point))
|
||
|
(goto-char (point-max))
|
||
|
(insert ?\n mode-name " " msg)
|
||
|
(forward-char -1)
|
||
|
(insert " at " (substring (current-time-string) 0 19))
|
||
|
(forward-char 1)
|
||
|
(setq mode-line-process
|
||
|
(concat ": "
|
||
|
(symbol-name (process-status proc))))
|
||
|
;; 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))
|
||
|
;; Force mode line redisplay soon
|
||
|
(set-buffer-modified-p (buffer-modified-p)))
|
||
|
(if (and opoint (< opoint omax))
|
||
|
(goto-char opoint))
|
||
|
(set-buffer obuf)))))
|
||
|
|
||
|
(defun kill-compilation ()
|
||
|
"Kill the process made by the \\[compile] command."
|
||
|
(interactive)
|
||
|
(let ((buffer
|
||
|
(if (assq 'compilation-parse-errors-hook (buffer-local-variables))
|
||
|
(current-buffer)
|
||
|
(get-buffer "*compilation*"))))
|
||
|
(if (get-buffer-process buffer)
|
||
|
(interrupt-process (get-buffer-process buffer)))))
|
||
|
|
||
|
;; Reparse errors or parse more/new errors, if appropriate.
|
||
|
(defun compile-reinitialize-errors (argp)
|
||
|
;; If we are out of errors, or if user says "reparse",
|
||
|
;; or if we are in a different buffer from the known errors,
|
||
|
;; discard the info we have, to force reparsing.
|
||
|
(if (or (eq compilation-error-list t)
|
||
|
(consp argp)
|
||
|
(if (assq 'compilation-parse-errors-hook (buffer-local-variables))
|
||
|
(not (eq compilation-error-buffer
|
||
|
(setq compilation-error-buffer (current-buffer))))))
|
||
|
(progn (compilation-forget-errors)
|
||
|
(setq compilation-parsing-end 1)))
|
||
|
(if compilation-error-list
|
||
|
nil
|
||
|
(save-excursion
|
||
|
(switch-to-buffer compilation-error-buffer)
|
||
|
(set-buffer-modified-p nil)
|
||
|
(let ((at-start (= compilation-parsing-end 1)))
|
||
|
(run-hooks 'compilation-parse-errors-hook)
|
||
|
;; Remember the entire list for compilation-forget-errors.
|
||
|
;; If this is an incremental parse, append to previous list.
|
||
|
(if at-start
|
||
|
(setq compilation-old-error-list compilation-error-list)
|
||
|
(setq compilation-old-error-list
|
||
|
(nconc compilation-old-error-list compilation-error-list)))))))
|
||
|
|
||
|
(defun compile-goto-error (&optional argp)
|
||
|
"Visit the source for the error message point is on.
|
||
|
Use this command in a compilation log buffer.
|
||
|
C-u as a prefix arg means to reparse the buffer's error messages first;
|
||
|
other kinds of prefix arguments are ignored."
|
||
|
(interactive "P")
|
||
|
(compile-reinitialize-errors argp)
|
||
|
(save-excursion
|
||
|
(beginning-of-line)
|
||
|
(setq compilation-error-list
|
||
|
(memq (assoc (point-marker) compilation-old-error-list)
|
||
|
compilation-old-error-list)))
|
||
|
;; Move to another window, so that next-error's window changes
|
||
|
;; result in the desired setup.
|
||
|
(or (one-window-p)
|
||
|
(other-window -1))
|
||
|
(next-error 1))
|
||
|
|
||
|
(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 prefix arg specifies how many error messages to move;
|
||
|
negative means move back to previous error messages.
|
||
|
Just C-u as a prefix means reparse the error message buffer
|
||
|
and start at the first error.
|
||
|
|
||
|
\\[next-error] normally applies to the most recent compilation started,
|
||
|
but as long as you are in the middle of parsing errors from one compilation
|
||
|
output buffer, you stay with that compilation output buffer.
|
||
|
|
||
|
Use \\[next-error] in a compilation output buffer to switch to
|
||
|
processing errors from that compilation.
|
||
|
|
||
|
See variables `compilation-parse-errors-hook' and `compilation-error-regexp'
|
||
|
for customization ideas. When we return, `compilation-last-error'
|
||
|
points to the error message and the erroneous code."
|
||
|
(interactive "P")
|
||
|
(compile-reinitialize-errors argp)
|
||
|
(if (consp argp)
|
||
|
(setq argp nil))
|
||
|
(let* ((next-errors (nthcdr (+ (- (length compilation-old-error-list)
|
||
|
(length compilation-error-list)
|
||
|
1)
|
||
|
(prefix-numeric-value argp))
|
||
|
compilation-old-error-list))
|
||
|
(next-error (car next-errors)))
|
||
|
(if (null next-error)
|
||
|
(save-excursion
|
||
|
(if argp (if (> (prefix-numeric-value argp) 0)
|
||
|
(error "Moved past last error")
|
||
|
(error "Moved back past first error")))
|
||
|
(set-buffer compilation-error-buffer)
|
||
|
(compilation-forget-errors)
|
||
|
(error (concat compilation-error-message
|
||
|
(if (and (get-buffer-process (current-buffer))
|
||
|
(eq (process-status (current-buffer))
|
||
|
'run))
|
||
|
" yet" "")))))
|
||
|
(setq compilation-error-list (cdr next-errors))
|
||
|
;; If we have an error to go to, go there.
|
||
|
(if (null (car (cdr next-error)))
|
||
|
nil
|
||
|
(switch-to-buffer (marker-buffer (car (cdr next-error))))
|
||
|
(goto-char (car (cdr next-error)))
|
||
|
;; If narrowing got in the way of going to the right place, widen.
|
||
|
(or (= (point) (car (cdr next-error)))
|
||
|
(progn
|
||
|
(widen)
|
||
|
(goto-char (car (cdr next-error))))))
|
||
|
;; Show compilation buffer in other window, scrolled to this error.
|
||
|
(let* ((pop-up-windows t)
|
||
|
(w (display-buffer (marker-buffer (car next-error)))))
|
||
|
(set-window-point w (car next-error))
|
||
|
(set-window-start w (car next-error)))
|
||
|
(setq compilation-last-error next-error)))
|
||
|
|
||
|
;; 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 ()
|
||
|
(while compilation-old-error-list
|
||
|
(let ((next-error (car compilation-old-error-list)))
|
||
|
(set-marker (car next-error) nil)
|
||
|
(if (car (cdr next-error))
|
||
|
(set-marker (car (cdr next-error)) nil)))
|
||
|
(setq compilation-old-error-list (cdr compilation-old-error-list)))
|
||
|
(setq compilation-error-list nil))
|
||
|
|
||
|
(defun compilation-parse-errors ()
|
||
|
"Parse the current buffer as grep, cc or lint error messages.
|
||
|
See variable `compilation-parse-errors-hook' for the interface it uses."
|
||
|
(setq compilation-error-list nil)
|
||
|
(message "Parsing error messages...")
|
||
|
(let (text-buffer
|
||
|
last-filename last-linenum)
|
||
|
;; 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 text-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.
|
||
|
(progn
|
||
|
(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 (and (equal filename last-filename)
|
||
|
(= linenum last-linenum))
|
||
|
nil
|
||
|
(beginning-of-line 1)
|
||
|
(setq error-marker (point-marker))
|
||
|
;; text-buffer gets the buffer containing this error's file.
|
||
|
(if (not (equal filename last-filename))
|
||
|
(setq last-filename filename
|
||
|
text-buffer (compilation-find-file filename)
|
||
|
last-linenum 0))
|
||
|
(if text-buffer
|
||
|
;; Go to that buffer and find the erring line.
|
||
|
(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)
|
||
|
(setq text-marker (point-marker))
|
||
|
(setq compilation-error-list
|
||
|
(cons (list error-marker text-marker)
|
||
|
compilation-error-list)))))
|
||
|
(forward-line 1)))
|
||
|
(setq compilation-parsing-end (point-max)))
|
||
|
(message "Parsing error messages...done")
|
||
|
(setq compilation-error-list (nreverse compilation-error-list)))
|
||
|
|
||
|
;; Find or create a buffer for file FILENAME.
|
||
|
;; Search the directories in compilation-search-path
|
||
|
;; after trying the current directory.
|
||
|
(defun compilation-find-file (filename)
|
||
|
(let ((dirs compilation-search-path)
|
||
|
result)
|
||
|
(while (and dirs (null result))
|
||
|
(let ((name (if (car dirs)
|
||
|
(concat (car dirs) filename)
|
||
|
filename)))
|
||
|
(setq result
|
||
|
(and (file-exists-p name)
|
||
|
(find-file-noselect name))))
|
||
|
(setq dirs (cdr dirs)))
|
||
|
result))
|
||
|
|
||
|
(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)))))
|
||
|
|
||
|
(define-key ctl-x-map "`" 'next-error)
|