Skip to main content
AI generated
Compilation mode runs a shell command, captures the output, and parses error messages so you can jump directly to the source location. It works for compilers, linters, test runners, and anything else that outputs file/line references.

Configuration

The compile package is configured in lisp/init-prog.el:
(use-package compile
  :ensure nil
  :custom
  (compilation-scroll-output 'first-error)
  (compilation-ask-about-save nil)
  (compilation-always-kill t))
  • compilation-scroll-output is set to 'first-error — the compilation buffer scrolls to follow output and stops scrolling when the first error appears.
  • compilation-ask-about-save is disabled — Emacs saves modified buffers before compiling without prompting.
  • compilation-always-kill is enabled — starting a new compilation kills any running one without asking.

Running a compilation

M-x compile prompts for a shell command and runs it. M-x recompile (or g in the compilation buffer) re-runs the last command. For per-language compile commands, compile-multi is configured in lisp/init-project.el with presets for Go, Python, Haskell, Nix, and Rust:
(compile-multi-config
 '((go-mode      . (("go test"        . "go test ./...")
                    ("go test current" . "go test .")
                    ("go build"       . "go build .")))
   (python-mode  . (("pytest"         . "pytest")
                    ("pytest file"    . "pytest %file-name%")))
   (haskell-mode . (("stack test"     . "stack test")
                    ("cabal test"     . "cabal test")))
   (nix-ts-mode  . (("nix flake check" . "nix flake check")
                    ("nix fmt"        . "nix fmt")))
   (rust-ts-mode . (("cargo test"     . "cargo test")
                    ("cargo clippy"   . "cargo clippy --all-targets")
                    ("cargo build"    . "cargo build")))))
Run M-x compile-multi to pick from these. The consult-compile-multi integration provides a narrowing UI.

In the compilation buffer

KeyCommandWhat it does
grecompileRe-run the last compilation command
M-ncompilation-next-errorMove to the next error message
M-pcompilation-previous-errorMove to the previous error message
RETcompile-goto-errorJump to the source location at point
C-c C-fnext-error-follow-minor-modeAuto-display source as you move through errors

From any buffer

The standard M-g n (next-error) and M-g p (previous-error) work from any buffer — Emacs tracks the last buffer that produced errors and jumps you there. This works across compile, grep, occur, and any other mode that produces error-like output. M-g M-n and M-g M-p do the same thing but are easier to type since you can hold Meta throughout. This config also binds M-g e to consult-compile-error (in lisp/init-completion.el), which provides a searchable, narrowable list of all compilation errors via consult.

Flymake integration

For in-buffer diagnostics, M-n and M-p are bound to flymake-goto-next-error and flymake-goto-prev-error in flymake-mode-map (in lisp/init-prog.el). These navigate flymake diagnostics inline without needing a compilation buffer.

Editing grep results

The wgrep package (configured in lisp/init-prog.el) enables editing grep result buffers in place. The workflow is:
  1. consult-ripgrep — search across the project
  2. C-c C-o (embark-export) — export results to a grep buffer
  3. C-x C-q — enter wgrep edit mode
  4. Edit the results as you would any buffer
  5. C-c C-c — apply changes to all matched files
Starting with Emacs 31, a built-in grep-edit-mode will also be available.

How error parsing works

Compilation mode uses compilation-error-regexp-alist and compilation-error-regexp-alist-alist to parse error output. Emacs ships with dozens of entries for GCC, Java, Ruby, Python, Perl, and many more — no configuration needed for common tools. Each entry has the shape:
(SYMBOL REGEXP FILE LINE COLUMN TYPE HYPERLINK HIGHLIGHT...)
Where TYPE controls severity: 2 = error, 1 = warning, 0 = info. The TYPE field can also be a cons cell for conditional severity based on match groups.

Adding a custom error regexp

If you use a tool whose output format is not recognized, you can teach compilation mode about it:
(with-eval-after-load 'compile
  (push '(my-linter
          "^\\[\\(?:ERROR\\|\\(WARN\\)\\)\\] \\([^:]+\\):\\([0-9]+\\):\\([0-9]+\\)"
          2 3 4 (1))
        compilation-error-regexp-alist-alist)
  (push 'my-linter compilation-error-regexp-alist))

Useful variables

Beyond what this config sets, a few more compilation variables are worth knowing:
;; Skip warnings and info when navigating with next-error.
;; 2 = only errors, 1 = errors + warnings, 0 = everything (default).
(setopt compilation-skip-threshold 2)

;; Some languages (e.g. OCaml) use 0-indexed columns.
(setq compilation-first-column 0)

;; Auto-close the compilation window on success.
(add-hook 'compilation-finish-functions
          (lambda (buf status)
            (when (string-match-p "finished" status)
              (run-at-time 1 nil #'delete-windows-on buf))))

The compilation mode family

Compilation mode is not just for compilers — it is the infrastructure for any structured output with source locations:
  • grep-modeM-x grep, M-x rgrep, M-x lgrep all produce compilation-derived buffers with the same navigation.
  • occur-modeM-x occur participates in the same next-error infrastructure.
  • flymake — uses compilation-style error navigation under the hood.
Any tool that outputs file/line references can plug into this system. Whether you are navigating compiler errors, grep results, test failures, or linter output, the workflow is the same: M-g n to jump to the next problem.
Last modified on April 15, 2026