diff options
Diffstat (limited to 'lisp/progmodes/compile.el')
-rw-r--r-- | lisp/progmodes/compile.el | 323 |
1 files changed, 236 insertions, 87 deletions
diff --git a/lisp/progmodes/compile.el b/lisp/progmodes/compile.el index 7d4a8ffc6fc..9f33186d8b1 100644 --- a/lisp/progmodes/compile.el +++ b/lisp/progmodes/compile.el @@ -82,6 +82,25 @@ after `call-process' inserts the grep output into the buffer.") "Position of the start of the text inserted by `compilation-filter'. This is bound before running `compilation-filter-hook'.") +(defcustom compilation-hidden-output nil + "Regexp to match output from the compilation that should be hidden. +This can also be a list of regexps. + +The text matched by this variable will be made invisible, which +means that it'll still be present in the buffer, so that +navigation commands (for instance, `next-error') can still make +use of the hidden text to determine the current directory and the +like. + +For instance, to hide the verbose output from recursive +makefiles, you can say something like: + + (setq compilation-hidden-output + \\='(\"^make[^\n]+\n\"))" + :type '(choice regexp + (repeat regexp)) + :version "29.1") + (defvar compilation-first-column 1 "This is how compilers number the first column, usually 1 or 0. If this is buffer-local in the destination buffer, Emacs obeys @@ -257,7 +276,16 @@ of[ \t]+\"?\\([a-zA-Z]?:?[^\":\n]+\\)\"?:" 3 2 nil (1)) "): ") 3 4 5 (1 . 2)) - (iar + (gradle-android + ,(rx bol (* " ") "ERROR:" + (group-n 1 ; file + (+ (not (in ":\n")))) + ":" + (group-n 2 (+ digit)) ; line + ": ") + 1 2) + + (iar "^\"\\(.*\\)\",\\([0-9]+\\)\\s-+\\(?:Error\\|Warnin\\(g\\)\\)\\[[0-9]+\\]:" 1 2 nil (3)) @@ -340,69 +368,73 @@ of[ \t]+\"?\\([a-zA-Z]?:?[^\":\n]+\\)\"?:" 3 2 nil (1)) ": \\*\\*\\* \\[\\(\\(.+?\\):\\([0-9]+\\): .+\\)\\]" 2 3 nil 0 1) (gnu + ;; The `gnu' message syntax is + ;; [PROGRAM:]FILE:LINE[-ENDLINE]:[COL[-ENDCOL]:] MESSAGE + ;; or + ;; [PROGRAM:]FILE:LINE[.COL][-ENDLINE[.ENDCOL]]: MESSAGE ,(rx bol - ;; Match an optional program name in the format - ;; PROGRAM:SOURCE-FILE-NAME:LINENO: MESSAGE - ;; which is used for non-interactive programs other than - ;; compilers (e.g. the "jade:" entry in compilation.txt). - (? (| (regexp "[[:alpha:]][-[:alnum:].]+: ?") - ;; FIXME: This pattern was added for handling messages - ;; from Ruby, but it is unclear whether it is actually - ;; used since the gcc-include rule above seems to cover - ;; it. - (regexp "[ \t]+\\(?:in \\|from\\)"))) + ;; Match an optional program name which is used for + ;; non-interactive programs other than compilers (e.g. the + ;; "jade:" entry in compilation.txt). + (? (| (: alpha (+ (in ?. ?- alnum)) ":" (? " ")) + ;; Skip indentation generated by GCC's -fanalyzer. + (: (+ " ") "|"))) ;; File name group. (group-n 1 - ;; Avoid matching the file name as a program in the pattern - ;; above by disallow file names entirely composed of digits. - (: (regexp "[0-9]*[^0-9\n]") - ;; This rule says that a file name can be composed - ;; of any non-newline char, but it also rules out - ;; some valid but unlikely cases, such as a - ;; trailing space or a space followed by a -, or a - ;; colon followed by a space. - (*? (| (regexp "[^\n :]") - (regexp " [^-/\n]") - (regexp ":[^ \n]"))))) - (regexp ": ?") + ;; Avoid matching the file name as a program in the pattern + ;; above by disallowing file names entirely composed of digits. + ;; Do not allow file names beginning with a space. + (| (not (in "0-9" "\n\t ")) + (: (+ (in "0-9")) + (not (in "0-9" "\n")))) + ;; A file name can be composed of any non-newline char, but + ;; rule out some valid but unlikely cases, such as a trailing + ;; space or a space followed by a -, or a colon followed by a + ;; space. + (*? (| (not (in "\n :")) + (: " " (not (in ?- "/\n"))) + (: ":" (not (in " \n")))))) + ":" (? " ") ;; Line number group. - (group-n 2 (regexp "[0-9]+")) + (group-n 2 (+ (in "0-9"))) (? (| (: "-" - (group-n 4 (regexp "[0-9]+")) ; ending line - (? "." (group-n 5 (regexp "[0-9]+")))) ; ending column + (group-n 4 (+ (in "0-9"))) ; ending line + (? "." (group-n 5 (+ (in "0-9"))))) ; ending column (: (in ".:") - (group-n 3 (regexp "[0-9]+")) ; starting column + (group-n 3 (+ (in "0-9"))) ; starting column (? "-" - (? (group-n 4 (regexp "[0-9]+")) ".") ; ending line - (group-n 5 (regexp "[0-9]+")))))) ; ending column + (? (group-n 4 (+ (in "0-9"))) ".") ; ending line + (group-n 5 (+ (in "0-9"))))))) ; ending column ":" (| (: (* " ") (group-n 6 (| "FutureWarning" "RuntimeWarning" - "Warning" - "warning" + "Warning" "warning" "W:"))) (: (* " ") - (group-n 7 (| (regexp "[Ii]nfo\\(?:\\>\\|rmationa?l?\\)") - "I:" - (: "[ skipping " (+ nonl) " ]") - "instantiated from" - "required from" - (regexp "[Nn]ote")))) + (group-n 7 + (| (| "Info" "info" + "Information" "information" + "Informational" "informational" + "I:" + "instantiated from" + "required from" + "Note" "note") + (: "[ skipping " (+ nonl) " ]")))) (: (* " ") - (regexp "[Ee]rror")) + (| "Error" "error")) ;; Avoid matching time stamps on the form "HH:MM:SS" where ;; MM is interpreted as a line number by trying to rule out ;; messages where the text after the line number starts with ;; a 2-digit number. - (: (regexp "[0-9]?") - (| (regexp "[^0-9\n]") + (: (? (in "0-9")) + (| (not (in "0-9\n")) eol)) - (regexp "[0-9][0-9][0-9]"))) + (: (in "0-9") (in "0-9") (in "0-9")))) 1 (2 . 4) (3 . 5) (6 . 7)) (cucumber @@ -954,7 +986,10 @@ Faces `compilation-error-face', `compilation-warning-face', (defcustom compilation-auto-jump-to-first-error nil "If non-nil, automatically jump to the first error during compilation." - :type 'boolean + :type '(choice (const :tag "Never" nil) + (const :tag "Always" t) + (const :tag "If location known" if-location-known) + (const :tag "First known location" first-known)) :version "23.1") (defvar-local compilation-auto-jump-to-next nil @@ -1185,21 +1220,46 @@ POS and RES.") l2 (setcdr l1 (cons (list ,key) l2))))))) +(defun compilation--file-known-p () + "Say whether the file under point can be found." + (when-let* ((msg (get-text-property (point) 'compilation-message)) + (loc (compilation--message->loc msg)) + (elem (compilation-find-file-1 + (point-marker) + (caar (compilation--loc->file-struct loc)) + (cadr (car (compilation--loc->file-struct loc))) + (compilation--file-struct->formats + (compilation--loc->file-struct loc))))) + (car elem))) + (defun compilation-auto-jump (buffer pos) (when (buffer-live-p buffer) (with-current-buffer buffer (goto-char pos) (let ((win (get-buffer-window buffer 0))) (if win (set-window-point win pos))) - (if compilation-auto-jump-to-first-error - (compile-goto-error))))) + (when compilation-auto-jump-to-first-error + (cl-case compilation-auto-jump-to-first-error + ('if-location-known + (when (compilation--file-known-p) + (compile-goto-error))) + ('first-known + (let (match) + (while (and (not (compilation--file-known-p)) + (setq match (text-property-search-forward + 'compilation-message nil nil t))) + (goto-char (prop-match-beginning match)))) + (when (compilation--file-known-p) + (compile-goto-error))) + (otherwise + (compile-goto-error))))))) ;; This function is the central driver, called when font-locking to gather ;; all information needed to later jump to corresponding source code. ;; Return a property list with all meta information on this error location. (defun compilation-error-properties (file line end-line col end-col type fmt - rule) + rule) (unless (text-property-not-all (match-beginning 0) (point) 'compilation-message nil) (if file @@ -1523,7 +1583,8 @@ to `compilation-error-regexp-alist' if RULES is nil." ;; FIXME-omake: Doing it here seems wrong, at least it should depend on ;; whether or not omake's own error messages are recognized. (cond - ((not omake-included) nil) + ((or (not omake-included) (not pat)) + nil) ((string-match "\\`\\([^^]\\|\\^\\( \\*\\|\\[\\)\\)" pat) nil) ;; Not anchored or anchored but already allows empty spaces. (t (setq pat (concat "^\\(?: \\)?" (substring pat 1))))) @@ -1542,7 +1603,7 @@ to `compilation-error-regexp-alist' if RULES is nil." (error "HYPERLINK should be an integer: %s" (nth 5 item))) (goto-char start) - (while (re-search-forward pat end t) + (while (and pat (re-search-forward pat end t)) (when (setq props (compilation-error-properties file line end-line col end-col (or type 2) fmt rule)) @@ -1755,13 +1816,21 @@ If nil, ask to kill it." :type 'boolean :version "24.3") +(defcustom compilation-max-output-line-length 400 + "Output lines that are longer than this value will be hidden. +If nil, don't hide anything." + :type '(choice (const :tag "Hide nothing" nil) + integer) + :version "29.1") + (defun compilation--update-in-progress-mode-line () ;; `compilation-in-progress' affects the mode-line of all ;; buffers when it changes from nil to non-nil or vice-versa. (unless compilation-in-progress (force-mode-line-update t))) ;;;###autoload -(defun compilation-start (command &optional mode name-function highlight-regexp) +(defun compilation-start (command &optional mode name-function highlight-regexp + continue) "Run compilation command COMMAND (low level interface). If COMMAND starts with a cd command, that becomes the `default-directory'. The rest of the arguments are optional; for them, nil means use the default. @@ -1778,6 +1847,12 @@ If HIGHLIGHT-REGEXP is non-nil, `next-error' will temporarily highlight the matching section of the visited source line; the default is to use the global value of `compilation-highlight-regexp'. +If CONTINUE is non-nil, the buffer won't be emptied before +compilation is started. This can be useful if you wish to +combine the output from several compilation commands in the same +buffer. The new output will be at the end of the buffer, and +point is not changed. + Returns the compilation buffer created." (or mode (setq mode 'compilation-mode)) (let* ((name-of-mode @@ -1841,7 +1916,12 @@ Returns the compilation buffer created." (if (= (length expanded-dir) 1) (car expanded-dir) substituted-dir))))) - (erase-buffer) + (if continue + (progn + ;; Save the point so we can restore it. + (setq continue (point)) + (goto-char (point-max))) + (erase-buffer)) ;; Select the desired mode. (if (not (eq mode t)) (progn @@ -1867,12 +1947,13 @@ Returns the compilation buffer created." (if (or compilation-auto-jump-to-first-error (eq compilation-scroll-output 'first-error)) (setq-local compilation-auto-jump-to-next t)) - ;; Output a mode setter, for saving and later reloading this buffer. - (insert "-*- mode: " name-of-mode - "; default-directory: " - (prin1-to-string (abbreviate-file-name default-directory)) - " -*-\n" - (format "%s started at %s\n\n" + (when (zerop (buffer-size)) + ;; Output a mode setter, for saving and later reloading this buffer. + (insert "-*- mode: " name-of-mode + "; default-directory: " + (prin1-to-string (abbreviate-file-name default-directory)) + " -*-\n")) + (insert (format "%s started at %s\n\n" mode-name (substring (current-time-string) 0 19)) command "\n") @@ -1891,28 +1972,33 @@ Returns the compilation buffer created." (and (derived-mode-p 'comint-mode) (comint-term-environment)) (list (format "INSIDE_EMACS=%s,compile" emacs-version)) + ;; Some external programs (like "git grep") use a pager; + ;; defeat that. + (list "PAGER=") (copy-sequence process-environment)))) (setq-local compilation-arguments (list command mode name-function highlight-regexp)) (setq-local revert-buffer-function 'compilation-revert-buffer) - (and outwin - ;; Forcing the window-start overrides the usual redisplay - ;; feature of bringing point into view, so setting the - ;; window-start to top of the buffer risks losing the - ;; effect of moving point to EOB below, per - ;; compilation-scroll-output, if the command is long - ;; enough to push point outside of the window. This - ;; could happen, e.g., in `rgrep'. - (not compilation-scroll-output) - (set-window-start outwin (point-min))) + (when (and outwin + (not continue) + ;; Forcing the window-start overrides the usual redisplay + ;; feature of bringing point into view, so setting the + ;; window-start to top of the buffer risks losing the + ;; effect of moving point to EOB below, per + ;; compilation-scroll-output, if the command is long + ;; enough to push point outside of the window. This + ;; could happen, e.g., in `rgrep'. + (not compilation-scroll-output)) + (set-window-start outwin (point-min))) ;; Position point as the user will see it. (let ((desired-visible-point - ;; Put it at the end if `compilation-scroll-output' is set. - (if compilation-scroll-output - (point-max) - ;; Normally put it at the top. - (point-min)))) + (cond + (continue continue) + ;; Put it at the end if `compilation-scroll-output' is set. + (compilation-scroll-output (point-max)) + ;; Normally put it at the top. + (t (point-min))))) (goto-char desired-visible-point) (when (and outwin (not (eq outwin (selected-window)))) (set-window-point outwin desired-visible-point))) @@ -2228,6 +2314,7 @@ The parent is always `compilation-mode' and the customizable `compilation-...' variables are also set from the name of the mode you have chosen, by replacing the first word, e.g., `compilation-scroll-output' from `grep-scroll-output' if that variable exists." + (declare (indent defun)) (let ((mode-name (replace-regexp-in-string "-mode\\'" "" (symbol-name mode)))) `(define-derived-mode ,mode compilation-mode ,name ,doc @@ -2407,8 +2494,8 @@ commands of Compilation major mode are available. See (defun compilation-filter (proc string) "Process filter for compilation buffers. -Just inserts the text, -handles carriage motion (see `comint-inhibit-carriage-motion'), +Just inserts the text, handles carriage motion (see +`comint-inhibit-carriage-motion'), `compilation-hidden-output', and runs `compilation-filter-hook'." (when (buffer-live-p (process-buffer proc)) (with-current-buffer (process-buffer proc) @@ -2428,13 +2515,18 @@ and runs `compilation-filter-hook'." ;; We used to use `insert-before-markers', so that windows with ;; point at `process-mark' scroll along with the output, but we ;; now use window-point-insertion-type instead. - (insert string) + (if (not compilation-max-output-line-length) + (insert string) + (dolist (line (string-lines string nil t)) + (compilation--insert-abbreviated-line + line compilation-max-output-line-length))) + (when compilation-hidden-output + (compilation--hide-output compilation-filter-start)) (unless comint-inhibit-carriage-motion (comint-carriage-motion (process-mark proc) (point))) (set-marker (process-mark proc) (point)) ;; Update the number of errors in compilation-mode-line-errors (compilation--ensure-parse (point)) - ;; (setq-local compilation-buffer-modtime (current-time)) (run-hooks 'compilation-filter-hook)) (goto-char pos) (narrow-to-region min max) @@ -2442,6 +2534,58 @@ and runs `compilation-filter-hook'." (set-marker min nil) (set-marker max nil)))))) +(defun compilation--hide-output (start) + (save-excursion + (goto-char start) + (beginning-of-line) + ;; Apply the match to each line, but wait until we have a complete + ;; line. + (let ((start (point))) + (while (search-forward "\n" nil t) + (save-restriction + (narrow-to-region start (point)) + (dolist (regexp (ensure-list compilation-hidden-output)) + (goto-char start) + (while (re-search-forward regexp nil t) + (add-text-properties (match-beginning 0) (match-end 0) + '( invisible t + rear-nonsticky t)))) + (goto-char (point-max))))))) + +(defun compilation--insert-abbreviated-line (string width) + (if (and (> (current-column) 0) + (get-text-property (1- (point)) 'button)) + ;; We already have an abbreviation; just add the string to it. + (let ((beg (point))) + (insert string) + (add-text-properties + beg + ;; Don't make the final newline invisible. + (if (= (aref string (1- (length string))) ?\n) + (1- (point)) + (point)) + (text-properties-at (1- beg)))) + (insert string) + ;; If we exceeded the limit, hide the last portion of the line. + (when (> (current-column) width) + (let ((start (save-excursion + (move-to-column width) + (point)))) + (buttonize-region + start (point) + (lambda (start) + (let ((inhibit-read-only t)) + (remove-text-properties start (save-excursion + (goto-char start) + (line-end-position)) + (text-properties-at start))))) + (put-text-property + start (if (= (aref string (1- (length string))) ?\n) + ;; Don't hide the final newline. + (1- (point)) + (point)) + 'display (if (char-displayable-p ?…) "[…]" "[...]")))))) + (defsubst compilation-buffer-internal-p () "Test if inside a compilation buffer." (local-variable-p 'compilation-locs)) @@ -2931,19 +3075,7 @@ and overlay is highlighted between MK and END-MK." (remove-hook 'pre-command-hook #'compilation-goto-locus-delete-o)) -(defun compilation-find-file (marker filename directory &rest formats) - "Find a buffer for file FILENAME. -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 where to find the file. -Search the directories in `compilation-search-path'. -A nil in `compilation-search-path' means to try the -\"current\" directory, which is passed in DIRECTORY. -If DIRECTORY is relative, it is combined with `default-directory'. -If DIRECTORY is nil, that means use `default-directory'. -FORMATS, if given, is a list of formats to reformat FILENAME when -looking for it: for each element FMT in FORMATS, this function -attempts to find a file whose name is produced by (format FMT FILENAME)." +(defun compilation-find-file-1 (marker filename directory &optional formats) (or formats (setq formats '("%s"))) (let ((dirs compilation-search-path) (spec-dir (if directory @@ -2992,6 +3124,23 @@ attempts to find a file whose name is produced by (format FMT FILENAME)." (find-file-noselect name)) fmts (cdr fmts))) (setq dirs (cdr dirs)))) + (list buffer spec-dir))) + +(defun compilation-find-file (marker filename directory &rest formats) + "Find a buffer for file FILENAME. +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 where to find the file. +Search the directories in `compilation-search-path'. +A nil in `compilation-search-path' means to try the +\"current\" directory, which is passed in DIRECTORY. +If DIRECTORY is relative, it is combined with `default-directory'. +If DIRECTORY is nil, that means use `default-directory'. +FORMATS, if given, is a list of formats to reformat FILENAME when +looking for it: for each element FMT in FORMATS, this function +attempts to find a file whose name is produced by (format FMT FILENAME)." + (pcase-let ((`(,buffer ,spec-dir) + (compilation-find-file-1 marker filename directory formats))) (while (null buffer) ;Repeat until the user selects an existing file. ;; The file doesn't exist. Ask the user where to find it. (save-excursion ;This save-excursion is probably not right. |