Check buffers¶
Flycheck provides two Emacs minor modes for automatic syntax checking:
Flycheck Mode to enable syntax checking in the current buffer, and
Global Flycheck Mode to enable syntax checking in all buffers whenever
possible.
- Minor Mode Flycheck Mode¶
Enable automatic syntax checking in the current buffer.
- Minor Mode Global Flycheck Mode¶
Enable
Flycheck Modein all buffers where syntax checking is possible.Note
Remote files (via TRAMP) are checked like local ones: command checkers run on the remote host, so the tools need to be installed there rather than locally. Because each check spawns a process over the network, remote buffers are checked on fewer triggers by default; see
flycheck-check-syntax-automatically-remote.This mode does not enable
Flycheck Modein encrypted files, as checking them would leak confidential data to temporary files and subprocesses. You can manually enableFlycheck Modein these buffers nonetheless, but we do not recommend it for said reason.Add the following to your init file to enable syntax checking permanently:
(add-hook 'after-init-hook #'global-flycheck-mode)
You can exclude specific major modes from syntax checking with
flycheck-global-modes:- defcustom flycheck-global-modes¶
Major modes for which
Global Flycheck Modeturns onFlycheck Mode:t(the default)Turn
Flycheck Modeon for all major modes.(foo-mode …)Turn
Flycheck Modeon for all major modes in this list, i.e. whenever the value ofmajor-modeis contained in this list.(not foo-mode …)Turn
Flycheck Modeon for all major modes not in this list, i.e. whenever the value ofmajor-modeis not contained in this list.
Note
Global Flycheck Modenever turns onFlycheck Modein major modes whosemode-classproperty isspecial, regardless of the value of this option. Syntax checking simply makes no sense in special buffers which are typically intended for non-interactive display rather than editing.See also
- :infonode:`(elisp)Major Mode Conventions`
Information about major modes, and modes marked as special.
Check automatically¶
By default Flycheck Mode automatically checks a buffer whenever
it is enabled,
the buffer is saved,
the buffer is reverted (e.g. via
global-auto-revert-mode),a new line is inserted,
or a short time after the last change was made in a buffer.
You can customise this behaviour with flycheck-check-syntax-automatically:
- defcustom flycheck-check-syntax-automatically¶
A list of events which trigger a syntax check in the current buffer:
saveCheck the buffer immediately after it was saved.
new-lineCheck the buffer immediately after a new line was inserted.
idle-changeCheck the buffer a short time after the last change. The delay is customisable with
flycheck-idle-change-delay:- defcustom flycheck-idle-change-delay¶
Seconds to wait after the last change to the buffer before starting a syntax check.
idle-buffer-switchCheck the buffer a short time after switching to it from another buffer. The delay is customisable with
flycheck-idle-buffer-switch-delay:- defcustom flycheck-idle-buffer-switch-delay¶
Seconds to wait after switching to a buffer before starting a syntax check.
If you switch to several buffers in rapid succession, the behavior depends on
flycheck-buffer-switch-check-intermediate-buffers:- defcustom flycheck-buffer-switch-check-intermediate-buffers¶
If non-nil, then a buffer you switch to will have a syntax check run even if you switch to another buffer before it starts. If nil, then only the current buffer can have a syntax check run. Note that syntax checks can still be run in other buffers due to changes to their contents.
mode-enabledCheck the buffer immediately after
Flycheck Modewas enabled.
For instance with the following setting
Flycheck Modewill only check the buffer when it was saved:(setq flycheck-check-syntax-automatically '(mode-enabled save))
- defcustom flycheck-check-syntax-automatically-remote¶
The same as
flycheck-check-syntax-automatically, but for buffers visiting remote files (seefile-remote-p). Checking a remote buffer spawns a process on the remote host over TRAMP, which is slow, so by default the change-driven triggers (idle-change,new-lineandidle-buffer-switch) are excluded and remote buffers are only checked onsaveandmode-enabled.Set it to
tto check remote buffers on the same events as local ones. A manual check with flycheck-buffer (C-c ! c) always works regardless of this option.
When a change-driven syntax check (idle-change or save) is triggered
while a recently started check is still running, the running check is
interrupted and the new one starts immediately, since its results would no
longer match the buffer. Checks triggered on every keystroke (the
new-line condition) or by buffer switches coalesce behind the running
check instead, and syntax checkers that cannot be interrupted or that have
already made substantial progress are left to finish:
- defcustom flycheck-interrupt-running-checks¶
Whether a new syntax check interrupts a running one. With the default value of
10, only running checks younger than ten seconds are interrupted; checks that have made more progress are left to complete, and the new check is deferred until they finish, so slow syntax checkers still publish their results. Set totto always interrupt, ornilto never interrupt and always defer, like older Flycheck versions did. Settingnilfile- or directory-locally is handy for projects whose syntax checkers you never want interrupted.
Check manually¶
You can also start a syntax check explicitly with C-c ! c:
LSP diagnostics via Eglot¶
Eglot, Emacs’ built-in LSP client, renders its diagnostics through Flymake and
provides no Flycheck backend of its own. flycheck-eglot-mode bridges the
gap, so a buffer can use Eglot for LSP features while Flycheck owns the error
display, navigation and the error list:
(global-flycheck-eglot-mode 1)
- Minor Mode Flycheck Eglot Mode¶
Report the diagnostics Eglot receives from the LSP server through Flycheck, via the
eglot-checkchecker, and turn Flymake off in the buffer. Only active in Eglot-managed buffers.
- Minor Mode Global Flycheck Eglot Mode¶
Enable
flycheck-eglot-modein every Eglot-managed buffer. This is the usual way to turn the integration on.
- defcustom flycheck-eglot-exclusive¶
When non-nil (the default), an Eglot buffer reports only the LSP server’s diagnostics. Set to nil to have
eglot-checkchain to the first command checker that supports the buffer’s major mode, so an LSP server and a command checker both contribute.
When the server supports code actions, an Eglot diagnostic’s quickfix
action is offered as a Flycheck fix: press C-c ! f on the error to apply it
(or C-c ! F to apply all). The action is fetched from the server only when
you apply a fix, so every diagnostic is shown as potentially fixable even if the
server has nothing for it.
- defcustom flycheck-eglot-code-actions¶
When non-nil (the default), offer LSP
quickfixcode actions as fixes, as described above. Set to nil to turn this off (and stop marking every diagnostic as fixable).
This obsoletes the third-party flycheck-eglot package; drop it from your
configuration if you used it.
Warning
The built-in mode uses the same names – flycheck-eglot-mode and
global-flycheck-eglot-mode – as the old package. If both are
installed they define the same commands, so uninstall the third-party
``flycheck-eglot`` package before relying on the built-in one.
LSP diagnostics without Eglot¶
A growing number of linters ship their own LSP server - RuboCop
(rubocop --lsp), Ruff (ruff server), Biome (biome lsp-proxy) and
more. flycheck-lsp-mode talks to one of these directly, over Emacs’
built-in jsonrpc library, and reports its diagnostics through the flycheck-lsp
checker - no Eglot, and no full LSP client, involved.
This is the setup the native LSP support is built for, and it is worth preferring when a linter offers it. A traditional command checker spawns the linter afresh on every check, and for a tool with a slow start (RuboCop loading your project, say) that startup cost is paid over and over. The linter’s own LSP server instead stays resident and lints incrementally, so checks are much snappier - and you get that without pulling in a full language server and client just to see diagnostics.
Turn it on everywhere with:
(global-flycheck-lsp-mode 1)
Out of the box this covers RuboCop (Ruby), Ruff (Python), Biome (JavaScript, TypeScript, JSON, CSS) and Harper (Markdown prose) - each lints with sensible defaults, no project configuration needed. A server is only used when its program is installed, so enabling the mode globally does nothing in a buffer whose language server you don’t have.
- Minor Mode Flycheck Lsp Mode¶
Start the LSP server configured for the buffer’s major mode, send it the buffer’s text, and report the diagnostics it pushes back through the
flycheck-lspchecker. Does nothing in a buffer whose major mode has no configured, installed server.
- Minor Mode Global Flycheck Lsp Mode¶
Enable
flycheck-lsp-modein every buffer whose major mode has an installed server inflycheck-lsp-servers.
Which LSP integration should I use?¶
Flycheck can get LSP diagnostics three ways. Pick by how much of a language server you actually want:
|
|
|
|
|---|---|---|---|
Runs the server |
Flycheck, over built-in |
Eglot |
|
Extra dependency |
none |
Eglot (built in since Emacs 29) |
the external |
Provides |
diagnostics + quickfix code actions |
full LSP (hover, completion, rename, …) |
full LSP |
Owns the buffer |
no - runs like any checker, can chain to a command checker |
yes |
yes |
Best for |
single-purpose linters as a Flycheck checker |
a real language server you also edit with |
if you already run |
In short: reach for flycheck-lsp-mode when you just want a linter that
happens to speak LSP, and for flycheck-eglot-mode (see above) when you want
a full language server whose diagnostics Flycheck should own.
Configuring servers¶
- defcustom flycheck-lsp-servers¶
An alist mapping a major mode to the server command, as
(MAJOR-MODE PROGRAM ARG...). The defaults are:Language / mode
Server
Command
Ruby
RuboCop
rubocop --lspPython
Ruff
ruff serverJavaScript/TypeScript/JSON/CSS
Biome
biome lsp-proxyMarkdown (prose)
Harper
harper-ls --stdioThese lint out of the box. Other linters ship an LSP server too but need project configuration before they report anything - TFLint wants a
.tflint.hcland plugins, Buf wants a module, Taplo wants a workspace config it fetches overworkspace/configuration(which theflycheck-lspchecker does not answer) - so they are left out of the defaults. Add them once your project is set up:(add-to-list 'flycheck-lsp-servers '(terraform-mode "tflint" "--langserver")) (add-to-list 'flycheck-lsp-servers '(protobuf-mode "buf" "lsp" "serve")) (add-to-list 'flycheck-lsp-servers '(toml-mode "taplo" "lsp" "stdio"))
A major mode maps to a single server. To use a different one, replace the entry - e.g. Standard instead of RuboCop, or Oxlint instead of Biome:
(setf (alist-get 'ruby-mode flycheck-lsp-servers) '("standardrb" "--lsp")) (setf (alist-get 'js-ts-mode flycheck-lsp-servers) '("oxlint" "--lsp"))
To drop a default, remove it:
(setq flycheck-lsp-servers (assq-delete-all 'css-mode flycheck-lsp-servers))
The server must speak LSP over stdio and publish diagnostics; a full language server works too, but for one of those you usually want Eglot. An entry whose PROGRAM is not on
exec-pathis ignored, so listing a server you have not installed is harmless. Flycheck starts one process per project root and command, feeds it the buffer’s unsaved text, and shuts it down when Emacs exits.
- defcustom flycheck-lsp-exclusive¶
When non-nil (the default), a buffer reports only the language server’s diagnostics. Set to nil to have
flycheck-lspchain to the first command checker that supports the buffer’s major mode, so the server and a command checker both contribute.
- defcustom flycheck-lsp-code-actions¶
When non-nil (the default), offer the server’s
quickfixcode actions as Flycheck fixes:C-c ! fapplies the fix of the error at point,C-c ! Fapplies all, and the error list marks fixable errors with[fix]. Set to nil to turn this off.Flycheck gets these actions two ways. Some servers (RuboCop, standardrb) ship each diagnostic’s autocorrect inline, in the diagnostic’s data; Flycheck uses it directly, for free. Others (Ruff, Biome, Oxlint) answer a
textDocument/codeActionrequest, which Flycheck sends only when you apply a fix - so with those, a diagnostic is shown as potentially fixable even when the server turns out to have nothing for it.
- defcustom flycheck-lsp-initialize-timeout¶
How many seconds to wait for a server to answer the
initializehandshake. The handshake runs asynchronously and does not block Emacs: the first check of a buffer whose server is still starting reports no diagnostics, and the buffer is rechecked once the handshake finishes. A server that does not answer in time is torn down and retried later. Defaults to 5.
Diagnostics from the flycheck-lsp checker carry the same detail as Eglot’s - error
level, id, an explanation URL from codeDescription, and the secondary
locations of relatedInformation (visit them with C-c ! j). When the
server offers code actions, they are available as fixes as well (see
flycheck-lsp-code-actions above).
Full language servers¶
The flycheck-lsp checker consumes only diagnostics, so nothing stops you pointing it
at a full language server - clojure-lsp, ruby-lsp, gopls and the like - and
letting Flycheck surface its diagnostics:
(add-to-list 'flycheck-lsp-servers '(clojure-mode "clojure-lsp"))
This can be handy: many full servers bundle a linter (clojure-lsp runs clj-kondo, ruby-lsp runs RuboCop), so this is one way to get that linting into Flycheck without running a full LSP client at all.
But it goes against the grain of what this feature is for. flycheck-lsp-mode
is aimed at linters that ship their own LSP server, precisely so you can lint
without either the repeated startup cost of a command checker or the weight of a
full server-plus-client stack. A full language server is the opposite trade:
you start a process that indexes the whole project to provide completion, hover,
rename and much more, and Flycheck throws all of that away and keeps only the
errors. If you want those features too, run the server through Eglot and use
flycheck-eglot-mode; if you only want diagnostics, a dedicated linter is
lighter than a language server.
Two caveats if you do point it at a full server: some report diagnostics only
through LSP pull requests rather than pushing them, which the flycheck-lsp checker
does not yet support (it will report nothing), and some need extra
initializationOptions or workspace configuration to lint the way you expect.