Skip to content

Commit

Permalink
feat: make lsp_fallback behavior more intuitive (#59)
Browse files Browse the repository at this point in the history
When lsp_fallback = true AND the only formatters for the buffer are from
the "*" or "_" filetype, format with LSP instead of the "*"/"_"
formatters.
  • Loading branch information
stevearc authored Sep 17, 2023
1 parent 6d74e6c commit 1abbb82
Show file tree
Hide file tree
Showing 4 changed files with 73 additions and 35 deletions.
17 changes: 12 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ Lightweight yet powerful formatter plugin for Neovim
- [list_formatters(bufnr)](#list_formattersbufnr)
- [list_all_formatters()](#list_all_formatters)
- [get_formatter_info(formatter, bufnr)](#get_formatter_infoformatter-bufnr)
- [will_fallback_lsp(options)](#will_fallback_lspoptions)
- [Acknowledgements](#acknowledgements)

<!-- /TOC -->
Expand Down Expand Up @@ -241,12 +242,9 @@ require("conform").setup({
-- Use a sub-list to run only the first available formatter
javascript = { { "prettierd", "prettier" } },
-- Use the "*" filetype to run formatters on all filetypes.
-- Note that if you use this, you may want to set lsp_fallback = "always"
-- (see :help conform.format)
["*"] = { "codespell" },
-- Use the "_" filetype to run formatters on all filetypes
-- that don't have other formatters configured. Again, you may want to
-- set lsp_fallback = "always" when using this value.
-- Use the "_" filetype to run formatters on filetypes that don't
-- have other formatters configured.
["_"] = { "trim_whitespace" },
},
-- If this is set, Conform will run the formatter on save.
Expand Down Expand Up @@ -384,6 +382,15 @@ Get information about a formatter (including availability)
| --------- | -------------- | ------------------------- |
| formatter | `string` | The name of the formatter |
| bufnr | `nil\|integer` | |

### will_fallback_lsp(options)

`will_fallback_lsp(options): boolean` \
Check if the buffer will use LSP formatting when lsp_fallback = true

| Param | Type | Desc |
| ------- | ------------ | ------------------------------------ |
| options | `nil\|table` | Options passed to vim.lsp.buf.format |
<!-- /API -->

## Acknowledgements
Expand Down
13 changes: 8 additions & 5 deletions doc/conform.txt
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,9 @@ OPTIONS *conform-option
-- Use a sub-list to run only the first available formatter
javascript = { { "prettierd", "prettier" } },
-- Use the "*" filetype to run formatters on all filetypes.
-- Note that if you use this, you may want to set lsp_fallback = "always"
-- (see :help conform.format)
["*"] = { "codespell" },
-- Use the "_" filetype to run formatters on all filetypes
-- that don't have other formatters configured. Again, you may want to
-- set lsp_fallback = "always" when using this value.
-- Use the "_" filetype to run formatters on filetypes that don't
-- have other formatters configured.
["_"] = { "trim_whitespace" },
},
-- If this is set, Conform will run the formatter on save.
Expand Down Expand Up @@ -149,6 +146,12 @@ get_formatter_info({formatter}, {bufnr}): conform.FormatterInfo *conform.get_for
{formatter} `string` The name of the formatter
{bufnr} `nil|integer`

will_fallback_lsp({options}): boolean *conform.will_fallback_lsp*
Check if the buffer will use LSP formatting when lsp_fallback = true

Parameters:
{options} `nil|table` Options passed to |vim.lsp.buf.format|

--------------------------------------------------------------------------------
FORMATTERS *conform-formatters*

Expand Down
71 changes: 51 additions & 20 deletions lua/conform/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,24 @@ M.setup = function(opts)
end, { desc = "Show information about Conform formatters" })
end

---Get the configured formatter filetype for a buffer
---@param bufnr? integer
---@return nil|string filetype or nil if no formatter is configured
local function get_matching_filetype(bufnr)
if not bufnr or bufnr == 0 then
bufnr = vim.api.nvim_get_current_buf()
end
local filetypes = vim.split(vim.bo[bufnr].filetype, ".", { plain = true })
table.insert(filetypes, "_")
for _, filetype in ipairs(filetypes) do
---@type conform.FormatterUnit[]
local ft_formatters = M.formatters_by_ft[filetype]
if ft_formatters then
return filetype
end
end
end

---@private
---@param bufnr? integer
---@return conform.FormatterUnit[]
Expand All @@ -154,7 +172,6 @@ M.list_formatters_for_buffer = function(bufnr)
end
local formatters = {}
local seen = {}
local filetypes = vim.split(vim.bo[bufnr].filetype, ".", { plain = true })

local function dedupe_formatters(names, collect)
for _, name in ipairs(names) do
Expand All @@ -171,10 +188,15 @@ M.list_formatters_for_buffer = function(bufnr)
end
end

table.insert(filetypes, "_")
for _, filetype in ipairs(filetypes) do
local filetypes = {}
local matching_filetype = get_matching_filetype(bufnr)
if matching_filetype then
table.insert(filetypes, matching_filetype)
end
table.insert(filetypes, "*")
for _, ft in ipairs(filetypes) do
---@type conform.FormatterUnit[]
local ft_formatters = M.formatters_by_ft[filetype]
local ft_formatters = M.formatters_by_ft[ft]
if ft_formatters then
-- support the old structure where formatters could be a subkey
if not vim.tbl_islist(ft_formatters) then
Expand All @@ -183,20 +205,9 @@ M.list_formatters_for_buffer = function(bufnr)
end

dedupe_formatters(ft_formatters, formatters)
break
end
end

local ft_formatters = M.formatters_by_ft["*"]
if ft_formatters then
-- support the old structure where formatters could be a subkey
if not vim.tbl_islist(ft_formatters) then
---@diagnostic disable-next-line: undefined-field
ft_formatters = ft_formatters.formatters
end
dedupe_formatters(ft_formatters, formatters)
end

return formatters
end

Expand Down Expand Up @@ -294,17 +305,23 @@ 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 any_formatters_configured = formatter_names ~= nil and not vim.tbl_isempty(formatter_names)
local formatters =
resolve_formatters(formatter_names, opts.bufnr, not opts.quiet and opts.formatters ~= nil)

local resolved_names = vim.tbl_map(function(f)
return f.name
end, formatters)
log.debug("Running formatters on %s: %s", vim.api.nvim_buf_get_name(opts.bufnr), resolved_names)

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
any_formatters = false
else
local resolved_names = vim.tbl_map(function(f)
return f.name
end, formatters)
log.debug("Running formatters on %s: %s", vim.api.nvim_buf_get_name(opts.bufnr), resolved_names)
end

if any_formatters then
local mode = vim.api.nvim_get_mode().mode
if not opts.range and mode == "v" or mode == "V" then
Expand Down Expand Up @@ -492,6 +509,20 @@ M.get_formatter_info = function(formatter, bufnr)
}
end

---Check if the buffer will use LSP formatting when lsp_fallback = true
---@param options? table Options passed to |vim.lsp.buf.format|
---@return boolean
M.will_fallback_lsp = function(options)
options = options or {}
if not options.bufnr or options.bufnr == 0 then
options.bufnr = vim.api.nvim_get_current_buf()
end
local matching_filetype = get_matching_filetype(options.bufnr)
local has_primary_formatters = matching_filetype and matching_filetype ~= "_"
local lsp_clients = require("conform.lsp_format").get_format_clients(options)
return not has_primary_formatters and not vim.tbl_isempty(lsp_clients)
end

M.formatexpr = function(opts)
-- Change the defaults slightly from conform.format
opts = vim.tbl_deep_extend("keep", opts or {}, {
Expand Down
7 changes: 2 additions & 5 deletions scripts/options_doc.lua
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,9 @@ require("conform").setup({
-- Use a sub-list to run only the first available formatter
javascript = { { "prettierd", "prettier" } },
-- Use the "*" filetype to run formatters on all filetypes.
-- Note that if you use this, you may want to set lsp_fallback = "always"
-- (see :help conform.format)
["*"] = { "codespell" },
-- Use the "_" filetype to run formatters on all filetypes
-- that don't have other formatters configured. Again, you may want to
-- set lsp_fallback = "always" when using this value.
-- Use the "_" filetype to run formatters on filetypes that don't
-- have other formatters configured.
["_"] = { "trim_whitespace" },
},
-- If this is set, Conform will run the formatter on save.
Expand Down

0 comments on commit 1abbb82

Please sign in to comment.