Skip to content

Commit

Permalink
feat(formatters): defined a 'execute' option so a custom function can…
Browse files Browse the repository at this point in the history
… be used as a formatter
  • Loading branch information
brunotvs committed Feb 16, 2024
1 parent 61cff43 commit ff88979
Show file tree
Hide file tree
Showing 3 changed files with 88 additions and 4 deletions.
10 changes: 10 additions & 0 deletions doc/conform.txt
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,17 @@ OPTIONS *conform-option
my_formatter = {
-- This can be a string or a function that returns a string.
-- When defining a new formatter, this is the only field that is *required*
-- unless execute is defined.
command = "my_cmd",
-- This is an alternative to defining a command.
-- Useful for creating a custom lsp formatter.
-- Must be a function that will be called when this formatter is
-- used. When 'execute' is defined, this formatter will be the last to
-- be executed, regardless of the order it is defined.
---@param config table every other entry for this table
---@param opts table require('conform').format(...) options
---@param callback fun(err: nil|string, did_edit: nil|boolean) callback function passed to require('conform').format(...)
execute = function(config, opts, callback) ... end,
-- A list of strings, or a function that returns a list of strings
-- Return a single string instead of a list to run the command in a shell
args = { "--stdin-from-filename", "$FILENAME" },
Expand Down
17 changes: 17 additions & 0 deletions doc/recipes.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
- [Command to toggle format-on-save](#command-to-toggle-format-on-save)
- [Automatically run slow formatters async](#automatically-run-slow-formatters-async)
- [Lazy loading with lazy.nvim](#lazy-loading-with-lazynvim)
- [Custom formatter with execute](#custom-formatter-with-execute)

<!-- /TOC -->

Expand Down Expand Up @@ -178,3 +179,19 @@ return {
end,
}
```

## Custom formatter with execute

Define a formatter using 'execute' function for eslint lsp server

```lua
require("conform").formatters.eslint_lsp = {
name = 'eslint',
execute = function(config, opts, callback)
local options = { async = opts.async, name = config.name }

local lsp_format = require('conform.lsp_format')
lsp_format.format(options, callback)
end,
}
```
65 changes: 61 additions & 4 deletions lua/conform/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ local M = {}
---@field cwd? string
---@field available boolean
---@field available_msg? string
---@field execute? fun(args: any)

---@class (exact) conform.JobFormatterConfig
---@field command string|fun(self: conform.JobFormatterConfig, ctx: conform.Context): string
Expand All @@ -24,20 +25,26 @@ local M = {}
---@field condition? fun(self: conform.LuaFormatterConfig, ctx: conform.Context): boolean
---@field options? table

---@class (exact) conform.ExecuteFormatterConfig
---@field execute fun(self: conform.ExecuteFormatterConfig, opts: table, callback: fun(err: nil|string, new_lines: nil|string[]))
---@field condition? fun(self: conform.ExecuteFormatterConfig, ctx: conform.Context): boolean
---@field options? table

---@class (exact) conform.FileLuaFormatterConfig : conform.LuaFormatterConfig
---@field meta conform.FormatterMeta

---@class (exact) conform.FileFormatterConfig : conform.JobFormatterConfig
---@field meta conform.FormatterMeta

---@alias conform.FormatterConfig conform.JobFormatterConfig|conform.LuaFormatterConfig
---@alias conform.FormatterConfig conform.JobFormatterConfig|conform.LuaFormatterConfig|conform.ExecuteFormatterConfig

---@class (exact) conform.FormatterConfigOverride : conform.JobFormatterConfig
---@field inherit? boolean
---@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 format? fun(self: conform.LuaFormatterConfig, ctx: conform.Context, lines: string[], callback: fun(err: nil|string, new_lines: nil|string[])) Mutually exclusive with command
---@field options? table
---@field execute? fun(opts: table)

---@class (exact) conform.FormatterMeta
---@field url string
Expand Down Expand Up @@ -392,6 +399,14 @@ M.format = function(opts, callback)
local formatters =
M.resolve_formatters(formatter_names, opts.bufnr, not opts.quiet and explicit_formatters)

local executable_formatters = vim.tbl_filter(function(value)
return value["execute"] ~= nil
end, formatters)

formatters = vim.tbl_filter(function(value)
return value["execute"] == nil
end, formatters)

local any_formatters = not vim.tbl_isempty(formatters)
if not explicit_formatters and opts.lsp_fallback == true and M.will_fallback_lsp(opts) then
-- use the LSP formatter when the configured formatters are from the fallback "_" filetype
Expand Down Expand Up @@ -449,6 +464,14 @@ M.format = function(opts, callback)
runner.format_sync(opts.bufnr, formatters, opts.timeout_ms, opts.range, run_opts)
handle_result(err, did_edit)
end
for _, formatter in ipairs(executable_formatters) do
---@cast formatter conform.FormatterInfo
local config = M.get_formatter_config(formatter.name)

---@cast config conform.ExecuteFormatterConfig
---@cast formatter conform.ExecuteFormatterConfig
formatter.execute(config, opts, callback)
end
return true
elseif opts.lsp_fallback and not vim.tbl_isempty(lsp_format.get_format_clients(opts)) then
log.debug("Running LSP formatter on %s", vim.api.nvim_buf_get_name(opts.bufnr))
Expand Down Expand Up @@ -571,6 +594,7 @@ end
---@param bufnr? integer
---@return nil|conform.FormatterConfig
M.get_formatter_config = function(formatter, bufnr)
vim.print("getting formatter config")
if not bufnr or bufnr == 0 then
bufnr = vim.api.nvim_get_current_buf()
end
Expand All @@ -579,9 +603,27 @@ M.get_formatter_config = function(formatter, bufnr)
if type(override) == "function" then
override = override(bufnr)
end
if override and override.command and override.format then

local one_of = { "command", "format", "execute" }
---@param config conform.FormatterConfigOverride
---@return boolean
local valid = function(config)
local config_contains = {}
for _, value in ipairs(one_of) do
if config and config[value] ~= nil then
table.insert(config_contains, value)
end
end
return #config_contains == 1
end

if override then
vim.print(valid(override))
end

if override and not valid(override) then
local msg =
string.format("Formatter '%s' cannot define both 'command' and 'format' function", formatter)
string.format("Formatter '%s' must define only one of '%s'", formatter, vim.inspect(one_of))
vim.notify_once(msg, vim.log.levels.ERROR)
return nil
end
Expand All @@ -597,7 +639,7 @@ M.get_formatter_config = function(formatter, bufnr)
config = mod_config
end
elseif override then
if override.command or override.format then
if override.command or override.format or override.execute then
config = override
else
local msg = string.format(
Expand Down Expand Up @@ -655,6 +697,21 @@ M.get_formatter_info = function(formatter, bufnr)
}
end

if config.execute then
---@cast config conform.ExecuteFormatterConfig
if config.condition and not config:condition(ctx) then
available = false
available_msg = "Condition failed"
end
return {
name = formatter,
command = formatter,
available = available,
available_msg = available_msg,
execute = config.execute,
}
end

local command = config.command
if type(command) == "function" then
command = util.compat_call_with_self(formatter, config, command, ctx)
Expand Down

0 comments on commit ff88979

Please sign in to comment.