aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSteven Arcangeli <stevearc@stevearc.com>2023-09-08 08:48:33 -0700
committerSteven Arcangeli <stevearc@stevearc.com>2023-09-08 09:09:17 -0700
commit2568d746abbadf66a03c62b568ee73d874cd8617 (patch)
treef111dfde4eed5b9d0d7b55c9a242e1b0345fa44e
parentbd1aa02ef191410b2ea0b3ef5caabe06592d9c51 (diff)
feat: syntax for using first available formatter
Alternations are now supported. You can specify a sub-list in place of a formatter name and conform will use the first formatter in that list that is available. For example, this will use either prettierd or prettier (whichever is available), and then always trim whitespace afterwards: conform.format(formatters = { { "prettierd", "prettier" }, "trim_whitespace" }) This syntax is available both in the formatters_by_ft config option and in the `formatters` argument of the `format` method.
-rw-r--r--README.md14
-rw-r--r--doc/conform.txt4
-rw-r--r--lua/conform/health.lua92
-rw-r--r--lua/conform/init.lua130
-rw-r--r--scripts/options_doc.lua2
5 files changed, 153 insertions, 89 deletions
diff --git a/README.md b/README.md
index 8d6cd45..66b50dc 100644
--- a/README.md
+++ b/README.md
@@ -110,8 +110,10 @@ At a minimum, you will need to set up some formatters by filetype
require("conform").setup({
formatters_by_ft = {
lua = { "stylua" },
- -- Conform will run multiple formatters sequentially
+ -- Conform will run multiple formatters sequentially
python = { "isort", "black" },
+ -- Use a sub-list to run only the first available formatter
+ javascript = { { "prettierd", "prettier" } },
},
})
```
@@ -210,6 +212,8 @@ 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" } },
},
-- If this is set, Conform will run the formatter on save.
-- It will pass the table to conform.format().
@@ -384,10 +388,10 @@ List information about all filetype-configured formatters
`get_formatter_info(formatter, bufnr): conform.FormatterInfo` \
Get information about a formatter (including availability)
-| Param | Type | Desc |
-| --------- | -------------- | ---- |
-| formatter | `string` | |
-| bufnr | `nil\|integer` | |
+| Param | Type | Desc |
+| --------- | -------------- | ------------------------- |
+| formatter | `string` | The name of the formatter |
+| bufnr | `nil\|integer` | |
<!-- /API -->
## Acknowledgements
diff --git a/doc/conform.txt b/doc/conform.txt
index 5995f99..66754a6 100644
--- a/doc/conform.txt
+++ b/doc/conform.txt
@@ -18,6 +18,8 @@ OPTIONS *conform-option
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" } },
},
-- If this is set, Conform will run the formatter on save.
-- It will pass the table to conform.format().
@@ -118,7 +120,7 @@ get_formatter_info({formatter}, {bufnr}): conform.FormatterInfo *conform.get_for
Get information about a formatter (including availability)
Parameters:
- {formatter} `string`
+ {formatter} `string` The name of the formatter
{bufnr} `nil|integer`
--------------------------------------------------------------------------------
diff --git a/lua/conform/health.lua b/lua/conform/health.lua
index d36ef03..18085bf 100644
--- a/lua/conform/health.lua
+++ b/lua/conform/health.lua
@@ -6,11 +6,24 @@ local function get_formatter_filetypes(name)
local conform = require("conform")
local filetypes = {}
for filetype, formatters in pairs(conform.formatters_by_ft) do
+ -- support the old structure where formatters could be a subkey
if not vim.tbl_islist(formatters) then
+ ---@diagnostic disable-next-line: undefined-field
formatters = formatters.formatters
end
- if vim.tbl_contains(formatters, name) then
- table.insert(filetypes, filetype)
+
+ for _, ft_name in ipairs(formatters) do
+ if type(ft_name) == "string" then
+ if ft_name == name then
+ table.insert(filetypes, filetype)
+ break
+ end
+ else
+ if vim.tbl_contains(ft_name, name) then
+ table.insert(filetypes, filetype)
+ break
+ end
+ end
end
end
return filetypes
@@ -38,6 +51,22 @@ M.check = function()
end
end
+---@param formatters conform.FormatterUnit[]
+---@return string[]
+local function flatten_formatters(formatters)
+ local flat = {}
+ for _, name in ipairs(formatters) do
+ if type(name) == "string" then
+ table.insert(flat, name)
+ else
+ for _, f in ipairs(flatten_formatters(name)) do
+ table.insert(flat, f)
+ end
+ end
+ end
+ return flat
+end
+
M.show_window = function()
local conform = require("conform")
local lines = {}
@@ -60,35 +89,43 @@ M.show_window = function()
end
table.insert(lines, "")
- ---@param formatters conform.FormatterInfo[]
+ ---@param formatter conform.FormatterInfo
+ local function append_formatter_info(formatter)
+ if not formatter.available then
+ local line = string.format("%s unavailable: %s", formatter.name, formatter.available_msg)
+ table.insert(lines, line)
+ table.insert(
+ highlights,
+ { "DiagnosticWarn", #lines, formatter.name:len(), formatter.name:len() + 12 }
+ )
+ else
+ local filetypes = get_formatter_filetypes(formatter.name)
+ local line = string.format("%s ready (%s)", formatter.name, table.concat(filetypes, ", "))
+ table.insert(lines, line)
+ table.insert(
+ highlights,
+ { "DiagnosticInfo", #lines, formatter.name:len(), formatter.name:len() + 6 }
+ )
+ end
+ end
+
+ local seen = {}
+ ---@param formatters string[]
local function append_formatters(formatters)
- for _, formatter in ipairs(formatters) do
- if not formatter.available then
- local line = string.format("%s unavailable: %s", formatter.name, formatter.available_msg)
- table.insert(lines, line)
- table.insert(
- highlights,
- { "DiagnosticWarn", #lines, formatter.name:len(), formatter.name:len() + 12 }
- )
+ for _, name in ipairs(formatters) do
+ if type(name) == "table" then
+ append_formatters(name)
else
- local filetypes = get_formatter_filetypes(formatter.name)
- local line = string.format("%s ready (%s)", formatter.name, table.concat(filetypes, ", "))
- table.insert(lines, line)
- table.insert(
- highlights,
- { "DiagnosticInfo", #lines, formatter.name:len(), formatter.name:len() + 6 }
- )
+ seen[name] = true
+ local formatter = conform.get_formatter_info(name)
+ append_formatter_info(formatter)
end
end
end
table.insert(lines, "Formatters for this buffer:")
table.insert(highlights, { "Title", #lines, 0, -1 })
- local seen = {}
- local buf_formatters = conform.list_formatters_for_buffer()
- for _, formatter in ipairs(buf_formatters) do
- seen[formatter.name] = true
- end
+ local buf_formatters = flatten_formatters(conform.list_formatters_for_buffer())
append_formatters(buf_formatters)
if vim.tbl_isempty(buf_formatters) then
table.insert(lines, "<none>")
@@ -97,10 +134,11 @@ M.show_window = function()
table.insert(lines, "")
table.insert(lines, "Other formatters:")
table.insert(highlights, { "Title", #lines, 0, -1 })
- local all_formatters = vim.tbl_filter(function(f)
- return not seen[f.name]
- end, conform.list_all_formatters())
- append_formatters(all_formatters)
+ for _, formatter in ipairs(conform.list_all_formatters()) do
+ if not seen[formatter.name] then
+ append_formatter_info(formatter)
+ end
+ end
local bufnr = vim.api.nvim_create_buf(false, true)
local winid = vim.api.nvim_open_win(bufnr, true, {
diff --git a/lua/conform/init.lua b/lua/conform/init.lua
index ef6f94a..2761b45 100644
--- a/lua/conform/init.lua
+++ b/lua/conform/init.lua
@@ -99,7 +99,7 @@ end
---@private
---@param bufnr? integer
----@return conform.FormatterInfo[]
+---@return conform.FormatterUnit[]
M.list_formatters_for_buffer = function(bufnr)
if not bufnr or bufnr == 0 then
bufnr = vim.api.nvim_get_current_buf()
@@ -107,8 +107,25 @@ M.list_formatters_for_buffer = function(bufnr)
local formatters = {}
local seen = {}
local filetypes = vim.split(vim.bo[bufnr].filetype, ".", { plain = true })
+
+ local function dedupe_formatters(names, collect)
+ for _, name in ipairs(names) do
+ if type(name) == "table" then
+ local alternation = {}
+ dedupe_formatters(name, alternation)
+ if not vim.tbl_isempty(alternation) then
+ table.insert(collect, alternation)
+ end
+ elseif not seen[name] then
+ table.insert(collect, name)
+ seen[name] = true
+ end
+ end
+ end
+
table.insert(filetypes, "*")
for _, filetype in ipairs(filetypes) do
+ ---@type conform.FormatterUnit[]
local ft_formatters = M.formatters_by_ft[filetype]
if ft_formatters then
-- support the old structure where formatters could be a subkey
@@ -117,35 +134,11 @@ M.list_formatters_for_buffer = function(bufnr)
ft_formatters = ft_formatters.formatters
end
- for _, formatter in ipairs(ft_formatters) do
- if not seen[formatter] then
- table.insert(formatters, formatter)
- seen[formatter] = true
- end
- end
+ dedupe_formatters(ft_formatters, formatters)
end
end
- ---@type conform.FormatterInfo[]
- local all_info = vim.tbl_map(function(f)
- return M.get_formatter_info(f, bufnr)
- end, formatters)
-
- return all_info
-end
-
----@param formatters conform.FormatterInfo[]
----@return conform.FormatterInfo[]
-local function filter_formatters(formatters)
- ---@type conform.FormatterInfo[]
- local all_info = {}
- for _, info in ipairs(formatters) do
- if info.available then
- table.insert(all_info, info)
- end
- end
-
- return all_info
+ return formatters
end
---@param bufnr integer
@@ -179,6 +172,41 @@ local function range_from_selection(bufnr, mode)
}
end
+---@param names conform.FormatterUnit[]
+---@param bufnr integer
+---@param warn_on_missing boolean
+---@return conform.FormatterInfo[]
+local function resolve_formatters(names, bufnr, warn_on_missing)
+ local all_info = {}
+ local function add_info(info, warn)
+ if info.available then
+ table.insert(all_info, info)
+ elseif warn then
+ vim.notify(
+ string.format("Formatter '%s' unavailable: %s", info.name, info.available_msg),
+ vim.log.levels.WARN
+ )
+ end
+ return info.available
+ end
+
+ for _, name in ipairs(names) do
+ if type(name) == "string" then
+ local info = M.get_formatter_info(name, bufnr)
+ add_info(info, warn_on_missing)
+ else
+ -- 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)
+ if add_info(info, i == #name) then
+ break
+ end
+ end
+ end
+ end
+ return all_info
+end
+
---Format a buffer
---@param opts? table
--- timeout_ms nil|integer Time in milliseconds to block for formatting. Defaults to 1000. No effect if async = true.
@@ -204,34 +232,15 @@ M.format = function(opts, callback)
local lsp_format = require("conform.lsp_format")
local runner = require("conform.runner")
- local formatters = {}
- local any_formatters_configured
- if opts.formatters then
- any_formatters_configured = true
- for _, formatter in ipairs(opts.formatters) do
- local info = M.get_formatter_info(formatter)
- if info.available then
- table.insert(formatters, info)
- else
- if opts.quiet then
- log.warn("Formatter '%s' unavailable: %s", info.name, info.available_msg)
- else
- vim.notify(
- string.format("Formatter '%s' unavailable: %s", info.name, info.available_msg),
- vim.log.levels.WARN
- )
- end
- end
- end
- else
- formatters = M.list_formatters_for_buffer(opts.bufnr)
- any_formatters_configured = not vim.tbl_isempty(formatters)
- formatters = filter_formatters(formatters)
- end
- local formatter_names = vim.tbl_map(function(f)
+ local formatter_names = opts.formatters or M.list_formatters_for_buffer(opts.bufnr)
+ local any_formatters_configured = formatter_names ~= nil and not vim.tbl_isempty(formatter_names)
+ local formatters =
+ resolve_formatters(formatter_names, opts.bufnr, not opts.quiet and opts.formatters ~= nil)
+
+ local resolved_names = vim.tbl_map(function(f)
return f.name
end, formatters)
- log.debug("Running formatters on %s: %s", vim.api.nvim_buf_get_name(opts.bufnr), formatter_names)
+ log.debug("Running formatters on %s: %s", vim.api.nvim_buf_get_name(opts.bufnr), resolved_names)
local any_formatters = not vim.tbl_isempty(formatters)
if any_formatters then
@@ -287,8 +296,11 @@ end
---@param bufnr? integer
---@return conform.FormatterInfo[]
M.list_formatters = function(bufnr)
+ if not bufnr or bufnr == 0 then
+ bufnr = vim.api.nvim_get_current_buf()
+ end
local formatters = M.list_formatters_for_buffer(bufnr)
- return filter_formatters(formatters)
+ return resolve_formatters(formatters, bufnr, false)
end
---List information about all filetype-configured formatters
@@ -303,7 +315,13 @@ M.list_all_formatters = function()
end
for _, formatter in ipairs(ft_formatters) do
- formatters[formatter] = true
+ if type(formatter) == "table" then
+ for _, v in ipairs(formatter) do
+ formatters[v] = true
+ end
+ else
+ formatters[formatter] = true
+ end
end
end
@@ -348,7 +366,7 @@ M.get_formatter_config = function(formatter, bufnr)
end
---Get information about a formatter (including availability)
----@param formatter string
+---@param formatter string The name of the formatter
---@param bufnr? integer
---@return conform.FormatterInfo
M.get_formatter_info = function(formatter, bufnr)
diff --git a/scripts/options_doc.lua b/scripts/options_doc.lua
index a796501..47eccd9 100644
--- a/scripts/options_doc.lua
+++ b/scripts/options_doc.lua
@@ -4,6 +4,8 @@ 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" } },
},
-- If this is set, Conform will run the formatter on save.
-- It will pass the table to conform.format().