Endless Parentheses

Ramblings on productivity and technical subjects.

profile for Malabarba on Stack Exchange

Provide input to the compilation buffer

The Emacs compile command is a severely underused tool. It allows you to run any build tool under the sun and provides error-highlighting and jump-to-error functionality for dozens of programming languages, but many an Emacser is still in the habit of switching to a terminal in order to run make, lein test, or bundle exec. It does have one limitation, though. The compilation buffer is not a real shell, so if the command being run asks for user input (even a simple y/n confirmation) there’s no way to provide it.

(Since posting this, I’ve learned that part of it is mildly futile. Read the update below for more information.)

Fortunately, that’s not hard to fix. The snippet below defines two commands. The first one prompts you for input and then sends it to the underlying terminal followed by a newline, designed for use with prompts and momentary REPLs. The second is a command that simply sends the key that was pressed to invoke it, designed for easily replying to y/n questions or quickly quitting REPLs with C-d or C-j.

(defun endless/send-input (input &optional nl)
  "Send INPUT to the current process.
Interactively also sends a terminating newline."
  (interactive "MInput: \nd")
  (let ((string (concat input (if nl "\n"))))
    ;; This is just for visual feedback.
    (let ((inhibit-read-only t))
      (insert-before-markers string))
    ;; This is the important part.
    (process-send-string
     (get-buffer-process (current-buffer))
     string)))

(defun endless/send-self ()
  "Send the pressed key to the current process."
  (interactive)
  (endless/send-input
   (apply #'string
          (append (this-command-keys-vector) nil))))

(dolist (key '("\C-d" "\C-j" "y" "n"))
  (define-key compilation-mode-map key
    #'endless/send-self))

This is something I’ve run into for years, but I finally decided to fix it because it meant I couldn’t run Ruby’s rspec in the compilation buffer if my code contained a binding.pry (which spawns a REPL). Now I can actually interact with this REPL via C-c i or just quickly get rid of it with C-d. If you run into the same situation, you should also set the following option in your .pryrc file.

Pry.config.pager = false if ENV["INSIDE_EMACS"]

Update 17 Jun 2016

As Clément points out in the comments, you can run compilation commands in comint-mode by providing the C-u prefix to M-x compile. You still have all of the usual compilation-mode features (like next/previous-error), with the additional benefit that the buffer accepts input like a regular shell does.

The only caveat is that, since the buffer is modifiable, you lose some convenience keys like q to quit the buffer or g to recompile, so you’ll need to bind them somewhere else

(define-key compilation-minor-mode-map (kbd "<f5>")
  #'recompile)
(define-key compilation-minor-mode-map (kbd "<f9>")
  #'quit-window)

(define-key compilation-shell-minor-mode-map (kbd "<f5>")
  #'recompile)
(define-key compilation-shell-minor-mode-map (kbd "<f9>")
  #'quit-window)

I still like it that the previous solution gives me quick access to C-d and y/n for those cases when I forget to use comint-mode, but the solution I had for inputting long strings is definitely redundant now. Instead, we can have a key that restarts the current compilation in comint-mode.

(require 'cl-lib)
(defun endless/toggle-comint-compilation ()
  "Restart compilation with (or without) `comint-mode'."
  (interactive)
  (cl-callf (lambda (mode) (if (eq mode t) nil t))
      (elt compilation-arguments 1))
  (recompile))

(define-key compilation-mode-map (kbd "C-c i")
  #'endless/toggle-comint-compilation)
(define-key compilation-minor-mode-map (kbd "C-c i")
  #'endless/toggle-comint-compilation)
(define-key compilation-shell-minor-mode-map (kbd "C-c i")
  #'endless/toggle-comint-compilation)
comments powered by Disqus