From 2568d746abbadf66a03c62b568ee73d874cd8617 Mon Sep 17 00:00:00 2001 From: Steven Arcangeli Date: Fri, 8 Sep 2023 08:48:33 -0700 Subject: 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. --- lua/conform/health.lua | 92 ++++++++++++++++++++++++---------- lua/conform/init.lua | 130 ++++++++++++++++++++++++++++--------------------- 2 files changed, 139 insertions(+), 83 deletions(-) (limited to 'lua/conform') 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, "") @@ -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) -- cgit v1.2.3-70-g09d2