diff options
-rw-r--r-- | README.md | 2 | ||||
-rw-r--r-- | doc/conform.txt | 54 | ||||
-rw-r--r-- | lua/conform/formatters/autopep8.lua | 2 | ||||
-rw-r--r-- | lua/conform/formatters/clang_format.lua | 2 | ||||
-rw-r--r-- | lua/conform/formatters/darker.lua | 2 | ||||
-rw-r--r-- | lua/conform/formatters/deno_fmt.lua | 2 | ||||
-rw-r--r-- | lua/conform/formatters/markdown-toc.lua | 2 | ||||
-rw-r--r-- | lua/conform/formatters/phpcbf.lua | 2 | ||||
-rw-r--r-- | lua/conform/formatters/prettier.lua | 2 | ||||
-rw-r--r-- | lua/conform/formatters/prettierd.lua | 2 | ||||
-rw-r--r-- | lua/conform/formatters/stylua.lua | 2 | ||||
-rw-r--r-- | lua/conform/formatters/uncrustify.lua | 2 | ||||
-rw-r--r-- | lua/conform/formatters/yapf.lua | 2 | ||||
-rw-r--r-- | lua/conform/formatters/zprint.lua | 2 | ||||
-rw-r--r-- | lua/conform/init.lua | 26 | ||||
-rw-r--r-- | lua/conform/runner.lua | 21 | ||||
-rw-r--r-- | lua/conform/util.lua | 62 | ||||
-rwxr-xr-x | scripts/generate.py | 40 | ||||
-rw-r--r-- | tests/runner_spec.lua | 12 |
19 files changed, 147 insertions, 94 deletions
@@ -337,7 +337,7 @@ require("conform").formatters.shfmt = { } -- prepend_args can be a function, just like args require("conform").formatters.shfmt = { - prepend_args = function(ctx) + prepend_args = function(self, ctx) return { "-i", "2" } end, } diff --git a/doc/conform.txt b/doc/conform.txt index 66514cc..096c195 100644 --- a/doc/conform.txt +++ b/doc/conform.txt @@ -6,7 +6,7 @@ CONTENTS *conform-content 1. Options |conform-options| 2. Api |conform-api| 3. Formatters |conform-formatters| - 4. Autoformat |conform-autoformat| + 4. Self argument migration |conform-self-args| -------------------------------------------------------------------------------- OPTIONS *conform-options* @@ -331,45 +331,27 @@ FORMATTERS *conform-formatter `zprint` - Formatter for Clojure and EDN. -------------------------------------------------------------------------------- -AUTOFORMAT *conform-autoformat* +SELF ARGUMENT MIGRATION *conform-self-args* -If you want more complex logic than the `format_on_save` option allows, you can -write it yourself using your own autocmd. For example: +The function arguments for formatter config functions have changed. Previously, +they took a single `ctx` argument. >lua - -- if format_on_save is a function, it will be called during BufWritePre - require("conform").setup({ - format_on_save = function(bufnr) - -- Disable autoformat on certain filetypes - local ignore_filetypes = { "sql", "java" } - if vim.tbl_contains(ignore_filetypes, vim.bo[bufnr].filetype) then - return - end - -- Disable with a global or buffer-local variable - if vim.g.disable_autoformat or vim.b[bufnr].disable_autoformat then - return - end - -- Disable autoformat for files in a certain path - local bufname = vim.api.nvim_buf_get_name(bufnr) - if bufname:match("/node_modules/") then - return + { + command = "phpcbf", + args = function(ctx) + return { "-q", "--stdin-path=" .. ctx.filename, "-" } end - -- ...additional logic... - return { timeout_ms = 500, lsp_fallback = true } - end, - }) - - -- There is a similar affordance for format_after_save, which uses BufWritePost. - -- This is good for formatters that are too slow to run synchronously. - require("conform").setup({ - format_after_save = function(bufnr) - if vim.g.disable_autoformat or vim.b[bufnr].disable_autoformat then - return + } +<Now, they take `self` as the first argument, and `ctx` as the second. +>lua + { + command = "phpcbf", + args = function(self, ctx) + return { "-q", "--stdin-path=" .. ctx.filename, "-" } end - -- ...additional logic... - return { lsp_fallback = true } - end, - }) -< + } +<The config values that can be defined as functions are: `command`, `args`, +`range_args`, `cwd`, `env`, and `condition`. ================================================================================ vim:tw=80:ts=2:ft=help:norl:syntax=help: diff --git a/lua/conform/formatters/autopep8.lua b/lua/conform/formatters/autopep8.lua index 3945e47..3d5b015 100644 --- a/lua/conform/formatters/autopep8.lua +++ b/lua/conform/formatters/autopep8.lua @@ -6,7 +6,7 @@ return { }, command = "autopep8", args = { "-" }, - range_args = function(ctx) + range_args = function(self, ctx) return { "-", "--line-range", tostring(ctx.range.start[1]), tostring(ctx.range["end"][1]) } end, } diff --git a/lua/conform/formatters/clang_format.lua b/lua/conform/formatters/clang_format.lua index 44af877..2e68fc1 100644 --- a/lua/conform/formatters/clang_format.lua +++ b/lua/conform/formatters/clang_format.lua @@ -7,7 +7,7 @@ return { }, command = "clang-format", args = { "-assume-filename", "$FILENAME" }, - range_args = function(ctx) + range_args = function(self, ctx) local start_offset, end_offset = util.get_offsets_from_range(ctx.buf, ctx.range) local length = end_offset - start_offset return { diff --git a/lua/conform/formatters/darker.lua b/lua/conform/formatters/darker.lua index 5f47e6b..9fe9b20 100644 --- a/lua/conform/formatters/darker.lua +++ b/lua/conform/formatters/darker.lua @@ -6,7 +6,7 @@ return { description = "Run black only on changed lines.", }, command = "darker", - args = function(ctx) + args = function(self, ctx) -- make sure pre-save doesn't lose changes while post-save respects -- the revision setting potentially set in pyproject.toml if vim.bo[ctx.buf].modified then diff --git a/lua/conform/formatters/deno_fmt.lua b/lua/conform/formatters/deno_fmt.lua index 7b48320..5e7f452 100644 --- a/lua/conform/formatters/deno_fmt.lua +++ b/lua/conform/formatters/deno_fmt.lua @@ -14,7 +14,7 @@ return { description = "Use [Deno](https://deno.land/) to format TypeScript, JavaScript/JSON and markdown.", }, command = "deno", - args = function(ctx) + args = function(self, ctx) return { "fmt", "-", diff --git a/lua/conform/formatters/markdown-toc.lua b/lua/conform/formatters/markdown-toc.lua index 129d50e..a7a9694 100644 --- a/lua/conform/formatters/markdown-toc.lua +++ b/lua/conform/formatters/markdown-toc.lua @@ -6,7 +6,7 @@ return { }, command = "markdown-toc", stdin = false, - args = function(ctx) + args = function(self, ctx) -- use the indentation set in the current buffer, effectively allowing us to -- use values from .editorconfig local indent = vim.bo[ctx.buf].expandtab and (" "):rep(vim.bo[ctx.buf].tabstop) or "\t" diff --git a/lua/conform/formatters/phpcbf.lua b/lua/conform/formatters/phpcbf.lua index 38f3155..6c6027e 100644 --- a/lua/conform/formatters/phpcbf.lua +++ b/lua/conform/formatters/phpcbf.lua @@ -9,7 +9,7 @@ return { command = util.find_executable({ "vendor/bin/phpcbf", }, "phpcbf"), - args = function(ctx) + args = function(self, ctx) return { "-q", "--stdin-path=" .. ctx.filename, "-" } end, stdin = true, diff --git a/lua/conform/formatters/prettier.lua b/lua/conform/formatters/prettier.lua index ea45ffb..52dda86 100644 --- a/lua/conform/formatters/prettier.lua +++ b/lua/conform/formatters/prettier.lua @@ -7,7 +7,7 @@ return { }, command = util.from_node_modules("prettier"), args = { "--stdin-filepath", "$FILENAME" }, - range_args = function(ctx) + range_args = function(self, ctx) local start_offset, end_offset = util.get_offsets_from_range(ctx.buf, ctx.range) return { "$FILENAME", "--range-start=" .. start_offset, "--range-end=" .. end_offset } end, diff --git a/lua/conform/formatters/prettierd.lua b/lua/conform/formatters/prettierd.lua index 3cdc19e..386d441 100644 --- a/lua/conform/formatters/prettierd.lua +++ b/lua/conform/formatters/prettierd.lua @@ -7,7 +7,7 @@ return { }, command = util.from_node_modules("prettierd"), args = { "$FILENAME" }, - range_args = function(ctx) + range_args = function(self, ctx) local start_offset, end_offset = util.get_offsets_from_range(ctx.buf, ctx.range) return { "$FILENAME", "--range-start=" .. start_offset, "--range-end=" .. end_offset } end, diff --git a/lua/conform/formatters/stylua.lua b/lua/conform/formatters/stylua.lua index d971932..25012bf 100644 --- a/lua/conform/formatters/stylua.lua +++ b/lua/conform/formatters/stylua.lua @@ -7,7 +7,7 @@ return { }, command = "stylua", args = { "--search-parent-directories", "--stdin-filepath", "$FILENAME", "-" }, - range_args = function(ctx) + range_args = function(self, ctx) local start_offset, end_offset = util.get_offsets_from_range(ctx.buf, ctx.range) return { "--search-parent-directories", diff --git a/lua/conform/formatters/uncrustify.lua b/lua/conform/formatters/uncrustify.lua index 2782e96..aec93c2 100644 --- a/lua/conform/formatters/uncrustify.lua +++ b/lua/conform/formatters/uncrustify.lua @@ -5,7 +5,7 @@ return { description = "A source code beautifier for C, C++, C#, ObjectiveC, D, Java, Pawn and Vala.", }, command = "uncrustify", - args = function(ctx) + args = function(self, ctx) return { "-q", "-l", vim.bo[ctx.buf].filetype:upper() } end, } diff --git a/lua/conform/formatters/yapf.lua b/lua/conform/formatters/yapf.lua index fc3cfeb..63bff83 100644 --- a/lua/conform/formatters/yapf.lua +++ b/lua/conform/formatters/yapf.lua @@ -6,7 +6,7 @@ return { }, command = "yapf", args = { "--quiet" }, - range_args = function(ctx) + range_args = function(self, ctx) return { "--quiet", "--lines", string.format("%d-%d", ctx.range.start[1], ctx.range["end"][1]) } end, } diff --git a/lua/conform/formatters/zprint.lua b/lua/conform/formatters/zprint.lua index 935a485..240b7ed 100644 --- a/lua/conform/formatters/zprint.lua +++ b/lua/conform/formatters/zprint.lua @@ -5,7 +5,7 @@ return { description = "Formatter for Clojure and EDN.", }, command = "zprint", - range_args = function(ctx) + range_args = function(self, ctx) return { string.format( "{:input {:range {:start %d :end %d :use-previous-!zprint? true :continue-after-!zprint-error? true}}}", diff --git a/lua/conform/init.lua b/lua/conform/init.lua index 50ae6ba..d980b8f 100644 --- a/lua/conform/init.lua +++ b/lua/conform/init.lua @@ -8,15 +8,16 @@ local M = {} ---@field available_msg? string ---@class (exact) conform.JobFormatterConfig ----@field command string|fun(ctx: conform.Context): string ----@field args? string|string[]|fun(ctx: conform.Context): string|string[] ----@field range_args? fun(ctx: conform.RangeContext): string|string[] ----@field cwd? fun(ctx: conform.Context): nil|string +---@field command string|fun(self: conform.JobFormatterConfig, ctx: conform.Context): string +---@field args? string|string[]|fun(self: conform.JobFormatterConfig, ctx: conform.Context): string|string[] +---@field range_args? fun(self: conform.JobFormatterConfig, ctx: conform.RangeContext): string|string[] +---@field cwd? fun(self: conform.JobFormatterConfig, ctx: conform.Context): nil|string ---@field require_cwd? boolean When cwd is not found, don't run the formatter (default false) ---@field stdin? boolean Send buffer contents to stdin (default true) ----@field condition? fun(ctx: conform.Context): boolean +---@field condition? fun(self: conform.JobFormatterConfig, ctx: conform.Context): boolean ---@field exit_codes? integer[] Exit codes that indicate success (default {0}) ----@field env? table<string, any>|fun(ctx: conform.Context): table<string, any> +---@field env? table<string, any>|fun(self: conform.JobFormatterConfig, ctx: conform.Context): table<string, any> +---@field options? table ---@class (exact) conform.LuaFormatterConfig ---@field format fun(self: conform.LuaFormatterConfig, ctx: conform.Context, lines: string[], callback: fun(err: nil|string, new_lines: nil|string[])) @@ -33,8 +34,8 @@ local M = {} ---@class (exact) conform.FormatterConfigOverride : conform.JobFormatterConfig ---@field inherit? boolean ----@field command? string|fun(ctx: conform.Context): string ----@field prepend_args? string|string[]|fun(ctx: conform.Context): string|string[] +---@field command? string|fun(self: conform.FormatterConfig, ctx: conform.Context): string +---@field prepend_args? string|string[]|fun(self: conform.FormatterConfig, ctx: conform.Context): string|string[] ---@field options? table ---@class (exact) conform.FormatterMeta @@ -606,6 +607,7 @@ end ---@param bufnr? integer ---@return conform.FormatterInfo M.get_formatter_info = function(formatter, bufnr) + local util = require("conform.util") if not bufnr or bufnr == 0 then bufnr = vim.api.nvim_get_current_buf() end @@ -639,19 +641,21 @@ M.get_formatter_info = function(formatter, bufnr) local command = config.command if type(command) == "function" then - command = command(ctx) + command = util.compat_call_with_self(formatter, config, command, ctx) end if vim.fn.executable(command) == 0 then available = false available_msg = "Command not found" - elseif config.condition and not config.condition(ctx) then + elseif + config.condition and not util.compat_call_with_self(formatter, config, config.condition, ctx) + then available = false available_msg = "Condition failed" end local cwd = nil if config.cwd then - cwd = config.cwd(ctx) + cwd = util.compat_call_with_self(formatter, config, config.cwd, ctx) if available and not cwd and config.require_cwd then available = false available_msg = "Root directory not found" diff --git a/lua/conform/runner.lua b/lua/conform/runner.lua index 9571588..8137ad1 100644 --- a/lua/conform/runner.lua +++ b/lua/conform/runner.lua @@ -8,25 +8,26 @@ local M = {} ---@class (exact) conform.RunOpts ---@field exclusive boolean If true, ensure only a single formatter is running per buffer +---@param formatter_name string ---@param ctx conform.Context ---@param config conform.JobFormatterConfig ---@return string|string[] -M.build_cmd = function(ctx, config) +M.build_cmd = function(formatter_name, ctx, config) local command = config.command if type(command) == "function" then - command = command(ctx) + command = util.compat_call_with_self(formatter_name, config, command, ctx) end ---@type string|string[] local args = {} if ctx.range and config.range_args then - ---@cast ctx conform.RangeContext - args = config.range_args(ctx) + args = util.compat_call_with_self(formatter_name, config, config.range_args, ctx) elseif config.args then - if type(config.args) == "function" then - args = config.args(ctx) + local computed_args = config.args + if type(computed_args) == "function" then + args = util.compat_call_with_self(formatter_name, config, computed_args, ctx) else ---@diagnostic disable-next-line: cast-local-type - args = config.args + args = computed_args end end @@ -262,14 +263,14 @@ local function run_formatter(bufnr, formatter, config, ctx, input_lines, opts, c return end ---@cast config conform.JobFormatterConfig - local cmd = M.build_cmd(ctx, config) + local cmd = M.build_cmd(formatter.name, ctx, config) local cwd = nil if config.cwd then - cwd = config.cwd(ctx) + cwd = util.compat_call_with_self(formatter.name, config, config.cwd, ctx) end local env = config.env if type(env) == "function" then - env = env(ctx) + env = util.compat_call_with_self(formatter.name, config, env, ctx) end local buffer_text diff --git a/lua/conform/util.lua b/lua/conform/util.lua index bbb711a..0ff54de 100644 --- a/lua/conform/util.lua +++ b/lua/conform/util.lua @@ -10,11 +10,11 @@ end ---Search parent directories for a relative path to a command ---@param paths string[] ---@param default string ----@return fun(ctx: conform.Context): string +---@return fun(self: conform.FormatterConfig, ctx: conform.Context): string ---@example --- local cmd = require("conform.util").find_executable({ "node_modules/.bin/prettier" }, "prettier") M.find_executable = function(paths, default) - return function(ctx) + return function(self, ctx) for _, path in ipairs(paths) do local normpath = vim.fs.normalize(path) local is_absolute = vim.startswith(normpath, "/") @@ -46,9 +46,9 @@ M.find_executable = function(paths, default) end ---@param files string|string[] ----@return fun(ctx: conform.Context): nil|string +---@return fun(self: conform.FormatterConfig, ctx: conform.Context): nil|string M.root_file = function(files) - return function(ctx) + return function(self, ctx) local found = vim.fs.find(files, { upward = true, path = ctx.dirname })[1] if found then return vim.fs.dirname(found) @@ -101,8 +101,8 @@ M.wrap_callback = function(cb, wrapper) end ---Helper function to add to the default args of a formatter. ----@param args string|string[]|fun(ctx: conform.Context): string|string[] ----@param extra_args string|string[]|fun(ctx: conform.Context): string|string[] +---@param args string|string[]|fun(self: conform.FormatterConfig, ctx: conform.Context): string|string[] +---@param extra_args string|string[]|fun(self: conform.FormatterConfig, ctx: conform.Context): string|string[] ---@param opts? { append?: boolean } ---@example --- local util = require("conform.util") @@ -113,12 +113,12 @@ end --- }) M.extend_args = function(args, extra_args, opts) opts = opts or {} - return function(ctx) + return function(self, ctx) if type(args) == "function" then - args = args(ctx) + args = M.compat_call_with_self("unknown", self, args, ctx) end if type(extra_args) == "function" then - extra_args = extra_args(ctx) + extra_args = M.compat_call_with_self("unknown", self, extra_args, ctx) end if type(args) == "string" then if type(extra_args) ~= "string" then @@ -143,7 +143,7 @@ M.extend_args = function(args, extra_args, opts) end ---@param formatter conform.FormatterConfig ----@param extra_args string|string[]|fun(ctx: conform.Context): string|string[] +---@param extra_args string|string[]|fun(self: conform.FormatterConfig, ctx: conform.Context): string|string[] ---@param opts? { append?: boolean } ---@example --- local util = require("conform.util") @@ -183,4 +183,46 @@ M.buf_get_changedtick = function(bufnr) end end +---Returns true if the function takes no args or has self as the first arg +---@param name string +---@param fn function(...: any): T +---@return boolean +local function has_self_arg(name, fn) + local first_arg_name = nil + debug.sethook(function() + local info = debug.getinfo(3) + if info.name ~= "pcall" then + return + end + first_arg_name = debug.getlocal(2, 1) + error() + end, "c") + pcall(fn) + debug.sethook() + + return first_arg_name == "self" or first_arg_name == nil +end + +---@generic T +---@param formatter_name string +---@param self any +---@param fn fun(...: any): T +---@param ... any +---@return T +M.compat_call_with_self = function(formatter_name, self, fn, ...) + local has_self = has_self_arg(formatter_name, fn) + if has_self then + return fn(self, ...) + else + vim.notify_once( + string.format( + "[conform] formatter %s should take 'self' as the first argument for args, range_args, cwd, condition, and env functions (see :help conform-self-args)\nCompatibility will be dropped on 2024-03-01", + formatter_name + ), + vim.log.levels.WARN + ) + return fn(...) + end +end + return M diff --git a/scripts/generate.py b/scripts/generate.py index f9db2a2..b4da7b4 100755 --- a/scripts/generate.py +++ b/scripts/generate.py @@ -141,17 +141,41 @@ def gen_options_vimdoc() -> VimdocSection: return section -def gen_autocmd_vimdoc() -> VimdocSection: - section = VimdocSection("Autoformat", "conform-autoformat", ["\n"]) +def gen_self_compat_vimdoc() -> VimdocSection: + section = VimdocSection("self argument migration", "conform-self-args", ["\n"]) section.body.extend( wrap( - "If you want more complex logic than the `format_on_save` option allows, you can write it yourself using your own autocmd. For example:" + "The function arguments for formatter config functions have changed. Previously, they took a single `ctx` argument." + ) + ) + section.body.append( + """>lua + { + command = "phpcbf", + args = function(ctx) + return { "-q", "--stdin-path=" .. ctx.filename, "-" } + end + } +<""" + ) + section.body.extend( + wrap("Now, they take `self` as the first argument, and `ctx` as the second.") + ) + section.body.append( + """>lua + { + command = "phpcbf", + args = function(self, ctx) + return { "-q", "--stdin-path=" .. ctx.filename, "-" } + end + } +<""" + ) + section.body.extend( + wrap( + "The config values that can be defined as functions are: `command`, `args`, `range_args`, `cwd`, `env`, and `condition`." ) ) - section.body.append(">lua\n") - with open(AUTOFORMAT, "r", encoding="utf-8") as f: - section.body.extend(indent(f.readlines(), 4)) - section.body.append("<\n") return section @@ -171,7 +195,7 @@ def generate_vimdoc(): gen_options_vimdoc(), VimdocSection("API", "conform-api", render_vimdoc_api("conform", funcs)), gen_formatter_vimdoc(), - gen_autocmd_vimdoc(), + gen_self_compat_vimdoc(), ] ) diff --git a/tests/runner_spec.lua b/tests/runner_spec.lua index 020afac..c9cf75a 100644 --- a/tests/runner_spec.lua +++ b/tests/runner_spec.lua @@ -70,7 +70,7 @@ describe("runner", function() } local config = assert(conform.get_formatter_config("test")) local ctx = runner.build_context(0, config) - local cmd = runner.build_cmd(ctx, config) + local cmd = runner.build_cmd("", ctx, config) assert.are.same({ "echo", vim.api.nvim_buf_get_name(bufnr) }, cmd) end) @@ -84,7 +84,7 @@ describe("runner", function() } local config = assert(conform.get_formatter_config("test")) local ctx = runner.build_context(0, config) - local cmd = runner.build_cmd(ctx, config) + local cmd = runner.build_cmd("", ctx, config) assert.are.same({ "echo", vim.fs.dirname(vim.api.nvim_buf_get_name(bufnr)) }, cmd) end) @@ -99,7 +99,7 @@ describe("runner", function() } local config = assert(conform.get_formatter_config("test")) local ctx = runner.build_context(0, config) - local cmd = runner.build_cmd(ctx, config) + local cmd = runner.build_cmd("", ctx, config) assert.are.same({ "echo", "--stdin" }, cmd) end) @@ -113,7 +113,7 @@ describe("runner", function() } local config = assert(conform.get_formatter_config("test")) local ctx = runner.build_context(0, config) - local cmd = runner.build_cmd(ctx, config) + local cmd = runner.build_cmd("", ctx, config) assert.equal("echo " .. vim.api.nvim_buf_get_name(bufnr) .. " | patch", cmd) end) @@ -127,7 +127,7 @@ describe("runner", function() } local config = assert(conform.get_formatter_config("test")) local ctx = runner.build_context(0, config) - local cmd = runner.build_cmd(ctx, config) + local cmd = runner.build_cmd("", ctx, config) assert.equal("echo " .. vim.fs.dirname(vim.api.nvim_buf_get_name(bufnr)) .. " | patch", cmd) end) @@ -142,7 +142,7 @@ describe("runner", function() } local config = assert(conform.get_formatter_config("test")) local ctx = runner.build_context(0, config) - local cmd = runner.build_cmd(ctx, config) + local cmd = runner.build_cmd("", ctx, config) assert.equal("echo | patch", cmd) end) end) |