null-ls includes a library of built-in sources meant to provide out-of-the-box functionality. Built-in sources run with optimizations to reduce startup time and enable user customization.
See BUILTINS for a list of available built-in sources.
null-ls exposes built-ins on null_ls.builtins
, which contains the following
groups of sources:
local null_ls = require("null-ls")
-- code action sources
local code_actions = null_ls.builtins.code_actions
-- diagnostic sources
local diagnostics = null_ls.builtins.diagnostics
-- formatting sources
local formatting = null_ls.builtins.formatting
-- hover sources
local hover = null_ls.builtins.hover
-- completion sources
local completion = null_ls.builtins.completion
You can then register sources by passing a sources
list into your setup
function:
local null_ls = require("null-ls")
-- register any number of sources simultaneously
local sources = {
null_ls.builtins.formatting.prettier,
null_ls.builtins.diagnostics.write_good,
null_ls.builtins.code_actions.gitsigns,
}
null_ls.setup({ sources = sources })
To run built-in sources, the command specified below must be available on your
$PATH
and visible to Neovim. For example, to check if stylua
is available,
run the following (Vim, not Lua) command:
" should echo 1 if available (and 0 if not)
:echo executable("stylua")
Built-in sources have access to a special method, with()
, which modifies a
subset of the source's default options. See the descriptions below or the
relevant source file to see the default options passed to each built-in source.
Some options are specific to built-in sources that spawn external commands.
You can override a source's default filetypes as follows:
local sources = {
null_ls.builtins.formatting.prettier.with({
filetypes = { "html", "json", "yaml", "markdown" },
}),
}
You can also add extra filetypes if your source supports them via a plugin or configuration:
local sources = {
null_ls.builtins.formatting.prettier.with({
extra_filetypes = { "toml" },
}),
}
If you see filetypes = {}
in a source's description, that means the source is
active for all filetypes by default. You may want to define a specific list of
filetypes to avoid conflicts or other issues.
You can also pass a list of specifically disabled filetypes:
local sources = {
null_ls.builtins.code_actions.gitsigns.with({
disabled_filetypes = { "lua" },
}),
}
null-ls is always inactive in non-file buffers (e.g. file trees and finders) so theres's no need to specify them.
To add more arguments to a source's defaults, use extra_args
:
local sources = {
null_ls.builtins.formatting.shfmt.with({
extra_args = { "-i", "2", "-ci" },
}),
}
You can also override a source's arguments entirely using
with({ args = your_args })
.
Both args
and extra_args
can also be functions that accept a single
argument, params
, which is an object containing information about editor
state. LSP options (e.g. formatting options) are available as params.options
,
making it possible to dynamically set arguments based on these options:
local sources = {
null_ls.builtins.formatting.prettier.with({
extra_args = function(params)
return params.options
and params.options.tabSize
and {
"--tab-width",
params.options.tabSize,
}
end,
}),
}
You can inject environment variables to the process via utilizing the env
option. This option can be in the form of a dictionary. It can also, just like
args
and extra_args
, take a function receiving a params
object.
-- Using a dictionary:
local sources = {
null_ls.builtins.formatting.prettierd.with({
env = {
PRETTIERD_DEFAULT_CONFIG = vim.fn.expand("~/.config/nvim/utils/linter-config/.prettierrc.json"),
},
}),
}
-- Using a function:
local sources = {
null_ls.builtins.diagnostics.pylint.with({
env = function(params)
return { PYTHONPATH = params.root }
end,
}),
}
Note that environment variables and ~
aren't expanded in arguments. As a
workaround, you can use vim.fn.expand
:
local sources = {
null_ls.builtins.formatting.stylua.with({
extra_args = { "--config-path", vim.fn.expand("~/.config/stylua.toml") },
}),
}
You can filter generator results using the filter
option. The option should be
a function that returns true
to keep the result, and false
or nil
to
ignore it.
local sources = {
null_ls.builtins.diagnostics.eslint_d.with({
-- ignore prettier warnings from eslint-plugin-prettier
filter = function(diagnostic)
return diagnostic.code ~= "prettier/prettier"
end,
}),
}
You can configure how Neovim displays source diagnostics by setting
diagnostic_config
:
local sources = {
null_ls.builtins.diagnostics.shellcheck.with({
diagnostic_config = {
-- see :help vim.diagnostic.config()
underline = true,
virtual_text = false,
signs = true,
update_in_insert = false,
severity_sort = true,
},
}),
}
- Specifying
diagnostic_config
for a built-in will override your globaldiagnostic_config
for that source.
For diagnostics sources, you can change the format of diagnostic messages by
setting diagnostics_format
:
local sources = {
-- will show code and source name
null_ls.builtins.diagnostics.shellcheck.with({
diagnostics_format = "[#{c}] #{m} (#{s})",
}),
}
- See CONFIG to learn about the structure of
diagnostics_format
. - Specifying
diagnostics_format
for a built-in will override your globaldiagnostics_format
for that source. - This option is not compatible with
diagnostics_postprocess
(see below).
For advanced customization of diagnostics, you can use the
diagnostics_postprocess
hook. The hook receives a diagnostic conforming to the
structure described in :help diagnostic-structure
and runs after the
built-in's generator, so you can use it to change, override, or add data to each
diagnostic.
- Using this option may affect performance when processing a large number of diagnostics, since the hook runs once for each diagnostic.
- This option is not compatible with
diagnostics_format
(see above).
local sources = {
null_ls.builtins.diagnostics.write_good.with({
diagnostics_postprocess = function(diagnostic)
diagnostic.severity = diagnostic.message:find("really") and vim.diagnostic.severity["ERROR"]
or vim.diagnostic.severity["WARN"]
end,
}),
}
If you have performance issues with a diagnostic source, you can configure any
it to run on save (not on each change) by overriding method
:
local sources = {
null_ls.builtins.diagnostics.pylint.with({
method = null_ls.methods.DIAGNOSTICS_ON_SAVE,
}),
}
If the documentation lists a source's method as diagnostics_on_save
, that
source will not run on change. The diagnostics you see will not reflect
changes to the buffer until you write those changes to the disk.
Typically, this is a workaround for linters that require project context to produce accurate results, and overriding the method will not work.
By default, null-ls spawns commands using the root directory of its client, as
specified in CONFIG. You can override this on a per-source basis
by setting cwd
to a function that returns your preferred spawn directory:
local sources = {
null_ls.builtins.diagnostics.pylint.with({
cwd = function(params)
-- falls back to root if return value is nil
return params.root:match("my-special-project") and "my-special-cwd"
end,
}),
}
Commands will time out after the timeout specified in CONFIG. If
a specific command is consistently timing out due to your environment, you can
set a different timeout
:
local sources = {
null_ls.builtins.formatting.prettier.with({
-- milliseconds
timeout = 10000,
}),
}
Specifying a timeout with a value less than zero will prevent the command from ever timing out.
Some built-in sources write the buffer's content to a temp file before command
execution and / or read from a temp file after execution, as a workaround for
commands that don't support stdio
. To maximize compatibility, null-ls defaults
to creating temp files in the same directory as the parent file.
Under normal circumstances, this will work seamlessly, but if you run into
issues with file watchers / other integrations, you can override the directory
to /tmp
(or another appropriate directory) using the temp_dir
option:
local sources = {
null_ls.builtins.formatting.phpstan.with({
temp_dir = "/tmp",
}),
}
Note: some null-ls built-in sources expect temp files to exist within a project for context and so will not work if this option changes.
If you want to override this globally, you can change the temp_dir
option in
CONFIG.
To prefer using a local executable for a built-in, use the prefer_local
option. This will cause null-ls to search upwards from the current buffer's
directory, try to find a local executable at each parent directory, and fall
back to a global executable if it can't find one locally.
prefer_local
can be a boolean or a string, in which case it's treated as a
prefix. For example, the following settings will cause null-ls to search for
node_modules/.bin/prettier
:
local sources = {
null_ls.builtins.formatting.prettier.with({
prefer_local = "node_modules/.bin",
}),
}
To only use a local executable without falling back, use only_local
, which
accepts the same options.
You can also choose to override a source's command and specify an absolute path
if the command is not available on your $PATH
:
local sources = {
null_ls.builtins.formatting.prettier.with({
command = "/path/to/prettier",
}),
}
Another solution is to use the dynamic_command
option, as described in
HELPERS. Note that this option can affect performance.
Built-ins have access to the condition
option, which should be a function that
returns a boolean or nil
indicating whether null-ls should register the
source. condition
should return true
(indicating that the source should
continue to run) or a falsy value (indicating that the source should not run
anymore).
local sources = {
null_ls.builtins.formatting.stylua.with({
condition = function(utils)
return utils.root_has_file({ "stylua.toml", ".stylua.toml" })
end,
}),
}
For more information, see condition
in HELPERS.
Note that if you pass conditional sources into null_ls.setup
, null-ls will
check the condition at the first opportunity (typically upon entering a named
buffer). After checking, null-ls will not check the same condition again.
You can force null-ls to check whether a source should run each time by using
the runtime_condition
option, which is a callback called when generating a
list of sources to run for a given method. If the callback's return value is
falsy, the source does not run.
Be aware that runtime_condition
runs every time a source can run and thus
should avoid doing anything overly expensive.
local sources = {
null_ls.builtins.formatting.pylint.with({
-- this will run every time the source runs,
-- so you should prefer caching results if possible
runtime_condition = function(params)
return params.root:match("my-monorepo-subdir") ~= nil
end,
}),
}
null-ls supports dynamic registration, meaning that you can register sources
whenever you want using the methods described in SOURCES. To
handle advanced registration behavior not covered by the above, you can use
null_ls.register
with a relevant autocommand
event listener (or register
sources on demand).