Rovanion
Rovanion

Reputation: 4582

Don't display *compilation* buffer in Emacs until the process exits with error or warning

I am looking for a way to have the Emacs compilation buffer triggered by M-x compile, M-x recompile or some compile on save script only appear when the compilation exits either with an error or a warning.

Note that I am not looking for a way to close the compile buffer if there are no errors or warnings as described in [1]. No I want the buffer to never appear until the compilation is fully finished and only appear if there is an error or warning to display.

The reasons are simple: The flickering compile buffer is disturbing and rearranges the position of the code on the screen. This becomes more annoying if you have compile on save turned on.

The compile buffer contains many different types of compile processes from make to pdflatex so it would be great if the function which determines whether the buffer should be displayed works across the board.

[1] emacs compile buffer auto close?

Upvotes: 11

Views: 3175

Answers (5)

vosov
vosov

Reputation: 121

The following displays the compilation buffer only on errors. If the buffer is displayed and a new compilation completes without errors, the buffer will disappear.

;; Prevent the compilation buffer from being displayed until the compilation
;; process has finished. After that, ensure that this buffer is visible if and
;; only if the compilation failed.

;; (compile.el displays the buffer with (allow-no-window . t))
(add-to-list 'display-buffer-alist
             '("\\*compilation\\*" (display-buffer-no-window)))
;; After the compilation process is finished:
(setq compilation-exit-message-function
      (lambda (status code msg)
        (let ((compilation-window
               (get-buffer-window "*compilation*")))
          (cond
           ;; If compilation failed and compilation buffer is not visible,
           ((and (eq status 'exit)
                 (not (zerop code))
                 (not compilation-window))
            ;; temporarily allow displaying the buffer,
            (let ((display-buffer-overriding-action
                   '((display-buffer-use-least-recent-window))))
              ;; and display that buffer.
              (display-buffer "*compilation*")))
           ;; If compilation succeeded and compilation buffer is visible,
           ((and (eq status 'exit)
                 (zerop code)
                 compilation-window)
            ;; bury that buffer.
            (with-selected-window compilation-window
              (bury-buffer)))))
        (cons msg code))) ;; default return value

Upvotes: 2

Rovanion
Rovanion

Reputation: 4582

I edited @assem's answer to use cl-letf instead of flet.

(defun brian-compile-finish (buffer outstr)
  (unless (string-match "finished" outstr)
    (switch-to-buffer-other-window buffer))
  t)

(setq compilation-finish-functions 'brian-compile-finish)

(defadvice compilation-start
  (around inhibit-display
      (command &optional mode name-function highlight-regexp))
  (if (not (string-match "^\\(find\\|grep\\)" command))
      (cl-letf ((display-buffer   #'ignore)
                (set-window-point #'ignoreco)
                (goto-char        #'ignore))
        (save-window-excursion
          ad-do-it))
    ad-do-it))

(ad-activate 'compilation-start)

(provide 'only-display-compile-on-error)

Upvotes: 3

Att Righ
Att Righ

Reputation: 1779

assems answer has been overtaken by events, somewhat.

Emacs core, in their wisdom have decided to deprecate flet. The suggested alternative is cl-flet. However, as discussed in this post, this seems to be lexically scoped rather than dynamically scoped. We explicitly want dynamic scoping.

Should `flet` be replaced with `cl-flet` or `cl-letf` ?

This page suggests replace flet to noflet, a third-party library.

This appears to only support the definition of functions and their bodies. So the flet in assem's answer becomes

(noflet ((display-buffer ()) ....

Upvotes: 1

assem
assem

Reputation: 2097

Looks like you can achieve what you want through temporarily disabling display-buffer across compilation-start.

This is a combination of what sds said and something posted on the comments @ here

The comment there had a nasty problem with point jumping in the original source buffer that I appear to have worked out by also blocking set-window-point and goto-char. It feels like a dirty hack, but is working so far. YMMV!

(defun brian-compile-finish (buffer outstr)
  (unless (string-match "finished" outstr)
    (switch-to-buffer-other-window buffer))
  t)

(setq compilation-finish-functions 'brian-compile-finish)

(require 'cl)

(defadvice compilation-start
  (around inhibit-display
      (command &optional mode name-function highlight-regexp)) 
  (if (not (string-match "^\\(find\\|grep\\)" command))
      (flet ((display-buffer)
         (set-window-point)
         (goto-char)) 
    (fset 'display-buffer 'ignore)
    (fset 'goto-char 'ignore)
    (fset 'set-window-point 'ignore)
    (save-window-excursion 
      ad-do-it))
    ad-do-it))

(ad-activate 'compilation-start)

Now you should see that all compile buffers will only be shown if outstr doesn't return a successful finish status OR the invoked command started with "find" or "grep."

Upvotes: 6

sds
sds

Reputation: 60014

The function compilation-start calls display-buffer on the compilation buffer. This should give you all the control you need.

I.e., you need to customize one of the action variables (display-buffer-overriding-action et al) so that it will handle compilation buffers specially buy displaying it in a separate frame and not displaying the frame itself.

Then you need to customize your compilation-filter-hook so that, whenever a warning or an error is inserted into the compilation buffer, the compilation buffer is displayed visibly (e.g., by popping up the aforementioned separate frame). Don't forget to bind your action variable to nil there!

Upvotes: 1

Related Questions