summaryrefslogtreecommitdiff
path: root/lisp
diff options
context:
space:
mode:
authorRoland McGrath <roland@gnu.org>1991-10-07 22:49:33 +0000
committerRoland McGrath <roland@gnu.org>1991-10-07 22:49:33 +0000
commitd3cb357bbffeba13170d88fd23ee95390c682802 (patch)
tree562f2a405078c50b269032c3243b888498f7975b /lisp
parentd6d472d5a363c3e3d178c1e0a9269af7ca7adb8e (diff)
downloademacs-d3cb357bbffeba13170d88fd23ee95390c682802.tar.gz
*** empty log message ***
Diffstat (limited to 'lisp')
-rw-r--r--lisp/progmodes/compile.el908
1 files changed, 606 insertions, 302 deletions
diff --git a/lisp/progmodes/compile.el b/lisp/progmodes/compile.el
index 8ced79837d1..1d6856ee1b1 100644
--- a/lisp/progmodes/compile.el
+++ b/lisp/progmodes/compile.el
@@ -1,66 +1,108 @@
+;;;!!! dup removal is broken.
;; Run compiler as inferior of Emacs, and parse its error messages.
-;; Copyright (C) 1985, 1986, 1988, 1989 Free Software Foundation, Inc.
+;; Copyright (C) 1985-1991 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.
+;; 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.
(provide 'compile)
+(defconst compilation-window-height nil
+ "*Number of lines in a compilation window. If nil, use Emacs default.")
+
(defvar compilation-error-list nil
"List of error message descriptors for visiting erring functions.
-Each error descriptor is a list of length two.
+Each error descriptor is a cons (or nil).
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.")
+If its cdr is a marker, it points to the text of the line the message is about.
+If its cdr is a cons, that cons's car is a cons (DIRECTORY . FILE), specifying
+file the message is about, and its cdr is the number of the line the message
+is about. Or its cdr may be nil if that error 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-parse-errors-function 'compilation-parse-errors
+ "Function to call (with 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-buffer-name-function nil
+ "Function to call with one argument, the name of the major mode of the
+compilation buffer, to give the buffer a name. It should return a string.
+If nil, the name \"*compilation*\" is used for compilation buffers,
+and the name \"*grep*\" is used for grep buffers.
+\(Actually, the name (concat "*" (downcase major-mode) "*") is used.)")
-(defvar compilation-parsing-end nil
- "Position of end of buffer when last error messages parsed.")
+(defvar compilation-finish-function nil
+ "Function to call when a compilation process finishes.
+It is called with two arguments: the compilation buffer, and a string
+describing how the process finished.")
-(defvar compilation-error-message nil
- "Message to print when no more matches for compilation-error-regexp are found")
+(defvar compilation-last-buffer nil
+ "The buffer in which the last compilation was started,
+or which was used by the last \\[next-error] or \\[compile-goto-error].")
-;; 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 compilation-parsing-end nil
+ "Position of end of buffer when last error messages were parsed.")
+
+(defvar compilation-error-message "No more errors"
+ "Message to print when no more matches for `compilation-error-regexp-alist'
+are found.")
+
+(defvar compilation-error-regexp-alist
+ '(
+ ;; 4.3BSD grep, cc, lint pass 1:
+ ;; /usr/src/foo/foo.c(8): warning: w may be used before set
+ ;; or GNU utilities
+ ;; foo.c:8: error message
+ ("^\\([^:( \t\n]+\\)[:( \t]+\\([0-9]+\\)[:) \t]" 1 2)
+ ;; 4.3BSD lint pass 2
+ ;; strcmp: variable # of args. llib-lc(359) :: /usr/src/foo/foo.c(8)
+ ("[ \t:]+\\([^:( \t\n]+\\)[ \t]*[:(]+[ \t]*\\([0-9]+\\)[:) \t]*$" 1 2)
+ ;; 4.3BSD lint pass 3
+ ;; bloofle defined( /users/wolfgang/foo.c(4) ), but never used
+ ("[ \t(]+\\([^:( \t\n]+\\)[:( \t]+\\([0-9]+\\)[:) \t]+" 1 2)
+ ;; Line 45 of "foo.c": bloofel undefined (who does this?)
+ ("^[Ll]ine[ \t]+\\([0-9]+\\)[ \t]+of[ \t]+\"\\([^\"]+\\)\":" 2 1)
+ ;; Apollo cc, 4.3BSD fc
+ ;; "foo.f", line 3: Error: syntax error near end of statement
+ ("^\"\\([^\"]+\\)\", line \\([0-9]+\\):" 1 2)
+ ;; HP-UX 7.0 fc
+ ;; foo.f :16 some horrible error message
+ ("\\([^ \t:]+\\)[ \t]*:\\([0-9]+\\)" 1 2)
+ ;; IBM AIX PS/2 C version 1.1
+ ;; ****** Error number 140 in line 8 of file errors.c ******
+ ("in line \\([0-9]+\\) of file \\([^ ]+[^. ]\\)\\.? " 2 1)
+ ;; IBM AIX lint is too painful to do right this way. File name
+ ;; prefixes entire sections rather than being on each line.
+ )
+ "Alist (REGEXP FILE-IDX LINE-IDX) of regular expressions to match errors in
+compilation. If REGEXP matches, the FILE-IDX'th subexpression gives the file
+name, and the LINE-IDX'th subexpression gives the line number.")
-(defvar compile-window-height nil
- "*Desired height of compilation window. nil means use Emacs default.")
+(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.")
(defvar compile-command "make -k "
"Last shell command used to do a compilation; default for next compilation.
@@ -75,34 +117,68 @@ You might also use mode hooks to specify it in certain modes, like this:
(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.")
-
+;;;###autoload
+(defvar grep-command "grep -n "
+ "Last shell command used to do a grep search; default for next search.
+Typically \"grep -n\" or \"egrep -n\".
+\(The \"-n\" option tells grep to output line numbers.)")
+
+(defconst compilation-enter-directory-regexp
+ ": Entering directory `\\\(.*\\\)'$"
+ "Regular expression for a line in the compilation log that
+changes the current directory. This must contain one \\\(, \\\) pair
+around the directory name.
+
+The default value matches lines printed by the `-w' option of GNU Make.")
+
+(defconst compilation-leave-directory-regexp
+ ": Leaving directory `\\\(.*\\\)'$"
+ "Regular expression for a line in the compilation log that
+changes the current directory to a previous value. This may
+contain one \\\(, \\\) pair around the name of the directory
+being moved from. If it does not, the last directory entered
+\(by a line matching `compilation-enter-directory-regexp'\) is assumed.
+
+The default value matches lines printed by the `-w' option of GNU Make.")
+
+(defvar compilation-directory-stack nil
+ "Stack of directories entered by lines matching
+\`compilation-enter-directory-regexp' and not yet left by lines matching
+\`compilation-leave-directory-regexp'. The head element is the directory
+the compilation was started in.")
+
+;;;###autoload
(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."
+\`*compilation*' buffer to some other name with \\[rename-buffer].
+Then start the next one.
+
+The name used for the buffer is actually whatever is returned by
+the function in `compilation-buffer-name-function', so you can set that
+to a function that generates a unique name."
(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)))
+ (compile-internal compile-command "No more errors"))
+;;;###autoload
(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."
+to find the text that grep hits refer to.
+
+The variable `grep-command' holds the last grep command run,
+and is the default for future runs. The command should use the `-n'
+flag, so that line numbers are displayed for each match.
+What the user enters in response to the prompt for grep args is
+appended to everything up to and including the `-n' in `grep-command'."
(interactive
(list (read-string (concat "Run "
(substring grep-command 0
@@ -121,156 +197,189 @@ has a `-n' flag, so that line numbers are displayed for each match."
"No more grep hits" "grep"))
(defun compile-internal (command error-message
- &optional name-of-mode parser regexp)
+ &optional name-of-mode parser regexp-alist
+ name-function)
"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)
+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-ALIST is the error message regexp alist to use (nil means the
+default). Sixth arg NAME-FUNCTION is a function called to name the buffer (nil
+means the default). The defaults for these variables are the global values of
+\`compilation-parse-errors-function', `compilation-error-regexp-alist', and
+\`compilation-buffer-name-function', respectively."
+ (let (outbuf)
(save-excursion
+ (or name-of-mode
+ (setq name-of-mode "Compilation"))
+ (setq outbuf
+ (get-buffer-create
+ (funcall (or name-function compilation-buffer-name-function
+ (function (lambda (mode)
+ (concat "*" (downcase mode) "*"))))
+ name-of-mode)))
+ (set-buffer outbuf)
+ (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 `%s' at once"
+ (buffer-name))
+ )))
+ ;; In case the compilation buffer is current, make sure we get the global
+ ;; values of compilation-error-regexp-alist, etc.
+ (kill-all-local-variables))
+ (let ((regexp-alist (or regexp-alist compilation-error-regexp-alist))
+ (parser (or parser compilation-parse-errors-function))
+ (thisdir default-directory)
+ outwin)
+ (save-excursion
+ ;; Clear out the compilation buffer and make it writable.
+ ;; Change its default-directory to the directory where the compilation
+ ;; will happen, and insert a `cd' command to indicate this.
+ (set-buffer outbuf)
+ (setq buffer-read-only nil)
+ (erase-buffer)
+ (setq default-directory thisdir)
+ (insert "cd " thisdir "\n" command "\n")
+ (set-buffer-modified-p nil))
+ ;; If we're already in the compilation buffer, go to the end
+ ;; of the buffer, so point will track the compilation output.
+ (if (eq outbuf (current-buffer))
+ (goto-char (point-max)))
+ ;; Pop up the compilation buffer.
+ (setq outwin (display-buffer outbuf))
(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 (make-local-variable 'compilation-parse-errors-function) parser)
+ (set (make-local-variable 'compilation-error-message) error-message)
+ (set (make-local-variable 'compilation-error-regexp-alist) regexp-alist)
+ (setq default-directory thisdir
+ compilation-directory-stack (list default-directory))
(set-window-start outwin (point-min))
- (setq mode-name (or name-of-mode "Compilation"))
- (setq buffer-read-only t)
+ (setq mode-name name-of-mode)
(or (eq outwin (selected-window))
- (set-window-point outwin (point-min))))))
+ (set-window-point outwin (point-min)))
+ (and compilation-window-height
+ (= (window-width outwin) (screen-width))
+ (let ((w (selected-window)))
+ (unwind-protect
+ (progn
+ (select-window outwin)
+ (enlarge-window (- compilation-window-height
+ (window-height))))
+ (select-window w))))
+ ;; Start the compilation.
+ (start-process-shell-command (downcase mode-name) outbuf command)
+ (set-process-sentinel (get-buffer-process outbuf)
+ 'compilation-sentinel))
+ ;; Make it so the next C-x ` will use this buffer.
+ (setq compilation-last-buffer outbuf)))
(defvar compilation-mode-map
(let ((map (make-sparse-keymap)))
(define-key map "\C-c\C-c" 'compile-goto-error)
+ (define-key map "\C-c\C-k" 'kill-compilation)
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]."
+move point to the error message line and type \\[compile-goto-error].
+To kill the compilation, type \\[kill-compilation]."
(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")))
+ ;; Make buffer's mode line show process state
+ (setq mode-line-process '(": %s"))
+ (set (make-local-variable 'compilation-error-list) nil)
+ (set (make-local-variable 'compilation-old-error-list) nil)
+ (set (make-local-variable 'compilation-parsing-end) 1)
+ (set (make-local-variable 'compilation-directory-stack) nil)
+ (setq compilation-last-buffer (current-buffer)))
;; 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)))))
+ "Sentinel for compilation buffers."
+ (let ((buffer (process-buffer proc)))
+ (cond ((null (buffer-name buffer))
+ ;; 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 the compilation buffer
+ ;; and hack its mode line.
+ (set-buffer buffer)
+ (setq omax (point-max)
+ opoint (point))
+ (goto-char omax)
+ (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))))
+ ;; Since the 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)
+ (if compilation-finish-function
+ (funcall compilation-finish-function buffer msg))
+ ))
+ )))
(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*"))))
+ (let ((buffer (compilation-find-buffer)))
(if (get-buffer-process buffer)
- (interrupt-process (get-buffer-process buffer)))))
+ (interrupt-process (get-buffer-process buffer))
+ (error "The compilation process is not running."))))
+
-;; Reparse errors or parse more/new errors, if appropriate.
+;; Parse any new errors in the compilation buffer,
+;; or reparse from the beginning if the user has asked for that.
(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)
+ (save-excursion
+ (set-buffer compilation-last-buffer)
+ ;; If we are out of errors, or if user says "reparse",
+ ;; discard the info we have, to force reparsing.
+ (if (or (eq compilation-error-list t)
+ (consp argp))
+ (progn (compilation-forget-errors)
+ (setq compilation-parsing-end 1)))
+ (if compilation-error-list
+ ;; Since compilation-error-list is non-nil, it points to a specific
+ ;; error the user wanted. So don't move it around.
+ nil
+ (switch-to-buffer compilation-last-buffer)
(set-buffer-modified-p nil)
(let ((at-start (= compilation-parsing-end 1)))
- (run-hooks 'compilation-parse-errors-hook)
+ (funcall compilation-parse-errors-function)
;; Remember the entire list for compilation-forget-errors.
;; If this is an incremental parse, append to previous list.
(if at-start
@@ -284,11 +393,23 @@ 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")
+ (or (compilation-buffer-p (current-buffer))
+ (error "Not in a compilation buffer."))
+ (setq compilation-last-buffer (current-buffer))
(compile-reinitialize-errors argp)
(save-excursion
(beginning-of-line)
+ ;; Move compilation-error-list to the elt of
+ ;; compilation-old-error-list whose car is the error we want.
(setq compilation-error-list
- (memq (assoc (point-marker) compilation-old-error-list)
+ (memq (let (elt)
+ (while (not (or (setq elt (assoc (point-marker)
+ compilation-old-error-list))
+ (eobp)))
+ ;; This line doesn't contain an error.
+ ;; Move forward a line and look again.
+ (forward-line 1))
+ elt)
compilation-old-error-list)))
;; Move to another window, so that next-error's window changes
;; result in the desired setup.
@@ -296,6 +417,28 @@ other kinds of prefix arguments are ignored."
(other-window -1))
(next-error 1))
+(defun compilation-buffer-p (buffer)
+ (assq 'compilation-error-list (buffer-local-variables buffer)))
+
+;; Return a compilation buffer.
+;; If the current buffer is a compilation buffer, return it.
+;; If compilation-last-buffer is set to a live buffer, use that.
+;; Otherwise, look for a compilation buffer and signal an error
+;; if there are none.
+(defun compilation-find-buffer ()
+ (if (compilation-buffer-p (current-buffer))
+ ;; The current buffer is a compilation buffer.
+ (current-buffer)
+ (if (and compilation-last-buffer (buffer-name compilation-last-buffer))
+ compilation-last-buffer
+ (let ((buffers (buffer-list)))
+ (while (and buffers (not (compilation-buffer-p (car buffers))))
+ (setq buffers (cdr buffers)))
+ (if buffers
+ (car buffers)
+ (error "No compilation started!"))))))
+
+;;;###autoload
(defun next-error (&optional argp)
"Visit next compilation error message and corresponding source code.
This operates on the output from the \\[compile] command.
@@ -314,165 +457,326 @@ 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."
+See variables `compilation-parse-errors-function' and
+\`compilation-error-regexp-alist' for customization ideas."
(interactive "P")
+ (setq compilation-last-buffer (compilation-find-buffer))
(compile-reinitialize-errors argp)
+ ;; Make ARGP nil if the prefix arg was just C-u,
+ ;; since that means to reparse the errors, which the
+ ;; compile-reinitialize-errors call just did.
+ ;; Now we are only interested in a numeric prefix arg.
(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)))
+ (let (next-errors next-error)
+ (save-excursion
+ (set-buffer compilation-last-buffer)
+ (setq 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))
+ (while
(progn
- (widen)
- (goto-char (car (cdr next-error))))))
+ (if (null next-error)
+ (progn
+ (if argp (if (> (prefix-numeric-value argp) 0)
+ (error "Moved past last error")
+ (error "Moved back past first error")))
+ (compilation-forget-errors)
+ (error (concat compilation-error-message
+ (and (get-buffer-process (current-buffer))
+ (eq (process-status
+ (get-buffer-process
+ (current-buffer)))
+ 'run)
+ " yet"))))
+ (setq compilation-error-list (cdr next-errors))
+ (if (null (cdr next-error))
+ ;; This error is boring. Go to the next.
+ t
+ (or (markerp (cdr next-error))
+ ;; This error has a filename/lineno pair.
+ ;; Find the file and turn it into a marker.
+ (let* ((fileinfo (car (cdr next-error)))
+ (buffer (compilation-find-file (cdr fileinfo)
+ (car fileinfo)
+ (car next-error))))
+ (if (null buffer)
+ ;; We can't find this error's file.
+ ;; Remove all errors in the same file.
+ (progn
+ (setq next-errors compilation-old-error-list)
+ (while next-errors
+ (and (consp (cdr (car next-errors)))
+ (equal (car (cdr (car next-errors)))
+ fileinfo)
+ (progn
+ (set-marker (car (car next-errors)) nil)
+ (setcdr (car next-errors) nil)))
+ (setq next-errors (cdr next-errors)))
+ ;; Look for the next error.
+ t)
+ ;; We found the file. Get a marker for this error.
+ (set-buffer buffer)
+ (save-excursion
+ (save-restriction
+ (widen)
+ (let ((errors compilation-old-error-list)
+ (last-line (cdr (cdr next-error))))
+ (goto-line last-line)
+ (beginning-of-line)
+ (setcdr next-error (point-marker))
+ ;; Make all the other error messages referring
+ ;; to the same file have markers into the buffer.
+ (while errors
+ (and (consp (cdr (car errors)))
+ (equal (car (cdr (car errors))) fileinfo)
+ (let ((this (cdr (cdr (car errors))))
+ (lines (- (cdr (cdr (car errors)))
+ last-line)))
+ (if (eq selective-display t)
+ (if (< lines 0)
+ (re-search-backward "[\n\C-m]"
+ nil 'end
+ (- lines))
+ (re-search-forward "[\n\C-m]"
+ nil 'end
+ lines))
+ (forward-line lines))
+ (setq last-line this)
+ (setcdr (car errors) (point-marker))))
+ (setq errors (cdr errors)))))))))
+ ;; If we didn't get a marker for this error,
+ ;; go on to the next one.
+ (not (markerp (cdr next-error))))))
+ (setq next-errors compilation-error-list
+ next-error (car next-errors))))
+
+ ;; Skip over multiple error messages for the same source location,
+ ;; so the next C-x ` won't go to an error in the same place.
+ (while (and compilation-error-list
+ (equal (cdr (car compilation-error-list)) (cdr next-error)))
+ (setq compilation-error-list (cdr compilation-error-list)))
+
+ ;; We now have a marker for the position of the error.
+ (switch-to-buffer (marker-buffer (cdr next-error)))
+ (goto-char (cdr next-error))
+ ;; If narrowing got in the way of
+ ;; going to the right place, widen.
+ (or (= (point) (marker-position (cdr next-error)))
+ (progn
+ (widen)
+ (goto-char (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.
+ (set-window-start w (car next-error)))))
+
+;;;###autoload
+(define-key ctl-x-map "`" 'next-error)
+
+;; Find a buffer for file FILENAME.
+;; Search the directories in compilation-search-path.
+;; A nil in compilation-search-path means to try the
+;; current directory, which is passed in DIR.
+;; If FILENAME is not found at all, ask the user where to find it.
+;; Pop up the buffer containing MARKER and scroll to MARKER if we ask the user.
+(defun compilation-find-file (filename dir marker)
+ (let ((dirs compilation-search-path)
+ result name)
+ (while (and dirs (null result))
+ (setq name (expand-file-name filename (or (car dirs) dir))
+ result (and (file-exists-p name)
+ (find-file-noselect name))
+ dirs (cdr dirs)))
+ (or result
+ ;; The file doesn't exist.
+ ;; Ask the user where to find it.
+ ;; If he hits C-g, then the next time he does
+ ;; next-error, he'll skip past it.
+ (progn
+ (let* ((pop-up-windows t)
+ (w (display-buffer (marker-buffer marker))))
+ (set-window-point w marker)
+ (set-window-start w marker))
+ (setq name
+ (expand-file-name
+ (read-file-name
+ (format "Find this error in: (default %s) "
+ filename) dir filename t)))
+ (if (file-directory-p name)
+ (setq name (concat (file-name-as-directory name) filename)))
+ (if (file-exists-p name)
+ (find-file-noselect name))))))
+
+;; 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 the 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)))
+ (if (markerp (cdr next-error))
+ (set-marker (cdr next-error) nil)))
(setq compilation-old-error-list (cdr compilation-old-error-list)))
- (setq compilation-error-list nil))
+ (setq compilation-error-list nil)
+ (while (cdr compilation-directory-stack)
+ (setq compilation-directory-stack (cdr compilation-directory-stack))))
+
+
+(defun count-regexp-groupings (regexp)
+ "Return the number of \\( ... \\) groupings in REGEXP (a string)."
+ (let ((groupings 0)
+ (len (length regexp))
+ (i 0)
+ c)
+ (while (< i len)
+ (setq c (aref regexp i)
+ i (1+ i))
+ (cond ((= c ?\[)
+ ;; Find the end of this [...].
+ (while (and (< i len)
+ (not (= (aref regexp i) ?\])))
+ (setq i (1+ i))))
+ ((= c ?\\)
+ (if (< i len)
+ (progn
+ (setq c (aref regexp i)
+ i (1+ i))
+ (if (= c ?\))
+ ;; We found the end of a grouping,
+ ;; so bump our counter.
+ (setq groupings (1+ groupings))))))))
+ groupings))
(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."
+See variable `compilation-parse-errors-function' for the interface it uses."
(setq compilation-error-list nil)
(message "Parsing error messages...")
(let (text-buffer
- last-filename last-linenum)
+ regexp enter-group leave-group error-group
+ alist subexpr error-regexp-groups)
+
;; 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)))
+
+ ;; Compile all the regexps we want to search for into one.
+ (setq regexp (concat "\\(" compilation-enter-directory-regexp "\\)\\|"
+ "\\(" compilation-leave-directory-regexp "\\)\\|"
+ "\\(" (mapconcat (function
+ (lambda (elt)
+ (concat "\\(" (car elt) "\\)")))
+ compilation-error-regexp-alist
+ "\\|") "\\)"))
+
+ ;; Find out how many \(...\) groupings are in each of the regexps, and set
+ ;; *-GROUP to the grouping containing each constituent regexp (whose
+ ;; subgroups will come immediately thereafter) of the big regexp we have
+ ;; just constructed.
+ (setq enter-group 1
+ leave-group (+ enter-group
+ (count-regexp-groupings
+ compilation-enter-directory-regexp)
+ 1)
+ error-group (+ leave-group
+ (count-regexp-groupings
+ compilation-leave-directory-regexp)
+ 1))
+
+ ;; Compile an alist (IDX FILE LINE), where IDX is the number of the
+ ;; subexpression for an entire error-regexp, and FILE and LINE are the
+ ;; numbers for the subexpressions giving the file name and line number.
+ (setq alist compilation-error-regexp-alist
+ subexpr (1+ error-group))
+ (while alist
+ (setq error-regexp-groups (cons (list subexpr
+ (+ subexpr (nth 1 (car alist)))
+ (+ subexpr (nth 2 (car alist))))
+ error-regexp-groups))
+ (setq subexpr (+ subexpr 1 (count-regexp-groupings (car (car alist)))))
+ (setq alist (cdr alist)))
+
+ (while (re-search-forward regexp nil t)
+ ;; Figure out which constituent regexp matched.
+ (cond ((match-beginning enter-group)
+ ;; The match was the enter-directory regexp.
+ (let ((dir
+ (file-name-as-directory
+ (expand-file-name
+ (buffer-substring (match-beginning (+ enter-group 1))
+ (match-end (+ enter-group 1)))))))
+ (setq compilation-directory-stack
+ (cons dir compilation-directory-stack))
+ (and (file-directory-p dir)
+ (setq default-directory dir))))
+
+ ((match-beginning leave-group)
+ ;; The match was the leave-directory regexp.
+ (let ((beg (match-beginning (+ leave-group 1)))
+ (stack compilation-directory-stack))
+ (if beg
+ (let ((dir
+ (file-name-as-directory
+ (expand-file-name
+ (buffer-substring beg
+ (match-end (+ leave-group
+ 1)))))))
+ (while (and stack
+ (not (string-equal (car stack) dir)))
+ (setq stack (cdr stack)))))
+ (setq compilation-directory-stack (cdr stack))
+ (setq stack (car compilation-directory-stack))
+ (if stack
+ (setq default-directory stack))
+ ))
+
+ ((match-beginning error-group)
+ ;; The match was the composite error regexp.
+ ;; Find out which individual regexp matched.
+ (setq alist error-regexp-groups)
+ (while (and alist
+ (null (match-beginning (car (car alist)))))
+ (setq alist (cdr alist)))
+ (if alist
+ (setq alist (car alist))
+ (error "Impossible regexp match!"))
+
+ ;; Extract the file name and line number from the error message.
+ (let ((filename
+ (cons default-directory
+ (buffer-substring (match-beginning (nth 1 alist))
+ (match-end (nth 1 alist)))))
+ (linenum (save-restriction
+ (narrow-to-region
+ (match-beginning (nth 2 alist))
+ (match-end (nth 2 alist)))
+ (goto-char (point-min))
+ (if (looking-at "[0-9]")
+ (read (current-buffer))))))
+ ;; Locate the erring file and line.
+ ;; Cons a new elt onto compilation-error-list,
+ ;; giving a marker for the current compilation buffer
+ ;; location, and the file and line number of the error.
+ (save-excursion
+ (beginning-of-line 1)
+ (setq compilation-error-list
+ (cons (cons (point-marker)
+ (cons filename linenum))
+ compilation-error-list)))))
+ (t
+ (error "Impossible regexp match!"))))
(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)