Mercurial > emacs
changeset 418:21a228b6a238
*** empty log message ***
author | Roland McGrath <roland@gnu.org> |
---|---|
date | Mon, 07 Oct 1991 22:49:33 +0000 |
parents | 51793184f9a9 |
children | 4fa4f423f981 |
files | lisp/progmodes/compile.el |
diffstat | 1 files changed, 601 insertions(+), 297 deletions(-) [+] |
line wrap: on
line diff
--- a/lisp/progmodes/compile.el Fri Sep 27 10:03:30 1991 +0000 +++ b/lisp/progmodes/compile.el Mon Oct 07 22:49:33 1991 +0000 @@ -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. 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. -;; 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. +;; 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-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-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-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-error-buffer nil - "Current compilation buffer for compilation error processing.") +(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-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].") (defvar compilation-parsing-end nil - "Position of end of buffer when last error messages parsed.") + "Position of end of buffer when last error messages were parsed.") -(defvar compilation-error-message nil - "Message to print when no more matches for compilation-error-regexp are found") +(defvar compilation-error-message "No more errors" + "Message to print when no more matches for `compilation-error-regexp-alist' +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 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 @@ (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 @@ "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. +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) +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) - (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) + (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) (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 @@ 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-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 @@ 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-window-start w (car next-error))))) + +;;;###autoload +(define-key ctl-x-map "`" '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. +;; 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)