aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSteven Arcangeli <506791+stevearc@users.noreply.github.com>2024-07-19 08:46:46 -0700
committerGitHub <noreply@github.com>2024-07-19 08:46:46 -0700
commitd60d41c34c9eacf8131685f28532e72f12f21482 (patch)
tree76a0820ccbae7c112ca4dab39882f135b4b0c64a
parentae213f5169d5d0c6abbe76e1438d932772fc1657 (diff)
parent54ea60d1591486e7e56183addf1f45b03244386d (diff)
Merge pull request #491 from stevearc/stevearc-config-refactor
feat!: large rework of configuration logic
-rw-r--r--README.md60
-rw-r--r--doc/conform.txt113
-rw-r--r--doc/recipes.md32
-rw-r--r--lua/conform/formatters/dcm_format.lua2
-rw-r--r--lua/conform/formatters/injected.lua26
-rw-r--r--lua/conform/health.lua11
-rw-r--r--lua/conform/init.lua261
-rw-r--r--lua/conform/types.lua44
-rw-r--r--scripts/options_doc.lua12
-rw-r--r--tests/injected_spec.lua5
10 files changed, 402 insertions, 164 deletions
diff --git a/README.md b/README.md
index 82f5436..e7860de 100644
--- a/README.md
+++ b/README.md
@@ -18,16 +18,16 @@ Lightweight yet powerful formatter plugin for Neovim
- [setup(opts)](#setupopts)
- [format(opts, callback)](#formatopts-callback)
- [list_formatters(bufnr)](#list_formattersbufnr)
+ - [list_formatters_to_run(bufnr)](#list_formatters_to_runbufnr)
- [list_all_formatters()](#list_all_formatters)
- [get_formatter_info(formatter, bufnr)](#get_formatter_infoformatter-bufnr)
- - [will_fallback_lsp(options)](#will_fallback_lspoptions)
- [Acknowledgements](#acknowledgements)
<!-- /TOC -->
## Requirements
-- Neovim 0.8+
+- Neovim 0.9+ (for older versions, use a [nvim-0.x branch](https://github.com/stevearc/conform.nvim/branches))
## Features
@@ -129,8 +129,10 @@ require("conform").setup({
lua = { "stylua" },
-- Conform will run multiple formatters sequentially
python = { "isort", "black" },
- -- Use a sub-list to run only the first available formatter
- javascript = { { "prettierd", "prettier" } },
+ -- You can customize some of the format options for the filetype (:help conform.format)
+ rust = { "rustfmt", lsp_format = "fallback" },
+ -- Conform will run the first available formatter
+ javascript = { "prettierd", "prettier", stop_after_first = true },
},
})
```
@@ -214,7 +216,7 @@ You can view this list in vim with `:help conform-formatters`
- [darker](https://github.com/akaihola/darker) - Run black only on changed lines.
- [dart_format](https://dart.dev/tools/dart-format) - Replace the whitespace in your program with formatting that follows Dart guidelines.
- [dcm_fix](https://dcm.dev/docs/cli/formatting/fix/) - Fixes issues produced by dcm analyze, dcm check-unused-code or dcm check-dependencies commands.
-- [dcm_format](https://dcm.dev/docs/cli/formatting/format/) - Formats *.dart files.
+- [dcm_format](https://dcm.dev/docs/cli/formatting/format/) - Formats .dart files.
- [deno_fmt](https://deno.land/manual/tools/formatter) - Use [Deno](https://deno.land/) to format TypeScript, JavaScript/JSON and markdown.
- [dfmt](https://github.com/dlang-community/dfmt) - Formatter for D source code.
- [djlint](https://github.com/Riverside-Healthcare/djLint) - ✨ HTML Template Linter and Formatter. Django - Jinja - Nunjucks - Handlebars - GoLang.
@@ -431,6 +433,7 @@ require("conform").formatters.shfmt = {
- [Command to toggle format-on-save](doc/recipes.md#command-to-toggle-format-on-save)
- [Automatically run slow formatters async](doc/recipes.md#automatically-run-slow-formatters-async)
- [Lazy loading with lazy.nvim](doc/recipes.md#lazy-loading-with-lazynvim)
+- [Leave visual mode after range format](doc/recipes.md#leave-visual-mode-after-range-format)
<!-- /RECIPES -->
@@ -457,8 +460,8 @@ require("conform").setup({
lua = { "stylua" },
-- Conform will run multiple formatters sequentially
go = { "goimports", "gofmt" },
- -- Use a sub-list to run only the first available formatter
- javascript = { { "prettierd", "prettier" } },
+ -- You can also customize some of the format options for the filetype
+ rust = { "rustfmt", lsp_format = "fallback" },
-- You can use a function here to determine the formatters dynamically
python = function(bufnr)
if require("conform").get_formatter_info("ruff_format", bufnr).available then
@@ -473,6 +476,11 @@ require("conform").setup({
-- have other formatters configured.
["_"] = { "trim_whitespace" },
},
+ -- Set this to change the default values when calling conform.format()
+ -- This will also affect the default values for format_on_save/format_after_save
+ default_format_opts = {
+ lsp_format = "fallback",
+ },
-- If this is set, Conform will run the formatter on save.
-- It will pass the table to conform.format().
-- This can also be a function that returns the table.
@@ -491,6 +499,8 @@ require("conform").setup({
log_level = vim.log.levels.ERROR,
-- Conform will notify you when a formatter errors
notify_on_error = true,
+ -- Conform will notify you when no formatters are available for the buffer
+ notify_no_formatters = true,
-- Custom formatters and overrides for built-in formatters
formatters = {
my_formatter = {
@@ -527,7 +537,6 @@ require("conform").setup({
-- Set to false to disable merging the config with the base definition
inherit = true,
-- When inherit = true, add these additional arguments to the beginning of the command.
- -- When inherit = true, add these additional arguments to the command.
-- This can also be a function, like args
prepend_args = { "--use-tabs" },
-- When inherit = true, add these additional arguments to the end of the command.
@@ -576,9 +585,11 @@ require("conform").formatters.my_formatter = {
| opts | `nil\|conform.setupOpts` | | |
| | formatters_by_ft | `nil\|table<string, conform.FiletypeFormatter>` | Map of filetype to formatters |
| | format_on_save | `nil\|conform.FormatOpts\|fun(bufnr: integer): nil\|conform.FormatOpts` | If this is set, Conform will run the formatter on save. It will pass the table to conform.format(). This can also be a function that returns the table. |
+| | default_format_opts | `nil\|conform.DefaultFormatOpts` | The default options to use when calling conform.format() |
| | format_after_save | `nil\|conform.FormatOpts\|fun(bufnr: integer): nil\|conform.FormatOpts` | If this is set, Conform will run the formatter asynchronously after save. It will pass the table to conform.format(). This can also be a function that returns the table. |
| | log_level | `nil\|integer` | Set the log level (e.g. `vim.log.levels.DEBUG`). Use `:ConformInfo` to see the location of the log file. |
| | notify_on_error | `nil\|boolean` | Conform will notify you when a formatter errors (default true). |
+| | notify_no_formatters | `nil\|boolean` | Conform will notify you when no formatters are available for the buffer (default true). |
| | formatters | `nil\|table<string, conform.FormatterConfigOverride\|fun(bufnr: integer): nil\|conform.FormatterConfigOverride>` | Custom formatters and overrides for built-in formatters. |
### format(opts, callback)
@@ -601,8 +612,9 @@ Format a buffer
| | | > `"prefer"` | use only LSP formatting when available |
| | | > `"first"` | LSP formatting is used when available and then other formatters |
| | | > `"last"` | other formatters are used then LSP formatting when available |
+| | stop_after_first | `nil\|boolean` | Only run the first available formatter in the list. Defaults to false. |
| | quiet | `nil\|boolean` | Don't show any notifications for warnings or failures. Defaults to false. |
-| | range | `nil\|table` | Range to format. Table must contain `start` and `end` keys with {row, col} tuples using (1,0) indexing. Defaults to current selection in visual mode |
+| | range | `nil\|conform.Range` | Range to format. Table must contain `start` and `end` keys with {row, col} tuples using (1,0) indexing. Defaults to current selection in visual mode |
| | id | `nil\|integer` | Passed to vim.lsp.buf.format when using LSP formatting |
| | name | `nil\|string` | Passed to vim.lsp.buf.format when using LSP formatting |
| | filter | `nil\|fun(client: table): boolean` | Passed to vim.lsp.buf.format when using LSP formatting |
@@ -623,6 +635,27 @@ Retrieve the available formatters for a buffer
| ----- | -------------- | ---- |
| bufnr | `nil\|integer` | |
+### list_formatters_to_run(bufnr)
+
+`list_formatters_to_run(bufnr): conform.FormatterInfo[], boolean` \
+Get the exact formatters that will be run for a buffer.
+
+| Param | Type | Desc |
+| ----- | -------------- | ---- |
+| bufnr | `nil\|integer` | |
+
+Returns:
+
+| Type | Desc |
+| ----------------------- | -------------------------- |
+| conform.FormatterInfo[] | |
+| boolean | lsp Will use LSP formatter |
+
+**Note:**
+<pre>
+This accounts for stop_after_first, lsp fallback logic, etc.
+</pre>
+
### list_all_formatters()
`list_all_formatters(): conform.FormatterInfo[]` \
@@ -638,15 +671,6 @@ Get information about a formatter (including availability)
| --------- | -------------- | ------------------------- |
| formatter | `string` | The name of the formatter |
| bufnr | `nil\|integer` | |
-
-### will_fallback_lsp(options)
-
-`will_fallback_lsp(options): boolean` \
-Check if the buffer will use LSP formatting when lsp_format = "fallback"
-
-| Param | Type | Desc |
-| ------- | ------------ | ------------------------------------ |
-| options | `nil\|table` | Options passed to vim.lsp.buf.format |
<!-- /API -->
## Acknowledgements
diff --git a/doc/conform.txt b/doc/conform.txt
index 3f6568f..5af52f8 100644
--- a/doc/conform.txt
+++ b/doc/conform.txt
@@ -17,8 +17,8 @@ OPTIONS *conform-option
lua = { "stylua" },
-- Conform will run multiple formatters sequentially
go = { "goimports", "gofmt" },
- -- Use a sub-list to run only the first available formatter
- javascript = { { "prettierd", "prettier" } },
+ -- You can also customize some of the format options for the filetype
+ rust = { "rustfmt", lsp_format = "fallback" },
-- You can use a function here to determine the formatters dynamically
python = function(bufnr)
if require("conform").get_formatter_info("ruff_format", bufnr).available then
@@ -33,6 +33,11 @@ OPTIONS *conform-option
-- have other formatters configured.
["_"] = { "trim_whitespace" },
},
+ -- Set this to change the default values when calling conform.format()
+ -- This will also affect the default values for format_on_save/format_after_save
+ default_format_opts = {
+ lsp_format = "fallback",
+ },
-- If this is set, Conform will run the formatter on save.
-- It will pass the table to conform.format().
-- This can also be a function that returns the table.
@@ -51,6 +56,8 @@ OPTIONS *conform-option
log_level = vim.log.levels.ERROR,
-- Conform will notify you when a formatter errors
notify_on_error = true,
+ -- Conform will notify you when no formatters are available for the buffer
+ notify_no_formatters = true,
-- Custom formatters and overrides for built-in formatters
formatters = {
my_formatter = {
@@ -87,7 +94,6 @@ OPTIONS *conform-option
-- Set to false to disable merging the config with the base definition
inherit = true,
-- When inherit = true, add these additional arguments to the beginning of the command.
- -- When inherit = true, add these additional arguments to the command.
-- This can also be a function, like args
prepend_args = { "--use-tabs" },
-- When inherit = true, add these additional arguments to the end of the command.
@@ -123,6 +129,26 @@ setup({opts}) *conform.setu
If this is set, Conform will run the formatter on
save. It will pass the table to conform.format().
This can also be a function that returns the table.
+ {default_format_opts} `nil|conform.DefaultFormatOpts` The default
+ options to use when calling conform.format()
+ {timeout_ms} `nil|integer` Time in milliseconds to block for
+ formatting. Defaults to 1000. No effect if
+ async = true.
+ {lsp_format} `nil|conform.LspFormatOpts` Configure if and
+ when LSP should be used for formatting.
+ Defaults to "never".
+ `"never"` never use the LSP for formatting (default)
+ `"fallback"` LSP formatting is used when no other formatters
+ are available
+ `"prefer"` use only LSP formatting when available
+ `"first"` LSP formatting is used when available and then
+ other formatters
+ `"last"` other formatters are used then LSP formatting
+ when available
+ {quiet} `nil|boolean` Don't show any notifications for
+ warnings or failures. Defaults to false.
+ {stop_after_first} `nil|boolean` Only run the first available
+ formatter in the list. Defaults to false.
{format_after_save} `nil|conform.FormatOpts|fun(bufnr: integer): nil|conform.FormatOpts`
If this is set, Conform will run the formatter
asynchronously after save. It will pass the table
@@ -133,6 +159,9 @@ setup({opts}) *conform.setu
the location of the log file.
{notify_on_error} `nil|boolean` Conform will notify you when a
formatter errors (default true).
+ {notify_no_formatters} `nil|boolean` Conform will notify you when no
+ formatters are available for the buffer (default
+ true).
{formatters} `nil|table<string, conform.FormatterConfigOverride|fun(bufnr: integer): nil|conform.FormatterConfigOverride>`
Custom formatters and overrides for built-in
formatters.
@@ -142,20 +171,23 @@ format({opts}, {callback}): boolean *conform.forma
Parameters:
{opts} `nil|conform.FormatOpts`
- {timeout_ms} `nil|integer` Time in milliseconds to block for
- formatting. Defaults to 1000. No effect if async = true.
- {bufnr} `nil|integer` Format this buffer (default 0)
- {async} `nil|boolean` If true the method won't block. Defaults to
- false. If the buffer is modified before the formatter
- completes, the formatting will be discarded.
- {dry_run} `nil|boolean` If true don't apply formatting changes to
- the buffer
- {undojoin} `nil|boolean` Use undojoin to merge formatting changes
- with previous edit (default false)
- {formatters} `nil|string[]` List of formatters to run. Defaults to all
- formatters for the buffer filetype.
- {lsp_format} `nil|conform.LspFormatOpts` Configure if and when LSP
- should be used for formatting. Defaults to "never".
+ {timeout_ms} `nil|integer` Time in milliseconds to block for
+ formatting. Defaults to 1000. No effect if async =
+ true.
+ {bufnr} `nil|integer` Format this buffer (default 0)
+ {async} `nil|boolean` If true the method won't block.
+ Defaults to false. If the buffer is modified before
+ the formatter completes, the formatting will be
+ discarded.
+ {dry_run} `nil|boolean` If true don't apply formatting
+ changes to the buffer
+ {undojoin} `nil|boolean` Use undojoin to merge formatting
+ changes with previous edit (default false)
+ {formatters} `nil|string[]` List of formatters to run. Defaults
+ to all formatters for the buffer filetype.
+ {lsp_format} `nil|conform.LspFormatOpts` Configure if and when
+ LSP should be used for formatting. Defaults to
+ "never".
`"never"` never use the LSP for formatting (default)
`"fallback"` LSP formatting is used when no other formatters are
available
@@ -164,17 +196,22 @@ format({opts}, {callback}): boolean *conform.forma
formatters
`"last"` other formatters are used then LSP formatting when
available
- {quiet} `nil|boolean` Don't show any notifications for warnings
- or failures. Defaults to false.
- {range} `nil|table` Range to format. Table must contain `start`
- and `end` keys with {row, col} tuples using (1,0)
- indexing. Defaults to current selection in visual mode
- {id} `nil|integer` Passed to |vim.lsp.buf.format| when using
- LSP formatting
- {name} `nil|string` Passed to |vim.lsp.buf.format| when using
- LSP formatting
- {filter} `nil|fun(client: table): boolean` Passed to
- |vim.lsp.buf.format| when using LSP formatting
+ {stop_after_first} `nil|boolean` Only run the first available
+ formatter in the list. Defaults to false.
+ {quiet} `nil|boolean` Don't show any notifications for
+ warnings or failures. Defaults to false.
+ {range} `nil|conform.Range` Range to format. Table must
+ contain `start` and `end` keys with {row, col}
+ tuples using (1,0) indexing. Defaults to current
+ selection in visual mode
+ {start} `integer[]`
+ {end} `integer[]`
+ {id} `nil|integer` Passed to |vim.lsp.buf.format| when
+ using LSP formatting
+ {name} `nil|string` Passed to |vim.lsp.buf.format| when
+ using LSP formatting
+ {filter} `nil|fun(client: table): boolean` Passed to |vim.ls
+ p.buf.format| when using LSP formatting
{callback} `nil|fun(err: nil|string, did_edit: nil|boolean)` Called once
formatting has completed
Returns:
@@ -186,6 +223,18 @@ list_formatters({bufnr}): conform.FormatterInfo[] *conform.list_formatter
Parameters:
{bufnr} `nil|integer`
+list_formatters_to_run({bufnr}): conform.FormatterInfo[], boolean *conform.list_formatters_to_run*
+ Get the exact formatters that will be run for a buffer.
+
+ Parameters:
+ {bufnr} `nil|integer`
+ Returns:
+ `conform.FormatterInfo[]`
+ `boolean` lsp Will use LSP formatter
+
+ Note:
+ This accounts for stop_after_first, lsp fallback logic, etc.
+
list_all_formatters(): conform.FormatterInfo[] *conform.list_all_formatters*
List information about all filetype-configured formatters
@@ -197,12 +246,6 @@ get_formatter_info({formatter}, {bufnr}): conform.FormatterInfo *conform.get_for
{formatter} `string` The name of the formatter
{bufnr} `nil|integer`
-will_fallback_lsp({options}): boolean *conform.will_fallback_lsp*
- Check if the buffer will use LSP formatting when lsp_format = "fallback"
-
- Parameters:
- {options} `nil|table` Options passed to |vim.lsp.buf.format|
-
--------------------------------------------------------------------------------
FORMATTERS *conform-formatters*
@@ -258,7 +301,7 @@ FORMATTERS *conform-formatter
follows Dart guidelines.
`dcm_fix` - Fixes issues produced by dcm analyze, dcm check-unused-code or dcm
check-dependencies commands.
-`dcm_format` - Formats *.dart files.
+`dcm_format` - Formats .dart files.
`deno_fmt` - Use [Deno](https://deno.land/) to format TypeScript,
JavaScript/JSON and markdown.
`dfmt` - Formatter for D source code.
diff --git a/doc/recipes.md b/doc/recipes.md
index f5d6f99..0c085f2 100644
--- a/doc/recipes.md
+++ b/doc/recipes.md
@@ -7,6 +7,7 @@
- [Command to toggle format-on-save](#command-to-toggle-format-on-save)
- [Automatically run slow formatters async](#automatically-run-slow-formatters-async)
- [Lazy loading with lazy.nvim](#lazy-loading-with-lazynvim)
+- [Leave visual mode after range format](#leave-visual-mode-after-range-format)
<!-- /TOC -->
@@ -149,22 +150,28 @@ return {
-- Customize or remove this keymap to your liking
"<leader>f",
function()
- require("conform").format({ async = true, lsp_format = "fallback" })
+ require("conform").format({ async = true })
end,
mode = "",
desc = "Format buffer",
},
},
- -- Everything in opts will be passed to setup()
+ -- This will provide type hinting with LuaLS
+ ---@module "conform"
+ ---@type conform.setupOpts
opts = {
-- Define your formatters
formatters_by_ft = {
lua = { "stylua" },
python = { "isort", "black" },
- javascript = { { "prettierd", "prettier" } },
+ javascript = { "prettierd", "prettier", stop_after_first = true },
+ },
+ -- Set default options
+ default_format_opts = {
+ lsp_format = "fallback",
},
-- Set up format-on-save
- format_on_save = { timeout_ms = 500, lsp_format = "fallback" },
+ format_on_save = { timeout_ms = 500 },
-- Customize formatters
formatters = {
shfmt = {
@@ -178,3 +185,20 @@ return {
end,
}
```
+
+## Leave visual mode after range format
+
+If you call `conform.format` when in visual mode, conform will perform a range format on the selected region. If you want it to leave visual mode afterwards (similar to the default `gw` or `gq` behavior), use this mapping:
+
+```lua
+vim.keymap.set("", "<leader>f", function()
+ require("conform").format({ async = true }, function(err)
+ if not err then
+ local mode = vim.api.nvim_get_mode().mode
+ if vim.startswith(string.lower(mode), "v") then
+ vim.api.nvim_feedkeys(vim.api.nvim_replace_termcodes("<Esc>", true, false, true), "n", true)
+ end
+ end
+ end)
+end, { desc = "Format code" })
+```
diff --git a/lua/conform/formatters/dcm_format.lua b/lua/conform/formatters/dcm_format.lua
index f2af0ac..04adf67 100644
--- a/lua/conform/formatters/dcm_format.lua
+++ b/lua/conform/formatters/dcm_format.lua
@@ -2,7 +2,7 @@
return {
meta = {
url = "https://dcm.dev/docs/cli/formatting/format/",
- description = "Formats *.dart files.",
+ description = "Formats .dart files.",
},
command = "dcm",
args = { "format", "$FILENAME" },
diff --git a/lua/conform/formatters/injected.lua b/lua/conform/formatters/injected.lua
index 4dbf1eb..bd3313b 100644
--- a/lua/conform/formatters/injected.lua
+++ b/lua/conform/formatters/injected.lua
@@ -136,12 +136,6 @@ return {
-- (defaults to the value from formatters_by_ft)
lang_to_formatters = {},
},
- condition = function(self, ctx)
- local ok, parser = pcall(vim.treesitter.get_parser, ctx.buf)
- -- Require Neovim 0.9 because the treesitter API has changed significantly
- ---@diagnostic disable-next-line: invisible
- return ok and parser._injection_query and vim.fn.has("nvim-0.9") == 1
- end,
format = function(self, ctx, lines, callback)
local conform = require("conform")
local errors = require("conform.errors")
@@ -284,13 +278,21 @@ return {
---@type string[]
local formatter_names
if type(ft_formatters) == "function" then
- formatter_names = ft_formatters(ctx.buf)
- else
- local formatters = require("conform").resolve_formatters(ft_formatters, ctx.buf, false)
- formatter_names = vim.tbl_map(function(f)
- return f.name
- end, formatters)
+ ft_formatters = ft_formatters(ctx.buf)
+ end
+ local stop_after_first = ft_formatters.stop_after_first
+ if stop_after_first == nil then
+ stop_after_first = conform.default_format_opts.stop_after_first
end
+ if stop_after_first == nil then
+ stop_after_first = false
+ end
+
+ local formatters =
+ conform.resolve_formatters(ft_formatters, ctx.buf, false, stop_after_first)
+ formatter_names = vim.tbl_map(function(f)
+ return f.name
+ end, formatters)
local idx = num_format
log.debug("Injected format %s:%d:%d: %s", lang, start_lnum, end_lnum, formatter_names)
log.trace("Injected format lines %s", input_lines)
diff --git a/lua/conform/health.lua b/lua/conform/health.lua
index 3ee7567..07ffa22 100644
--- a/lua/conform/health.lua
+++ b/lua/conform/health.lua
@@ -6,7 +6,6 @@ local health_start = vim.health.start or vim.health.report_start
local health_warn = vim.health.warn or vim.health.report_warn
local health_info = vim.health.info or vim.health.report_info
local health_ok = vim.health.ok or vim.health.report_ok
-local islist = vim.islist or vim.tbl_islist
---@param name string
---@return string[]
@@ -16,14 +15,6 @@ local function get_formatter_filetypes(name)
for filetype, formatters in pairs(conform.formatters_by_ft) do
if type(formatters) == "function" then
formatters = formatters(0)
- -- support the old structure where formatters could be a subkey
- elseif not islist(formatters) then
- vim.notify_once(
- "Using deprecated structure for formatters_by_ft. See :help conform-options for details.",
- vim.log.levels.ERROR
- )
- ---@diagnostic disable-next-line: undefined-field
- formatters = formatters.formatters
end
for _, ft_name in ipairs(formatters) do
@@ -61,7 +52,7 @@ M.check = function()
end
end
----@param formatters conform.FormatterUnit[]
+---@param formatters conform.FiletypeFormatterInternal
---@return string[]
local function flatten_formatters(formatters)
local flat = {}
diff --git a/lua/conform/init.lua b/lua/conform/init.lua
index f0b2dfd..c174dea 100644
--- a/lua/conform/init.lua
+++ b/lua/conform/init.lua
@@ -1,5 +1,3 @@
----@diagnostic disable-next-line: deprecated
-local islist = vim.islist or vim.tbl_islist
local M = {}
---@type table<string, conform.FiletypeFormatter>
@@ -9,20 +7,72 @@ M.formatters_by_ft = {}
M.formatters = {}
M.notify_on_error = true
+M.notify_no_formatters = true
+
+---@type conform.DefaultFormatOpts
+M.default_format_opts = {}
+
+-- Defer notifications because nvim-notify can throw errors if called immediately
+-- in some contexts (e.g. inside statusline function)
+local notify = vim.schedule_wrap(function(...)
+ vim.notify(...)
+end)
+local notify_once = vim.schedule_wrap(function(...)
+ vim.notify_once(...)
+end)
+
+local allowed_default_opts = { "timeout_ms", "lsp_format", "quiet", "stop_after_first" }
+local function merge_default_opts(a, b)
+ for _, key in ipairs(allowed_default_opts) do
+ if a[key] == nil then
+ a[key] = b[key]
+ end
+ end
+ return a
+end
+
+---@param conf? conform.FiletypeFormatter
+local function check_for_default_opts(conf)
+ if not conf or type(conf) ~= "table" then
+ return
+ end
+ for k in pairs(conf) do
+ if type(k) == "string" then
+ notify(
+ string.format(
+ 'conform.setup: the "_" and "*" keys in formatters_by_ft do not support configuring format options, such as "%s"',
+ k
+ ),
+ vim.log.levels.WARN
+ )
+ break
+ end
+ end
+end
---@param opts? conform.setupOpts
M.setup = function(opts)
+ if vim.fn.has("nvim-0.9") == 0 then
+ notify("conform.nvim requires Neovim 0.9+", vim.log.levels.ERROR)
+ return
+ end
opts = opts or {}
M.formatters = vim.tbl_extend("force", M.formatters, opts.formatters or {})
M.formatters_by_ft = vim.tbl_extend("force", M.formatters_by_ft, opts.formatters_by_ft or {})
+ check_for_default_opts(M.formatters_by_ft["_"])
+ check_for_default_opts(M.formatters_by_ft["*"])
+ M.default_format_opts =
+ vim.tbl_extend("force", M.default_format_opts, opts.default_format_opts or {})
if opts.log_level then
require("conform.log").level = opts.log_level
end
- local notify_on_error = opts.notify_on_error
- if notify_on_error ~= nil then
- M.notify_on_error = notify_on_error
+ if opts.notify_on_error ~= nil then
+ M.notify_on_error = opts.notify_on_error
+ end
+ if opts.notify_no_formatters ~= nil then
+ M.notify_no_formatters = opts.notify_no_formatters
end
local aug = vim.api.nvim_create_augroup("Conform", { clear = true })
@@ -44,7 +94,7 @@ M.setup = function(opts)
end
if format_args then
if format_args.async then
- vim.notify_once(
+ notify_once(
"Conform format_on_save cannot use async=true. Use format_after_save instead.",
vim.log.levels.ERROR
)
@@ -97,7 +147,7 @@ M.setup = function(opts)
exit_timeout = format_args.timeout_ms or exit_timeout
num_running_format_jobs = num_running_format_jobs + 1
if format_args.async == false then
- vim.notify_once(
+ notify_once(
"Conform format_after_save cannot use async=false. Use format_on_save instead.",
vim.log.levels.ERROR
)
@@ -174,7 +224,7 @@ end
---Get the configured formatter filetype for a buffer
---@param bufnr? integer
----@return nil|string filetype or nil if no formatter is configured
+---@return nil|string filetype or nil if no formatter is configured. Can be "_".
local function get_matching_filetype(bufnr)
if not bufnr or bufnr == 0 then
bufnr = vim.api.nvim_get_current_buf()
@@ -193,7 +243,7 @@ end
---@private
---@param bufnr? integer
----@return conform.FormatterUnit[]
+---@return string[]
M.list_formatters_for_buffer = function(bufnr)
if not bufnr or bufnr == 0 then
bufnr = vim.api.nvim_get_current_buf()
@@ -204,6 +254,10 @@ M.list_formatters_for_buffer = function(bufnr)
local function dedupe_formatters(names, collect)
for _, name in ipairs(names) do
if type(name) == "table" then
+ notify_once(
+ "deprecated[conform]: The nested {} syntax to run the first formatter has been replaced by the stop_after_first option (see :help conform.format).\nSupport for the old syntax will be dropped on 2025-01-01.",
+ vim.log.levels.WARN
+ )
local alternation = {}
dedupe_formatters(name, alternation)
if not vim.tbl_isempty(alternation) then
@@ -228,16 +282,6 @@ M.list_formatters_for_buffer = function(bufnr)
if type(ft_formatters) == "function" then
dedupe_formatters(ft_formatters(bufnr), formatters)
else
- -- support the old structure where formatters could be a subkey
- if not islist(ft_formatters) then
- vim.notify_once(
- "Using deprecated structure for formatters_by_ft. See :help conform-options for details.",
- vim.log.levels.ERROR
- )
- ---@diagnostic disable-next-line: undefined-field
- ft_formatters = ft_formatters.formatters
- end
-
dedupe_formatters(ft_formatters, formatters)
end
end
@@ -246,6 +290,25 @@ M.list_formatters_for_buffer = function(bufnr)
return formatters
end
+---@param bufnr? integer
+---@return nil|conform.DefaultFormatOpts
+local function get_opts_from_filetype(bufnr)
+ if not bufnr or bufnr == 0 then
+ bufnr = vim.api.nvim_get_current_buf()
+ end
+ local matching_filetype = get_matching_filetype(bufnr)
+ if not matching_filetype then
+ return nil
+ end
+
+ local ft_formatters = M.formatters_by_ft[matching_filetype]
+ assert(ft_formatters ~= nil, "get_matching_filetype ensures formatters_by_ft has key")
+ if type(ft_formatters) == "function" then
+ ft_formatters = ft_formatters(bufnr)
+ end
+ return merge_default_opts({}, ft_formatters)
+end
+
---@param bufnr integer
---@param mode "v"|"V"
---@return table {start={row,col}, end={row,col}} using (1, 0) indexing
@@ -278,17 +341,18 @@ local function range_from_selection(bufnr, mode)
end
---@private
----@param names conform.FormatterUnit[]
+---@param names conform.FiletypeFormatterInternal
---@param bufnr integer
---@param warn_on_missing boolean
+---@param stop_after_first boolean
---@return conform.FormatterInfo[]
-M.resolve_formatters = function(names, bufnr, warn_on_missing)
+M.resolve_formatters = function(names, bufnr, warn_on_missing, stop_after_first)
local all_info = {}
local function add_info(info, warn)
if info.available then
table.insert(all_info, info)
elseif warn then
- vim.notify(
+ notify(
string.format("Formatter '%s' unavailable: %s", info.name, info.available_msg),
vim.log.levels.WARN
)
@@ -301,6 +365,10 @@ M.resolve_formatters = function(names, bufnr, warn_on_missing)
local info = M.get_formatter_info(name, bufnr)
add_info(info, warn_on_missing)
else
+ notify_once(
+ "deprecated[conform]: The nested {} syntax to run the first formatter has been replaced by the stop_after_first option (see :help conform.format).\nSupport for the old syntax will be dropped on 2025-01-01.",
+ vim.log.levels.WARN
+ )
-- If this is an alternation, take the first one that's available
for i, v in ipairs(name) do
local info = M.get_formatter_info(v, bufnr)
@@ -309,6 +377,10 @@ M.resolve_formatters = function(names, bufnr, warn_on_missing)
end
end
end
+
+ if stop_after_first and #all_info > 0 then
+ break
+ end
end
return all_info
end
@@ -328,34 +400,22 @@ local function has_lsp_formatter(opts)
return not vim.tbl_isempty(lsp_format.get_format_clients(opts))
end
----@alias conform.LspFormatOpts
----| '"never"' # never use the LSP for formatting (default)
----| '"fallback"' # LSP formatting is used when no other formatters are available
----| '"prefer"' # use only LSP formatting when available
----| '"first"' # LSP formatting is used when available and then other formatters
----| '"last"' # other formatters are used then LSP formatting when available
-
----@class conform.FormatOpts
----@field timeout_ms nil|integer Time in milliseconds to block for formatting. Defaults to 1000. No effect if async = true.
----@field bufnr nil|integer Format this buffer (default 0)
----@field async nil|boolean If true the method won't block. Defaults to false. If the buffer is modified before the formatter completes, the formatting will be discarded.
----@field dry_run nil|boolean If true don't apply formatting changes to the buffer
----@field undojoin nil|boolean Use undojoin to merge formatting changes with previous edit (default false)
----@field formatters nil|string[] List of formatters to run. Defaults to all formatters for the buffer filetype.
----@field lsp_format? conform.LspFormatOpts Configure if and when LSP should be used for formatting. Defaults to "never".
----@field quiet nil|boolean Don't show any notifications for warnings or failures. Defaults to false.
----@field range nil|table Range to format. Table must contain `start` and `end` keys with {row, col} tuples using (1,0) indexing. Defaults to current selection in visual mode
----@field id nil|integer Passed to |vim.lsp.buf.format| when using LSP formatting
----@field name nil|string Passed to |vim.lsp.buf.format| when using LSP formatting
----@field filter nil|fun(client: table): boolean Passed to |vim.lsp.buf.format| when using LSP formatting
+local has_notified_ft_no_formatters = {}
---Format a buffer
---@param opts? conform.FormatOpts
---@param callback? fun(err: nil|string, did_edit: nil|boolean) Called once formatting has completed
---@return boolean True if any formatters were attempted
M.format = function(opts, callback)
- ---@type {timeout_ms: integer, bufnr: integer, async: boolean, dry_run: boolean, lsp_format: "never"|"first"|"last"|"prefer"|"fallback", quiet: boolean, formatters?: string[], range?: conform.Range, undojoin: boolean}
- opts = vim.tbl_extend("keep", opts or {}, {
+ opts = opts or {}
+ local has_explicit_formatters = opts.formatters ~= nil
+ -- If formatters were not passed in directly, fetch any options from formatters_by_ft
+ if not has_explicit_formatters then
+ merge_default_opts(opts, get_opts_from_filetype(opts.bufnr) or {})
+ end
+ merge_default_opts(opts, M.default_format_opts)
+ ---@type {timeout_ms: integer, bufnr: integer, async: boolean, dry_run: boolean, lsp_format: "never"|"first"|"last"|"prefer"|"fallback", quiet: boolean, stop_after_first: boolean, formatters?: string[], range?: conform.Range, undojoin: boolean}
+ opts = vim.tbl_extend("keep", opts, {
timeout_ms = 1000,
bufnr = 0,
async = false,
@@ -363,7 +423,11 @@ M.format = function(opts, callback)
lsp_format = "never",
quiet = false,
undojoin = false,
+ stop_after_first = false,
})
+ if opts.bufnr == 0 then
+ opts.bufnr = vim.api.nvim_get_current_buf()
+ end
-- For backwards compatibility
---@diagnostic disable-next-line: undefined-field
@@ -374,9 +438,6 @@ M.format = function(opts, callback)
opts.lsp_format = "last"
end
- if opts.bufnr == 0 then
- opts.bufnr = vim.api.nvim_get_current_buf()
- end
local mode = vim.api.nvim_get_mode().mode
if not opts.range and mode == "v" or mode == "V" then
opts.range = range_from_selection(opts.bufnr, mode)
@@ -387,18 +448,23 @@ M.format = function(opts, callback)
local lsp_format = require("conform.lsp_format")
local runner = require("conform.runner")
- local explicit_formatters = opts.formatters ~= nil
local formatter_names = opts.formatters or M.list_formatters_for_buffer(opts.bufnr)
- local formatters =
- M.resolve_formatters(formatter_names, opts.bufnr, not opts.quiet and explicit_formatters)
+ local formatters = M.resolve_formatters(
+ formatter_names,
+ opts.bufnr,
+ not opts.quiet and has_explicit_formatters,
+ opts.stop_after_first
+ )
local has_lsp = has_lsp_formatter(opts)
+ ---Handle errors and maybe run LSP formatting after cli formatters complete
---@param err? conform.Error
---@param did_edit? boolean
local function handle_result(err, did_edit)
if err then
local level = errors.level_for_code(err.code)
log.log(level, err.message)
+ ---@type boolean?
local should_notify = not opts.quiet and level >= vim.log.levels.WARN
-- Execution errors have special handling. Maybe should reconsider this.
local notify_msg = err.message
@@ -407,7 +473,7 @@ M.format = function(opts, callback)
notify_msg = "Formatter failed. See :ConformInfo for details"
end
if should_notify then
- vim.notify(notify_msg, level)
+ notify(notify_msg, level)
end
end
local err_message = err and err.message
@@ -427,6 +493,8 @@ M.format = function(opts, callback)
callback(nil, did_edit)
end
end
+
+ ---Run the resolved formatters on the buffer
local function run_cli_formatters(cb)
local resolved_names = vim.tbl_map(function(f)
return f.name
@@ -471,19 +539,25 @@ M.format = function(opts, callback)
run_cli_formatters(handle_result)
return true
else
- local level = explicit_formatters and "warn" or "debug"
- log[level]("No formatters found for %s", vim.api.nvim_buf_get_name(opts.bufnr))
- callback("No formatters found for buffer")
+ local level = has_explicit_formatters and "warn" or "debug"
+ log[level]("Formatters unavailable for %s", vim.api.nvim_buf_get_name(opts.bufnr))
+
+ local ft = vim.bo[opts.bufnr].filetype
+ if
+ not vim.tbl_isempty(formatter_names)
+ and not has_notified_ft_no_formatters[ft]
+ and not opts.quiet
+ and M.notify_no_formatters
+ then
+ notify(string.format("Formatters unavailable for %s file", ft), vim.log.levels.WARN)
+ has_notified_ft_no_formatters[ft] = true
+ end
+
+ callback("No formatters available for buffer")
return false
end
end
----@class conform.FormatLinesOpts
----@field timeout_ms nil|integer Time in milliseconds to block for formatting. Defaults to 1000. No effect if async = true.
----@field bufnr nil|integer use this as the working buffer (default 0)
----@field async nil|boolean If true the method won't block. Defaults to false. If the buffer is modified before the formatter completes, the formatting will be discarded.
----@field quiet nil|boolean Don't show any notifications for warnings or failures. Defaults to false.
-
---Process lines with formatters
---@private
---@param formatter_names string[]
@@ -493,18 +567,20 @@ end
---@return nil|conform.Error error Only present if async = false
---@return nil|string[] new_lines Only present if async = false
M.format_lines = function(formatter_names, lines, opts, callback)
- ---@type {timeout_ms: integer, bufnr: integer, async: boolean, quiet: boolean}
+ ---@type {timeout_ms: integer, bufnr: integer, async: boolean, quiet: boolean, stop_after_first: boolean}
opts = vim.tbl_extend("keep", opts or {}, {
timeout_ms = 1000,
bufnr = 0,
async = false,
quiet = false,
+ stop_after_first = false,
})
callback = callback or function(_err, _lines) end
local errors = require("conform.errors")
local log = require("conform.log")
local runner = require("conform.runner")
- local formatters = M.resolve_formatters(formatter_names, opts.bufnr, not opts.quiet)
+ local formatters =
+ M.resolve_formatters(formatter_names, opts.bufnr, not opts.quiet, opts.stop_after_first)
if vim.tbl_isempty(formatters) then
callback(nil, lines)
return
@@ -540,7 +616,44 @@ M.list_formatters = function(bufnr)
bufnr = vim.api.nvim_get_current_buf()
end
local formatters = M.list_formatters_for_buffer(bufnr)
- return M.resolve_formatters(formatters, bufnr, false)
+ return M.resolve_formatters(formatters, bufnr, false, false)
+end
+
+---Get the exact formatters that will be run for a buffer.
+---@param bufnr? integer
+---@return conform.FormatterInfo[]
+---@return boolean lsp Will use LSP formatter
+---@note
+--- This accounts for stop_after_first, lsp fallback logic, etc.
+M.list_formatters_to_run = function(bufnr)
+ if not bufnr or bufnr == 0 then
+ bufnr = vim.api.nvim_get_current_buf()
+ end
+ ---@type {bufnr: integer, lsp_format: conform.LspFormatOpts, stop_after_first: boolean}
+ local opts = vim.tbl_extend(
+ "keep",
+ get_opts_from_filetype(bufnr) or {},
+ M.default_format_opts,
+ { stop_after_first = false, lsp_format = "never", bufnr = bufnr }
+ )
+ local formatter_names = M.list_formatters_for_buffer(bufnr)
+ local formatters = M.resolve_formatters(formatter_names, bufnr, false, opts.stop_after_first)
+
+ local has_lsp = has_lsp_formatter(opts)
+ local any_formatters = has_filetype_formatters(opts.bufnr) and not vim.tbl_isempty(formatters)
+
+ if
+ has_lsp
+ and (opts.lsp_format == "prefer" or (opts.lsp_format ~= "never" and not any_formatters))
+ then
+ return {}, true
+ elseif has_lsp and opts.lsp_format == "first" then
+ return formatters, true
+ elseif not vim.tbl_isempty(formatters) then
+ return formatters, opts.lsp_format == "last" and has_lsp
+ else
+ return {}, false
+ end
end
---List information about all filetype-configured formatters
@@ -551,18 +664,13 @@ M.list_all_formatters = function()
if type(ft_formatters) == "function" then
ft_formatters = ft_formatters(0)
end
- -- support the old structure where formatters could be a subkey
- if not islist(ft_formatters) then
- vim.notify_once(
- "Using deprecated structure for formatters_by_ft. See :help conform-options for details.",
- vim.log.levels.ERROR
- )
- ---@diagnostic disable-next-line: undefined-field
- ft_formatters = ft_formatters.formatters
- end
for _, formatter in ipairs(ft_formatters) do
if type(formatter) == "table" then
+ notify_once(
+ "deprecated[conform]: The nested {} syntax to run the first formatter has been replaced by the stop_after_first option (see :help conform.format).\nSupport for the old syntax will be dropped on 2025-01-01.",
+ vim.log.levels.WARN
+ )
for _, v in ipairs(formatter) do
formatters[v] = true
end
@@ -601,7 +709,7 @@ M.get_formatter_config = function(formatter, bufnr)
if override and override.command and override.format then
local msg =
string.format("Formatter '%s' cannot define both 'command' and 'format' function", formatter)
- vim.notify_once(msg, vim.log.levels.ERROR)
+ notify_once(msg, vim.log.levels.ERROR)
return nil
end
@@ -623,7 +731,7 @@ M.get_formatter_config = function(formatter, bufnr)
"Formatter '%s' missing built-in definition\nSet `command` to get rid of this error.",
formatter
)
- vim.notify_once(msg, vim.log.levels.ERROR)
+ notify_once(msg, vim.log.levels.ERROR)
return nil
end
else
@@ -707,9 +815,14 @@ M.get_formatter_info = function(formatter, bufnr)
end
---Check if the buffer will use LSP formatting when lsp_format = "fallback"
+---@deprecated
---@param options? table Options passed to |vim.lsp.buf.format|
---@return boolean
M.will_fallback_lsp = function(options)
+ notify_once(
+ "deprecated[conform]: will_fallback_lsp is deprecated. Use list_formatters_to_run instead.\nThis method will be removed on 2025-01-01.",
+ vim.log.levels.WARN
+ )
options = vim.tbl_deep_extend("keep", options or {}, {
bufnr = vim.api.nvim_get_current_buf(),
})
diff --git a/lua/conform/types.lua b/lua/conform/types.lua
index 05a4e4f..a4b8cc0 100644
--- a/lua/conform/types.lua
+++ b/lua/conform/types.lua
@@ -58,13 +58,53 @@
---@field start integer[]
---@field end integer[]
----@alias conform.FormatterUnit string|string[]
----@alias conform.FiletypeFormatter conform.FormatterUnit[]|fun(bufnr: integer): string[]
+---@alias conform.FiletypeFormatter conform.FiletypeFormatterInternal|fun(bufnr: integer): conform.FiletypeFormatterInternal
+
+---This list of formatters to run for a filetype, an any associated format options.
+---@class conform.FiletypeFormatterInternal : conform.DefaultFormatOpts
+---@field [integer] string
+
+---@alias conform.LspFormatOpts
+---| '"never"' # never use the LSP for formatting (default)
+---| '"fallback"' # LSP formatting is used when no other formatters are available
+---| '"prefer"' # use only LSP formatting when available
+---| '"first"' # LSP formatting is used when available and then other formatters
+---| '"last"' # other formatters are used then LSP formatting when available
+
+---@class (exact) conform.FormatOpts
+---@field timeout_ms? integer Time in milliseconds to block for formatting. Defaults to 1000. No effect if async = true.
+---@field bufnr? integer Format this buffer (default 0)
+---@field async? boolean If true the method won't block. Defaults to false. If the buffer is modified before the formatter completes, the formatting will be discarded.
+---@field dry_run? boolean If true don't apply formatting changes to the buffer
+---@field undojoin? boolean Use undojoin to merge formatting changes with previous edit (default false)
+---@field formatters? string[] List of formatters to run. Defaults to all formatters for the buffer filetype.
+---@field lsp_format? conform.LspFormatOpts Configure if and when LSP should be used for formatting. Defaults to "never".
+---@field stop_after_first? boolean Only run the first available formatter in the list. Defaults to false.
+---@field quiet? boolean Don't show any notifications for warnings or failures. Defaults to false.
+---@field range? conform.Range Range to format. Table must contain `start` and `end` keys with {row, col} tuples using (1,0) indexing. Defaults to current selection in visual mode
+---@field id? integer Passed to |vim.lsp.buf.format| when using LSP formatting
+---@field name? string Passed to |vim.lsp.buf.format| when using LSP formatting
+---@field filter? fun(client: table): boolean Passed to |vim.lsp.buf.format| when using LSP formatting
+
+---@class (exact) conform.DefaultFormatOpts
+---@field timeout_ms? integer Time in milliseconds to block for formatting. Defaults to 1000. No effect if async = true.
+---@field lsp_format? conform.LspFormatOpts Configure if and when LSP should be used for formatting. Defaults to "never".
+---@field quiet? boolean Don't show any notifications for warnings or failures. Defaults to false.
+---@field stop_after_first? boolean Only run the first available formatter in the list. Defaults to false.
+
+---@class conform.FormatLinesOpts
+---@field timeout_ms? integer Time in milliseconds to block for formatting. Defaults to 1000. No effect if async = true.
+---@field bufnr? integer use this as the working buffer (default 0)
+---@field async? boolean If true the method won't block. Defaults to false. If the buffer is modified before the formatter completes, the formatting will be discarded.
+---@field quiet? boolean Don't show any notifications for warnings or failures. Defaults to false.
+---@field stop_after_first? boolean Only run the first available formatter in the list. Defaults to false.
---@class (exact) conform.setupOpts
---@field formatters_by_ft? table<string, conform.FiletypeFormatter> Map of filetype to formatters
---@field format_on_save? conform.FormatOpts|fun(bufnr: integer): nil|conform.FormatOpts If this is set, Conform will run the formatter on save. It will pass the table to conform.format(). This can also be a function that returns the table.
+---@field default_format_opts? conform.DefaultFormatOpts The default options to use when calling conform.format()
---@field format_after_save? conform.FormatOpts|fun(bufnr: integer): nil|conform.FormatOpts If this is set, Conform will run the formatter asynchronously after save. It will pass the table to conform.format(). This can also be a function that returns the table.
---@field log_level? integer Set the log level (e.g. `vim.log.levels.DEBUG`). Use `:ConformInfo` to see the location of the log file.
---@field notify_on_error? boolean Conform will notify you when a formatter errors (default true).
+---@field notify_no_formatters? boolean Conform will notify you when no formatters are available for the buffer (default true).
---@field formatters? table<string, conform.FormatterConfigOverride|fun(bufnr: integer): nil|conform.FormatterConfigOverride> Custom formatters and overrides for built-in formatters.
diff --git a/scripts/options_doc.lua b/scripts/options_doc.lua
index 193b462..561751d 100644
--- a/scripts/options_doc.lua
+++ b/scripts/options_doc.lua
@@ -4,8 +4,8 @@ require("conform").setup({
lua = { "stylua" },
-- Conform will run multiple formatters sequentially
go = { "goimports", "gofmt" },
- -- Use a sub-list to run only the first available formatter
- javascript = { { "prettierd", "prettier" } },
+ -- You can also customize some of the format options for the filetype
+ rust = { "rustfmt", lsp_format = "fallback" },
-- You can use a function here to determine the formatters dynamically
python = function(bufnr)
if require("conform").get_formatter_info("ruff_format", bufnr).available then
@@ -20,6 +20,11 @@ require("conform").setup({
-- have other formatters configured.
["_"] = { "trim_whitespace" },
},
+ -- Set this to change the default values when calling conform.format()
+ -- This will also affect the default values for format_on_save/format_after_save
+ default_format_opts = {
+ lsp_format = "fallback",
+ },
-- If this is set, Conform will run the formatter on save.
-- It will pass the table to conform.format().
-- This can also be a function that returns the table.
@@ -38,6 +43,8 @@ require("conform").setup({
log_level = vim.log.levels.ERROR,
-- Conform will notify you when a formatter errors
notify_on_error = true,
+ -- Conform will notify you when no formatters are available for the buffer
+ notify_no_formatters = true,
-- Custom formatters and overrides for built-in formatters
formatters = {
my_formatter = {
@@ -74,7 +81,6 @@ require("conform").setup({
-- Set to false to disable merging the config with the base definition
inherit = true,
-- When inherit = true, add these additional arguments to the beginning of the command.
- -- When inherit = true, add these additional arguments to the command.
-- This can also be a function, like args
prepend_args = { "--use-tabs" },
-- When inherit = true, add these additional arguments to the end of the command.
diff --git a/tests/injected_spec.lua b/tests/injected_spec.lua
index f6c7175..94d96b1 100644
--- a/tests/injected_spec.lua
+++ b/tests/injected_spec.lua
@@ -4,11 +4,6 @@ local injected = require("conform.formatters.injected")
local runner = require("conform.runner")
local test_util = require("tests.test_util")
--- injected formatter only supported on neovim 0.9+
-if vim.fn.has("nvim-0.9") == 0 then
- return
-end
-
---@param dir string
---@return string[]
local function list_test_files(dir)