All-around helper for dealing with errors and diagnostics.
TypeScript error messages. Code blocks in the error message are
formatted with prettier or biome if available.// eslint disable-next-line some-rule. Supports previous line, same line, and enclosing lines.// prettier-ignore.You easily add a custom source via the plugin configuration. Please consider making a PR to add support for a source if it is missing.
Rule data for built-in support of linters and formatters
ansible-lintbasedpyrightbiomeclang-tidyeslintltex_plusLTeXLua Diagnostics.markdownlint-cli2markdownlintpylintPyreflyPyrightquick-lint-jsRuffseleneshellcheckstylelintstylelintplusswiftlinttstsservertytypescriptyamllint--ignore-regex to ignore-regex=".*codespell-ignore$")lua_ls)default.extend-ignore-re to [default] extend-ignore-re = ["[^\ ]*typos: ignore-line[^\n]*\n"])c, cppcss, html, javascript, javascriptreact, markdown, scss, svelte,
typescript, typescriptreact, vue, yamlpythonluaCurrently only supports the TypeScript LSP (ts_ls).
Take a look at this file to see how to add prettifiers for other sources. PRs are welcome.
Requirements
nvim built-in LSP client,
efm-langserver or
nvim-lint are such sources.)Recommended for error prettifying
TSInstall markdown markdown_inlineprettier or biome available in PATH.-- lazy.nvim
{ "chrisgrieser/nvim-rulebook" },
-- packer
use { "chrisgrieser/nvim-rulebook" }
You can use the commands via Lua functions:
require("rulebook").ignoreRule()
require("rulebook").prettifyError()
require("rulebook").yankDiagnosticCode()
require("rulebook").suppressFormatter()
require("rulebook").prettifyError()
-- snippets to create keymaps, for your convenience
vim.keymap.set("n", "<leader>ri", function() require("rulebook").ignoreRule() end)
vim.keymap.set("n", "<leader>rl", function() require("rulebook").lookupRule() end)
vim.keymap.set("n", "<leader>ry", function() require("rulebook").yankDiagnosticCode() end)
vim.keymap.set({ "n", "x" }, "<leader>rf", function() require("rulebook").suppressFormatter() end)
vim.api.nvim_create_autocmd("Filetype", {
pattern = { "typescript", "javascript" },
group = vim.api.nvim_create_augroup("rulebook.prettify-ts-error", { clear = true }),
callback = function(ctx)
vim.keymap.set(
"n",
"<leader>rp",
function() require("rulebook").prettifyError() end,
{ buffer = ctx.buf }
)
end,
})
Alternatively, you can use the :Rulebook ex-command:
:Rulebook ignoreRule
:Rulebook lookupRule
:Rulebook yankDiagnosticCode
:Rulebook suppressFormatter
:Rulebook prettifyError
Note that :Rulebook suppressFormatter only supports normal mode. To add
formatter-ignore comments for a line range, you need to use the Lua function
require("rulebook").suppressFormatter() from visual mode.
The .setup() call is optional. You only need to add a config when you want to
add or customize sources.
When adding your own source, you must add the exact, case-sensitive
source name (for example, clang-tidy, not clang).
require("rulebook").setup = ({
-- if no diagnostic is found in the current line, search this many lines forward
forwSearchLines = 10,
ignoreComments = {
shellcheck = {
comment = "# shellcheck disable=%s",
location = "prevLine",
multiRuleIgnore = true,
multiRuleSeparator = ",",
},
-- ... (a full list of sources with builtin support can be found in the README)
yourCustomSource = { -- exact, case-sensitive source-name
---@type string|fun(vim.Diagnostic): string if string, "%s" will be replaced with the rule id
comment = "// disabling-comment %s",
---@type "prevLine"|"sameLine"|"encloseLine"|"inlineBeforeDiagnostic"|fun(vim.Diagnostic): string
location = "sameLine",
-- whether multiple rules can be ignored with one comment, defaults to `false`
multiRuleIgnore = true,
-- separator for multiple rule-ids, defaults to ", " (with space)
multiRuleSeparator = ",",
}
-- if location is `encloseLine`, the comment needs to be a list of two strings
anotherCustomSource = {
location = "encloseLine",
comment = {
"// disable-rule %s",
"// enable-rule %s",
},
}
},
ruleDocs = {
selene = "https://kampfkarren.github.io/selene/lints/%s.html"
-- ... (a full list of sources with builtin support can be found in the README)
-- Search URL when no documentation definition is available for a
-- diagnostic source. `%s` will be replaced with the diagnostic source and
-- the code/message.
fallback = "https://www.google.com/search?q=%s",
-- the key must be named exactly like `diagnostic.source` (case-sensitive!)
-- * string: `%s` will be replaced with the rule id
-- * function: will be called with the diagnostic object
-- * `false`: disable rule docs, will use the fallback
---@type string|false|fun(diag: vim.Diagnostic): string?
yourCustomSource = "https://my-docs/%s.hthml",
anotherCustomSource = function(diag)
-- ...
return url
end,
}
suppressFormatter = {
lua = {
-- used for normal mode
ignoreBlock = "-- stylua: ignore",
---@type "prevLine"|"sameLine"|"encloseLine"|fun(): string
location = "prevLine",
-- used for visual mode
ignoreRange = { "-- stylua: ignore start", "-- stylua: ignore end" },
},
}
prettifyError = {
---@type fun(vim.Diagnostic): string[]
typescript = function(diag)
-- ...
end,
}
})
nvim-rulebook uses
vim.ui.select, so the
appearance of the rule selection can be customized by using a UI plugin like
snacks.nvim.
Built-in sources can be customized by overwriting them in the configuration:
-- example: use `disable-line` instead of the default `disable-next-line` for eslint
require("rulebook").setup = {
ignoreComments = {
eslint = {
comment = "// eslint-disable-line %s",
location = "sameLine",
},
},
}
This plugin requires that the diagnostic providers (the LSP or a linter
integration tool like nvim-lint
or efm-langserver) provide the
source and code for the diagnostic. The source must be exactly the
same case-sensitive name in the diagnostic source and in the nvim-rulebook
config.
For nvim-lint, most linters should already be configured out of the box.
efm-langserverefm-langserver, you have to set lintSource for the source name, and
correctly configure the
errorformat. Other
than %l & %c (line number & column), this requires %n which
efm-langserver uses to fill in the diagnostic code.errorformat only matches numbers for %n, which means it
is not possible to parse diagnostic codes that consist of letters, such
as diagnostics from the Lua linter
selene. To use such linters with
nvim-rulebook you need to use nvim-lint which allows more flexible
parsing.[!TIP] You can also use efmls-configs-nvim to configure those linters for you.
-- example: configuring `efm-langserver` for `markdownlint` in `/lsp/efm.lua`
return {
init_options = { documentFormatting = true },
filetypes = { "markdown" },
root_markers = { ".markdownlint.yaml" },
settings = {
languages = {
markdown = {
{
lintSource = "markdownlint",
lintCommand = "markdownlint --stdin",
lintStdin = true, -- caveat: linting from stdin does not support `.markdownlintignore`
lintIgnoreExitCode = true,
lintFormats = { "%f:%l:%c MD%n/%m", "%f:%l MD%n/%m" },
rootMarkers = { ".markdownlint.yaml" },
},
},
},
},
}
To ask an LLM about a rule, use a URL that opens a chat it for
ruleDocs.fallback.
For ruleDocs.fallback, the %s placeholder will be replaced with the
diagnostic source and the quoted code (or message, if no code is available),
both URL-encoded.
-- example to use ChatGPT for rule lookup
require("rulebook").setup = ({
ruleDocs = {
fallback = "https://chatgpt.com/?q=Explain%20the%20following%20diagnostic%20error%3A%20%s"
-- To use `fallback` instead of the builtin rule docs, overwrite the
-- builtin one with `false`.
typescript = false,
}
})
The function require("rulebook").hasDocs(diag), expects a diagnostic object
and returns a boolean whether nvim-rulebook documentation for the respective
diagnostic available. One use case for this is to add a visual indicator if
there is a rule lookup available for a diagnostic (see
vim.diagnostic.config).
vim.diagnostic.config {
virtual_text = {
suffix = function(diag) return require("rulebook").hasDocs(diag) and "  " or "" end,
},
}
In my day job, I am a sociologist studying the social mechanisms underlying the digital economy. For my PhD project, I investigate the governance of the app economy and how software ecosystems manage the tension between innovation and compatibility. If you are interested in this subject, feel free to get in touch.