From 659838ff4244ef6af095395ce68aaaf99fa8e696 Mon Sep 17 00:00:00 2001 From: Steven Arcangeli <506791+stevearc@users.noreply.github.com> Date: Wed, 6 Dec 2023 22:20:29 -0800 Subject: refactor!: formatter config functions take self as first argument (#233) This is a breaking API change, but there is a shim in place that will keep existing functions working, just with a warning notification. Most people should not encounter this at all. For anyone overriding a formatter config value with a function that takes `(ctx)` as the parameter, it will need to be updated to take `(self, ctx)`. --- lua/conform/formatters/autopep8.lua | 2 +- lua/conform/formatters/clang_format.lua | 2 +- lua/conform/formatters/darker.lua | 2 +- lua/conform/formatters/deno_fmt.lua | 2 +- lua/conform/formatters/markdown-toc.lua | 2 +- lua/conform/formatters/phpcbf.lua | 2 +- lua/conform/formatters/prettier.lua | 2 +- lua/conform/formatters/prettierd.lua | 2 +- lua/conform/formatters/stylua.lua | 2 +- lua/conform/formatters/uncrustify.lua | 2 +- lua/conform/formatters/yapf.lua | 2 +- lua/conform/formatters/zprint.lua | 2 +- lua/conform/init.lua | 26 ++++++++------ lua/conform/runner.lua | 21 +++++------ lua/conform/util.lua | 62 +++++++++++++++++++++++++++------ 15 files changed, 90 insertions(+), 43 deletions(-) (limited to 'lua/conform') 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|fun(ctx: conform.Context): table +---@field env? table|fun(self: conform.JobFormatterConfig, ctx: conform.Context): table +---@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 -- cgit v1.2.3-70-g09d2