From d7de350233e8f686b9affac9c1e106a6602f5fe8 Mon Sep 17 00:00:00 2001 From: Steven Arcangeli Date: Sat, 13 Jul 2024 12:41:18 -0700 Subject: feat: allow customizing format() defaults --- lua/conform/init.lua | 29 +++++++---------------------- 1 file changed, 7 insertions(+), 22 deletions(-) (limited to 'lua/conform/init.lua') diff --git a/lua/conform/init.lua b/lua/conform/init.lua index f0b2dfd..91ec1bd 100644 --- a/lua/conform/init.lua +++ b/lua/conform/init.lua @@ -10,12 +10,17 @@ M.formatters = {} M.notify_on_error = true +---@type conform.DefaultFormatOpts +M.default_format_opts = {} + ---@param opts? conform.setupOpts M.setup = function(opts) 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 {}) + 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 @@ -328,34 +333,14 @@ 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 - ---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 = vim.tbl_extend("keep", opts or {}, M.default_format_opts) + opts = vim.tbl_extend("keep", opts, { timeout_ms = 1000, bufnr = 0, async = false, -- cgit v1.2.3-70-g09d2 From 3a0e9b44076514ffba6c81ca28685107928b55f7 Mon Sep 17 00:00:00 2001 From: Steven Arcangeli Date: Sat, 13 Jul 2024 13:29:30 -0700 Subject: feat: allow configuring conform.format() args on a per-filetype basis --- README.md | 7 ++++++ doc/conform.txt | 18 +++++++++++++++ doc/recipes.md | 4 +++- lua/conform/health.lua | 10 +------- lua/conform/init.lua | 61 +++++++++++++++++++++++++++++-------------------- lua/conform/types.lua | 9 +++++--- scripts/options_doc.lua | 2 ++ 7 files changed, 73 insertions(+), 38 deletions(-) (limited to 'lua/conform/init.lua') diff --git a/README.md b/README.md index f7e255f..9b42903 100644 --- a/README.md +++ b/README.md @@ -459,6 +459,8 @@ require("conform").setup({ 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 @@ -602,6 +604,11 @@ Format a 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 | +| | | > `"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. | | | 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 | diff --git a/doc/conform.txt b/doc/conform.txt index 32baf86..002e46c 100644 --- a/doc/conform.txt +++ b/doc/conform.txt @@ -19,6 +19,8 @@ OPTIONS *conform-option 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 @@ -135,6 +137,14 @@ setup({opts}) *conform.setu 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. {format_after_save} `nil|conform.FormatOpts|fun(bufnr: integer): nil|conform.FormatOpts` @@ -170,6 +180,14 @@ format({opts}, {callback}): boolean *conform.forma 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 + `"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. {range} `nil|conform.Range` Range to format. Table must contain diff --git a/doc/recipes.md b/doc/recipes.md index f5d6f99..45c1e07 100644 --- a/doc/recipes.md +++ b/doc/recipes.md @@ -155,7 +155,9 @@ return { 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 = { diff --git a/lua/conform/health.lua b/lua/conform/health.lua index 3ee7567..d0bfca1 100644 --- a/lua/conform/health.lua +++ b/lua/conform/health.lua @@ -16,14 +16,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 +53,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 91ec1bd..acd8c8c 100644 --- a/lua/conform/init.lua +++ b/lua/conform/init.lua @@ -198,7 +198,7 @@ end ---@private ---@param bufnr? integer ----@return conform.FormatterUnit[] +---@return conform.FiletypeFormatterInternal[] M.list_formatters_for_buffer = function(bufnr) if not bufnr or bufnr == 0 then bufnr = vim.api.nvim_get_current_buf() @@ -233,16 +233,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 @@ -251,6 +241,31 @@ 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 + local ret = {} + for k, v in pairs(ft_formatters) do + if type(k) == "string" then + ret[k] = v + end + end + return ret +end + ---@param bufnr integer ---@param mode "v"|"V" ---@return table {start={row,col}, end={row,col}} using (1, 0) indexing @@ -283,7 +298,7 @@ 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 ---@return conform.FormatterInfo[] @@ -339,7 +354,13 @@ end ---@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 {}, M.default_format_opts) + 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) opts = vim.tbl_extend("keep", opts, { timeout_ms = 1000, bufnr = 0, @@ -372,10 +393,9 @@ 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) + M.resolve_formatters(formatter_names, opts.bufnr, not opts.quiet and has_explicit_formatters) local has_lsp = has_lsp_formatter(opts) ---@param err? conform.Error @@ -456,7 +476,7 @@ M.format = function(opts, callback) run_cli_formatters(handle_result) return true else - local level = explicit_formatters and "warn" or "debug" + local level = has_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") return false @@ -536,15 +556,6 @@ 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 diff --git a/lua/conform/types.lua b/lua/conform/types.lua index 1f1beee..2f051ca 100644 --- a/lua/conform/types.lua +++ b/lua/conform/types.lua @@ -58,9 +58,12 @@ ---@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|string[] + ---@alias conform.LspFormatOpts ---| '"never"' # never use the LSP for formatting (default) ---| '"fallback"' # LSP formatting is used when no other formatters are available diff --git a/scripts/options_doc.lua b/scripts/options_doc.lua index d47c832..c6d8457 100644 --- a/scripts/options_doc.lua +++ b/scripts/options_doc.lua @@ -6,6 +6,8 @@ require("conform").setup({ 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 -- cgit v1.2.3-70-g09d2 From 0b3d25969e2da2f5de90cc02ccd6446aa68dd895 Mon Sep 17 00:00:00 2001 From: Steven Arcangeli Date: Sat, 13 Jul 2024 14:28:18 -0700 Subject: feat: format parameter to only run the first available formatter --- README.md | 1 + doc/conform.txt | 73 +++++++++++++++++++++---------------- lua/conform/formatters/injected.lua | 20 +++++++--- lua/conform/init.lua | 34 ++++++++++------- lua/conform/types.lua | 9 +++++ 5 files changed, 85 insertions(+), 52 deletions(-) (limited to 'lua/conform/init.lua') diff --git a/README.md b/README.md index 9b42903..f5b4258 100644 --- a/README.md +++ b/README.md @@ -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 | diff --git a/doc/conform.txt b/doc/conform.txt index 002e46c..589289b 100644 --- a/doc/conform.txt +++ b/doc/conform.txt @@ -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 @@ -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 @@ -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 @@ -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: diff --git a/lua/conform/formatters/injected.lua b/lua/conform/formatters/injected.lua index 4dbf1eb..6cfd387 100644 --- a/lua/conform/formatters/injected.lua +++ b/lua/conform/formatters/injected.lua @@ -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) diff --git a/lua/conform/init.lua b/lua/conform/init.lua index acd8c8c..208edc7 100644 --- a/lua/conform/init.lua +++ b/lua/conform/init.lua @@ -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() @@ -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 @@ -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 @@ -353,7 +358,6 @@ 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 @@ -361,6 +365,7 @@ M.format = function(opts, callback) 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, @@ -369,6 +374,7 @@ M.format = function(opts, callback) lsp_format = "never", quiet = false, undojoin = false, + stop_after_first = false, }) -- For backwards compatibility @@ -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 @@ -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[] @@ -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 @@ -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 diff --git a/lua/conform/types.lua b/lua/conform/types.lua index 2f051ca..9b775a9 100644 --- a/lua/conform/types.lua +++ b/lua/conform/types.lua @@ -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 @@ -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 Map of filetype to formatters -- cgit v1.2.3-70-g09d2 From 9f111be14818c91832db8f320c4a4aa68de0e00b Mon Sep 17 00:00:00 2001 From: Steven Arcangeli Date: Sat, 13 Jul 2024 14:39:46 -0700 Subject: refactor: deprecate formatter alternation syntax --- README.md | 8 ++++---- doc/conform.txt | 2 -- doc/recipes.md | 10 +++++++--- lua/conform/init.lua | 8 ++++++++ lua/conform/types.lua | 2 +- scripts/options_doc.lua | 2 -- 6 files changed, 20 insertions(+), 12 deletions(-) (limited to 'lua/conform/init.lua') diff --git a/README.md b/README.md index f5b4258..8583331 100644 --- a/README.md +++ b/README.md @@ -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 }, }, }) ``` @@ -457,8 +459,6 @@ 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 diff --git a/doc/conform.txt b/doc/conform.txt index 589289b..89b5468 100644 --- a/doc/conform.txt +++ b/doc/conform.txt @@ -17,8 +17,6 @@ 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 diff --git a/doc/recipes.md b/doc/recipes.md index 45c1e07..97c3cdc 100644 --- a/doc/recipes.md +++ b/doc/recipes.md @@ -149,7 +149,7 @@ return { -- Customize or remove this keymap to your liking "f", function() - require("conform").format({ async = true, lsp_format = "fallback" }) + require("conform").format({ async = true }) end, mode = "", desc = "Format buffer", @@ -163,10 +163,14 @@ return { 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 = { diff --git a/lua/conform/init.lua b/lua/conform/init.lua index 208edc7..087445b 100644 --- a/lua/conform/init.lua +++ b/lua/conform/init.lua @@ -209,6 +209,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 + vim.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. Support 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 @@ -322,6 +326,10 @@ M.resolve_formatters = function(names, bufnr, warn_on_missing, stop_after_first) local info = M.get_formatter_info(name, bufnr) add_info(info, warn_on_missing) else + vim.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. Support 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) diff --git a/lua/conform/types.lua b/lua/conform/types.lua index 9b775a9..007e84a 100644 --- a/lua/conform/types.lua +++ b/lua/conform/types.lua @@ -62,7 +62,7 @@ ---This list of formatters to run for a filetype, an any associated format options. ---@class conform.FiletypeFormatterInternal : conform.DefaultFormatOpts ----@field [integer] string|string[] +---@field [integer] string ---@alias conform.LspFormatOpts ---| '"never"' # never use the LSP for formatting (default) diff --git a/scripts/options_doc.lua b/scripts/options_doc.lua index c6d8457..17734d3 100644 --- a/scripts/options_doc.lua +++ b/scripts/options_doc.lua @@ -4,8 +4,6 @@ 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 -- cgit v1.2.3-70-g09d2 From c16c749612fb34a9c1dcc6e4a0f40e24e37d5cfb Mon Sep 17 00:00:00 2001 From: Steven Arcangeli Date: Sat, 13 Jul 2024 16:37:47 -0700 Subject: fix: crash in nvim-notify --- lua/conform/init.lua | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) (limited to 'lua/conform/init.lua') diff --git a/lua/conform/init.lua b/lua/conform/init.lua index 087445b..da55067 100644 --- a/lua/conform/init.lua +++ b/lua/conform/init.lua @@ -13,6 +13,15 @@ M.notify_on_error = 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) + ---@param opts? conform.setupOpts M.setup = function(opts) opts = opts or {} @@ -49,7 +58,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 ) @@ -102,7 +111,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 ) @@ -209,8 +218,8 @@ M.list_formatters_for_buffer = function(bufnr) local function dedupe_formatters(names, collect) for _, name in ipairs(names) do if type(name) == "table" then - vim.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. Support for the old syntax will be dropped on 2025-01-01.", + 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 = {} @@ -313,7 +322,7 @@ M.resolve_formatters = function(names, bufnr, warn_on_missing, stop_after_first) 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 ) @@ -326,8 +335,8 @@ M.resolve_formatters = function(names, bufnr, warn_on_missing, stop_after_first) local info = M.get_formatter_info(name, bufnr) add_info(info, warn_on_missing) else - vim.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. Support for the old syntax will be dropped on 2025-01-01.", + 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 @@ -430,7 +439,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 @@ -611,7 +620,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 @@ -633,7 +642,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 -- cgit v1.2.3-70-g09d2 From 8c226d917918ffe92e0f30f4e13acfc088a5faa7 Mon Sep 17 00:00:00 2001 From: Steven Arcangeli Date: Sun, 14 Jul 2024 10:45:06 -0700 Subject: feat: notify when no formatters available for a buffer --- README.md | 3 +++ doc/conform.txt | 5 +++++ lua/conform/init.lua | 23 +++++++++++++++++++++-- lua/conform/types.lua | 1 + scripts/options_doc.lua | 2 ++ 5 files changed, 32 insertions(+), 2 deletions(-) (limited to 'lua/conform/init.lua') diff --git a/README.md b/README.md index 8583331..08f4b07 100644 --- a/README.md +++ b/README.md @@ -498,6 +498,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 = { @@ -587,6 +589,7 @@ require("conform").formatters.my_formatter = { | | 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` | Custom formatters and overrides for built-in formatters. | ### format(opts, callback) diff --git a/doc/conform.txt b/doc/conform.txt index 89b5468..8d2fa3e 100644 --- a/doc/conform.txt +++ b/doc/conform.txt @@ -56,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 = { @@ -158,6 +160,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` Custom formatters and overrides for built-in formatters. diff --git a/lua/conform/init.lua b/lua/conform/init.lua index da55067..1a648f0 100644 --- a/lua/conform/init.lua +++ b/lua/conform/init.lua @@ -9,6 +9,7 @@ M.formatters_by_ft = {} M.formatters = {} M.notify_on_error = true +M.notify_no_formatters = true ---@type conform.DefaultFormatOpts M.default_format_opts = {} @@ -38,6 +39,10 @@ M.setup = function(opts) if notify_on_error ~= nil then M.notify_on_error = notify_on_error end + local notify_no_formatters = opts.notify_no_formatters + if notify_no_formatters ~= nil then + M.notify_no_formatters = notify_no_formatters + end local aug = vim.api.nvim_create_augroup("Conform", { clear = true }) if opts.format_on_save then @@ -370,6 +375,8 @@ local function has_lsp_formatter(opts) return not vim.tbl_isempty(lsp_format.get_format_clients(opts)) end +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 @@ -504,8 +511,20 @@ M.format = function(opts, callback) return true else local level = has_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") + 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 diff --git a/lua/conform/types.lua b/lua/conform/types.lua index 007e84a..a4b8cc0 100644 --- a/lua/conform/types.lua +++ b/lua/conform/types.lua @@ -106,4 +106,5 @@ ---@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 Custom formatters and overrides for built-in formatters. diff --git a/scripts/options_doc.lua b/scripts/options_doc.lua index 17734d3..62ba104 100644 --- a/scripts/options_doc.lua +++ b/scripts/options_doc.lua @@ -43,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 = { -- cgit v1.2.3-70-g09d2 From 834d42c17687541750447046b94193d47386665d Mon Sep 17 00:00:00 2001 From: Steven Arcangeli Date: Sun, 14 Jul 2024 21:06:35 -0700 Subject: fix: ensure only expected options get passed through --- lua/conform/init.lua | 54 ++++++++++++++++++++++++++++--------------------- scripts/options_doc.lua | 1 - 2 files changed, 31 insertions(+), 24 deletions(-) (limited to 'lua/conform/init.lua') diff --git a/lua/conform/init.lua b/lua/conform/init.lua index 1a648f0..2b27092 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 @@ -23,6 +21,16 @@ 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 opts? conform.setupOpts M.setup = function(opts) opts = opts or {} @@ -35,13 +43,11 @@ M.setup = function(opts) 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 - local notify_no_formatters = opts.notify_no_formatters - if notify_no_formatters ~= nil then - M.notify_no_formatters = notify_no_formatters + 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 }) @@ -193,7 +199,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() @@ -275,13 +281,7 @@ local function get_opts_from_filetype(bufnr) if type(ft_formatters) == "function" then ft_formatters = ft_formatters(bufnr) end - local ret = {} - for k, v in pairs(ft_formatters) do - if type(k) == "string" then - ret[k] = v - end - end - return ret + return merge_default_opts({}, ft_formatters) end ---@param bufnr integer @@ -383,12 +383,12 @@ local has_notified_ft_no_formatters = {} ---@return boolean True if any formatters were attempted M.format = function(opts, callback) opts = opts or {} - local has_explicit_formatters = opts ~= nil and opts.formatters ~= nil + 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 - opts = vim.tbl_extend("keep", opts, get_opts_from_filetype(opts.bufnr)) + merge_default_opts(opts, get_opts_from_filetype(opts.bufnr) or {}) end - - opts = vim.tbl_extend("keep", opts, M.default_format_opts) + 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, @@ -400,6 +400,9 @@ M.format = function(opts, callback) 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 @@ -410,9 +413,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) @@ -432,12 +432,14 @@ M.format = function(opts, callback) ) 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 @@ -466,6 +468,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 @@ -601,6 +605,10 @@ M.list_all_formatters = function() 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 diff --git a/scripts/options_doc.lua b/scripts/options_doc.lua index 62ba104..561751d 100644 --- a/scripts/options_doc.lua +++ b/scripts/options_doc.lua @@ -81,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. -- cgit v1.2.3-70-g09d2 From 1b590cda290bbabdb116397cb241d20530281b87 Mon Sep 17 00:00:00 2001 From: Steven Arcangeli Date: Sun, 14 Jul 2024 23:09:51 -0700 Subject: feat: deprecate will_fallback_lsp in favor of list_formatters_to_run --- lua/conform/init.lua | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) (limited to 'lua/conform/init.lua') diff --git a/lua/conform/init.lua b/lua/conform/init.lua index 2b27092..a0f1b3c 100644 --- a/lua/conform/init.lua +++ b/lua/conform/init.lua @@ -594,6 +594,43 @@ M.list_formatters = function(bufnr) 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 ---@return conform.FormatterInfo[] M.list_all_formatters = function() @@ -753,9 +790,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(), }) -- cgit v1.2.3-70-g09d2 From 8b0e62b731429ecd89cdb6c6b8f004f8468bcf71 Mon Sep 17 00:00:00 2001 From: Steven Arcangeli Date: Sun, 14 Jul 2024 23:59:19 -0700 Subject: fix: warn user when they are attempting unsupported behavior --- lua/conform/init.lua | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) (limited to 'lua/conform/init.lua') diff --git a/lua/conform/init.lua b/lua/conform/init.lua index a0f1b3c..5d515c4 100644 --- a/lua/conform/init.lua +++ b/lua/conform/init.lua @@ -31,12 +31,33 @@ local function merge_default_opts(a, b) 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) 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 {}) -- cgit v1.2.3-70-g09d2 From 54ea60d1591486e7e56183addf1f45b03244386d Mon Sep 17 00:00:00 2001 From: Steven Arcangeli Date: Mon, 15 Jul 2024 09:32:56 -0700 Subject: cleanup!: drop support for nvim 0.8 --- README.md | 4 ++-- doc/conform.txt | 2 +- lua/conform/formatters/dcm_format.lua | 2 +- lua/conform/formatters/injected.lua | 6 ------ lua/conform/init.lua | 4 ++++ tests/injected_spec.lua | 5 ----- 6 files changed, 8 insertions(+), 15 deletions(-) (limited to 'lua/conform/init.lua') diff --git a/README.md b/README.md index 3c5e826..e7860de 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ Lightweight yet powerful formatter plugin for Neovim ## Requirements -- Neovim 0.8+ +- Neovim 0.9+ (for older versions, use a [nvim-0.x branch](https://github.com/stevearc/conform.nvim/branches)) ## Features @@ -216,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. diff --git a/doc/conform.txt b/doc/conform.txt index d514c62..5af52f8 100644 --- a/doc/conform.txt +++ b/doc/conform.txt @@ -301,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/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 6cfd387..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") diff --git a/lua/conform/init.lua b/lua/conform/init.lua index 5d515c4..c174dea 100644 --- a/lua/conform/init.lua +++ b/lua/conform/init.lua @@ -52,6 +52,10 @@ 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 {}) 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) -- cgit v1.2.3-70-g09d2