From 1abbb82bb8e519e652d8b31b12a311872e9090d1 Mon Sep 17 00:00:00 2001 From: Steven Arcangeli <506791+stevearc@users.noreply.github.com> Date: Sun, 17 Sep 2023 10:02:10 -0700 Subject: feat: make lsp_fallback behavior more intuitive (#59) When lsp_fallback = true AND the only formatters for the buffer are from the "*" or "_" filetype, format with LSP instead of the "*"/"_" formatters. --- README.md | 17 ++++++++---- doc/conform.txt | 13 +++++---- lua/conform/init.lua | 71 +++++++++++++++++++++++++++++++++++-------------- scripts/options_doc.lua | 7 ++--- 4 files changed, 73 insertions(+), 35 deletions(-) diff --git a/README.md b/README.md index eceb89d..fb2a923 100644 --- a/README.md +++ b/README.md @@ -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) @@ -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. @@ -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 | ## Acknowledgements diff --git a/doc/conform.txt b/doc/conform.txt index bb3d90d..76b0714 100644 --- a/doc/conform.txt +++ b/doc/conform.txt @@ -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. @@ -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* diff --git a/lua/conform/init.lua b/lua/conform/init.lua index aadec4d..7c81e1e 100644 --- a/lua/conform/init.lua +++ b/lua/conform/init.lua @@ -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[] @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 {}, { diff --git a/scripts/options_doc.lua b/scripts/options_doc.lua index bce6908..b68614e 100644 --- a/scripts/options_doc.lua +++ b/scripts/options_doc.lua @@ -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. -- cgit v1.2.3-70-g09d2