Skip to content

Commit

Permalink
feat: format parameter to only run the first available formatter
Browse files Browse the repository at this point in the history
  • Loading branch information
stevearc committed Jul 19, 2024
1 parent 3a0e9b4 commit 0b3d259
Show file tree
Hide file tree
Showing 5 changed files with 85 additions and 52 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -609,6 +609,7 @@ 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\|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 |
Expand Down
73 changes: 41 additions & 32 deletions doc/conform.txt
Original file line number Diff line number Diff line change
Expand Up @@ -132,11 +132,12 @@ setup({opts}) *conform.setu
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".
{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
Expand All @@ -145,8 +146,10 @@ setup({opts}) *conform.setu
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.
{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
Expand All @@ -166,20 +169,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
Expand All @@ -188,19 +194,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|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
{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.lsp.buf.format| when using LSP formatting
{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:
Expand Down
20 changes: 14 additions & 6 deletions lua/conform/formatters/injected.lua
Original file line number Diff line number Diff line change
Expand Up @@ -284,13 +284,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)
Expand Down
34 changes: 20 additions & 14 deletions lua/conform/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ end

---@private
---@param bufnr? integer
---@return conform.FiletypeFormatterInternal[]
---@return string[]
M.list_formatters_for_buffer = function(bufnr)
if not bufnr or bufnr == 0 then
bufnr = vim.api.nvim_get_current_buf()
Expand Down Expand Up @@ -301,8 +301,9 @@ end
---@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
Expand All @@ -329,6 +330,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
Expand All @@ -353,14 +358,14 @@ end
---@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 = opts or {}
local has_explicit_formatters = opts ~= nil and opts.formatters ~= nil
if not has_explicit_formatters then
opts = vim.tbl_extend("keep", opts, get_opts_from_filetype(opts.bufnr))
end

opts = vim.tbl_extend("keep", 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,
Expand All @@ -369,6 +374,7 @@ M.format = function(opts, callback)
lsp_format = "never",
quiet = false,
undojoin = false,
stop_after_first = false,
})

-- For backwards compatibility
Expand All @@ -394,8 +400,12 @@ M.format = function(opts, callback)
local runner = require("conform.runner")

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 has_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)

---@param err? conform.Error
Expand Down Expand Up @@ -483,12 +493,6 @@ M.format = function(opts, callback)
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[]
Expand All @@ -498,18 +502,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
Expand Down Expand Up @@ -545,7 +551,7 @@ 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

---List information about all filetype-configured formatters
Expand Down
9 changes: 9 additions & 0 deletions lua/conform/types.lua
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@
---@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
Expand All @@ -89,6 +90,14 @@
---@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
Expand Down

0 comments on commit 0b3d259

Please sign in to comment.