For many tasks in a developer’s day, a well-tuned compilation mode is more efficient than reaching for a shell (fewer keystrokes, less context switching) and lighter on the brain than specialized major modes (no new key bindings).
Emacs’s “compilation” mode is not limited to running compilers. You can run bundlers for front-end development, test runners for red-green-refactor TDD, project-wide linters, dev servers for back-end code, Makefile targets, and more.
These notes cover:
- handling commands that produce rich output, and/or clear the screen between multiple runs in a single long-lived session
- choosing efficient key bindings
- browsing command output without leaving the source window
- selecting commands from configurable, contextual lists using the
run-command
package
Rich output and long-lived commands
Colors are often the only way to notice errors in a flurry of output. Also, commands with a long startup time sometimes offer a “watch” mode to do several runs for a single invocation, and clear the screen between runs.
Compilation mode by default handles neither the control characters that produce colors, nor those that clear the screen. A simple solution that covers many cases is to watch out for a few sequences in the command output using a “compilation filter”, and apply them:
(defun local/postprocess-compilation-buffer ()
(goto-char compilation-filter-start)
(when (looking-at "\033c")
(delete-region (point-min) (match-end 0)))
(ansi-color-apply-on-region (point) (point-max))))
(add-hook 'compilation-filter-hook 'local/postprocess-compilation-buffer)
Some commands produce even richer output (progress bars, simple animations) that expects terminal capabilities such as repositioning the cursor and partially overwriting text. The run-command
package (described later) can run them in Emacs’s term-mode
, providing those capabilities, while maintaining compilation-mode
functionality.
Choosing efficient key bindings
The basic way to run a command in compilation mode is M-x compile RET <command> RET
, e.g. M-x compile RET npm start RET
. Next time you invoke compile
, <command>
defaults to the previous command, so recompiling effectively becomes M-x compile RET RET
.
However, recompiling being the more common case, Emacs also provides M-x recompile
, which reuses the last command right away, without prompting. (You can still edit the command with C-u M-x recompile
.) So, recompile
is the better candidate for quick access via a key binding, e.g.:
(global-set-key (kbd "C-c c") 'recompile)
With that, C-c c
will re-run the last command, and C-u C-c c
will let you edit the command first.
There’s an even shorter binding for when the cursor is already in the compilation window: g
. This is useful when you need to alternate among several complex commands: leave their buffers open, then switch back to them and press g
as needed.
To stop a command, move the cursor to the compilation buffer and press C-c k
.
Browsing command output without leaving the source window
Unless you need to copy a command’s output, or re-run a previous command, there’s little reason to move the cursor to the command output window; you can more efficiently browse it without leaving the window where you’re writing using these key bindings:
- To scroll to the beginning of the output:
M-home
- To scroll to the end of the output:
M-end
- To scroll the output forward:
M-next
(where “next” meansPageDown
) orC-M-v
- To scroll the output backward:
M-prior
(where “prior” meansPageUp
) orC-M-S-v
These bindings are not specific to compilation mode, the work in every situation where there is an “other window”. They may seem cumbersome, especially on laptop keyboards which require an additional Fn
press, but are well worth the effort.
Another flow saver, provided that the command prints errors in a format that compilation mode understands, is C-x `
, which jumps to errors in the output, in succession. For example, given a TypeScript project with a file src/App.tsx
:
import React from 'react'
import ReactDOM from 'react-dom'
console.helloWorld()
Running M-x compile RET npxj tsc -P . --noEmit --watch
will launch the type checker and print the following:
[8:13:36 PM] File change detected. Starting incremental compilation...
src/App.tsx:3:9 - error TS2339: Property 'helloWorld' does not exist on type 'Console'.
3 console.helloWorld()
~~~~~~~~~~
[8:13:36 PM] Found 1 error. Watching for file changes.
If you now hit C-x `
, the App.tsx
buffer will appear, with the cursor on helloWorld
.
Selecting commands from configurable, contextual lists using the run-command
package
If, in a given context, you tend to run the same small subset of commands over and over, (e.g. npm test:watch
in a JavaScript project, make clean
in a C project, hugo server --buildDrafts
in a Hugo blog), that’s a prime case for automation.
The run-command
Emacs package (by the author) lets you write simple command lists, bring them up depending on the context, and select them with autocompletion:
See the run-command GitHub page for setup and usage instructions.
Additional customization
- To have the window scroll as output comes in, set
compilation-scroll-output
tot
.- The default scrolling mode often leads to a large amount of empty space at the bottom of the window; this is due to “recentering”. To prevent that and display as much output as possible, set
scroll-conservatively
to101
(seeM-x describe-variable scroll-conservatively RET
for why).
- The default scrolling mode often leads to a large amount of empty space at the bottom of the window; this is due to “recentering”. To prevent that and display as much output as possible, set
- To have it scroll, but stop scrolling when the first error is detected, set
compilation-scroll-output
toerror