diff --git a/lua/lazyvim/config/keymaps.lua b/lua/lazyvim/config/keymaps.lua index b60aa43e..ced9cdf7 100644 --- a/lua/lazyvim/config/keymaps.lua +++ b/lua/lazyvim/config/keymaps.lua @@ -95,13 +95,14 @@ end -- formatting map({ "n", "v" }, "cf", function() - require("lazyvim.plugins.lsp.format").format({ force = true }) + Util.format({ force = true }) end, { desc = "Format" }) -- stylua: ignore start -- toggle options -map("n", "uf", require("lazyvim.plugins.lsp.format").toggle, { desc = "Toggle format on Save" }) +map("n", "uf", function() Util.format.toggle() end, { desc = "Toggle auto format (global)" }) +map("n", "uF", function() Util.format.toggle(true) end, { desc = "Toggle auto format (buffer)" }) map("n", "us", function() Util.toggle("spell") end, { desc = "Toggle Spelling" }) map("n", "uw", function() Util.toggle("wrap") end, { desc = "Toggle Word Wrap" }) map("n", "uL", function() Util.toggle("relativenumber") end, { desc = "Toggle Relative Line Numbers" }) diff --git a/lua/lazyvim/config/options.lua b/lua/lazyvim/config/options.lua index e2ae9485..1649896b 100644 --- a/lua/lazyvim/config/options.lua +++ b/lua/lazyvim/config/options.lua @@ -2,6 +2,9 @@ vim.g.mapleader = " " vim.g.maplocalleader = "\\" +-- Enable LazyVim auto format +vim.g.autoformat = true + local opt = vim.opt opt.autowrite = true -- Enable auto write diff --git a/lua/lazyvim/plugins/extras/formatting/conform.lua b/lua/lazyvim/plugins/extras/formatting/conform.lua index 601967c5..855e356e 100644 --- a/lua/lazyvim/plugins/extras/formatting/conform.lua +++ b/lua/lazyvim/plugins/extras/formatting/conform.lua @@ -18,9 +18,20 @@ return { vim.o.formatexpr = "v:lua.require'conform'.formatexpr()" -- Install the conform formatter on VeryLazy require("lazyvim.util").on_very_lazy(function() - require("lazyvim.plugins.lsp.format").custom_format = function(buf) - return require("conform").format({ bufnr = buf }) - end + require("lazyvim.util").format.register({ + name = "conform.nvim", + priority = 100, + primary = true, + format = function(buf) + require("conform").format({ bufnr = buf }) + end, + sources = function(buf) + local ret = require("conform").list_formatters(buf) + return vim.tbl_map(function(v) + return v.name + end, ret) + end, + }) end) end, opts = { diff --git a/lua/lazyvim/plugins/extras/linting/eslint.lua b/lua/lazyvim/plugins/extras/linting/eslint.lua index 1fea3c8c..9537303d 100644 --- a/lua/lazyvim/plugins/extras/linting/eslint.lua +++ b/lua/lazyvim/plugins/extras/linting/eslint.lua @@ -14,22 +14,37 @@ return { }, setup = { eslint = function() - vim.api.nvim_create_autocmd("BufWritePre", { - callback = function(event) - if not require("lazyvim.plugins.lsp.format").enabled() then - -- exit early if autoformat is not enabled - return - end + local function get_client(buf) + return require("lazyvim.util").lsp.get_clients({ name = "eslint", bufnr = buf })[1] + end - local client = require("lazyvim.util").get_clients({ bufnr = event.buf, name = "eslint" })[1] + local formatter = require("lazyvim.util").lsp.formatter({ + name = "eslint: lsp", + primary = false, + priority = 200, + filter = "eslint", + }) + + -- Use EslintFixAll on Neovim < 0.10.0 + if not pcall(require, "vim.lsp._dynamic") then + formatter.name = "eslint: EslintFixAll" + formatter.sources = function(buf) + local client = get_client(buf) + return client and { "eslint" } or {} + end + formatter.format = function(buf) + local client = get_client(buf) if client then - local diag = vim.diagnostic.get(event.buf, { namespace = vim.lsp.diagnostic.get_namespace(client.id) }) + local diag = vim.diagnostic.get(buf, { namespace = vim.lsp.diagnostic.get_namespace(client.id) }) if #diag > 0 then vim.cmd("EslintFixAll") end end - end, - }) + end + end + + -- register the formatter with LazyVim + require("lazyvim.util").format.register(formatter) end, }, }, diff --git a/lua/lazyvim/plugins/lsp/format.lua b/lua/lazyvim/plugins/lsp/format.lua deleted file mode 100644 index f507a38a..00000000 --- a/lua/lazyvim/plugins/lsp/format.lua +++ /dev/null @@ -1,126 +0,0 @@ -local Util = require("lazy.core.util") - -local M = {} - ----@type PluginLspOpts -M.opts = nil - --- Allow plugins to set a custom formatter for a buffer. --- See the conform extra for an example. ----@type fun(bufnr:number):boolean -M.custom_format = nil - -function M.enabled() - return M.opts.autoformat -end - -function M.toggle() - if vim.b.autoformat == false then - vim.b.autoformat = nil - M.opts.autoformat = true - else - M.opts.autoformat = not M.opts.autoformat - end - if M.opts.autoformat then - Util.info("Enabled format on save", { title = "Format" }) - else - Util.warn("Disabled format on save", { title = "Format" }) - end -end - ----@param opts? {force?:boolean} -function M.format(opts) - local buf = vim.api.nvim_get_current_buf() - if vim.b.autoformat == false and not (opts and opts.force) then - return - end - - if - M.custom_format - and Util.try(function() - return M.custom_format(buf) - end, { msg = "Custom formatter failed" }) - then - return - end - - local formatters = M.get_formatters(buf) - local client_ids = vim.tbl_map(function(client) - return client.id - end, formatters.active) - - if #client_ids == 0 then - if opts and opts.force then - Util.warn("No formatter available", { title = "LazyVim" }) - end - return - end - - vim.lsp.buf.format(vim.tbl_deep_extend("force", { - bufnr = buf, - filter = function(client) - return vim.tbl_contains(client_ids, client.id) - end, - }, require("lazyvim.util").opts("nvim-lspconfig").format or {})) -end - --- Gets all lsp clients that support formatting. --- When a null-ls formatter is available for the current filetype, --- only null-ls formatters are returned. -function M.get_formatters(bufnr) - local ft = vim.bo[bufnr].filetype - -- check if we have any null-ls formatters for the current filetype - local null_ls = package.loaded["null-ls"] and require("null-ls.sources").get_available(ft, "NULL_LS_FORMATTING") or {} - - ---@class LazyVimFormatters - local ret = { - ---@type lsp.Client[] - active = {}, - ---@type lsp.Client[] - available = {}, - null_ls = null_ls, - } - - ---@type lsp.Client[] - local clients = require("lazyvim.util").get_clients({ bufnr = bufnr }) - for _, client in ipairs(clients) do - if M.supports_format(client) then - if (#null_ls > 0 and client.name == "null-ls") or #null_ls == 0 then - table.insert(ret.active, client) - else - table.insert(ret.available, client) - end - end - end - - return ret -end - --- Gets all lsp clients that support formatting --- and have not disabled it in their client config ----@param client lsp.Client -function M.supports_format(client) - if - client.config - and client.config.capabilities - and client.config.capabilities.documentFormattingProvider == false - then - return false - end - return client.supports_method("textDocument/formatting") or client.supports_method("textDocument/rangeFormatting") -end - ----@param opts PluginLspOpts -function M.setup(opts) - M.opts = opts - vim.api.nvim_create_autocmd("BufWritePre", { - group = vim.api.nvim_create_augroup("LazyVimFormat", {}), - callback = function() - if M.opts.autoformat then - M.format() - end - end, - }) -end - -return M diff --git a/lua/lazyvim/plugins/lsp/init.lua b/lua/lazyvim/plugins/lsp/init.lua index f18eed4f..b9e38e8b 100644 --- a/lua/lazyvim/plugins/lsp/init.lua +++ b/lua/lazyvim/plugins/lsp/init.lua @@ -35,8 +35,6 @@ return { }, -- add any global capabilities here capabilities = {}, - -- Automatically format on save - autoformat = true, -- options for vim.lsp.buf.format -- `bufnr` and `filter` is handled by the LazyVim formatter, -- but can be also overridden when specified @@ -80,16 +78,20 @@ return { }, ---@param opts PluginLspOpts config = function(_, opts) - local Util = require("lazyvim.util") - if Util.has("neoconf.nvim") then local plugin = require("lazy.core.config").spec.plugins["neoconf.nvim"] require("neoconf").setup(require("lazy.core.plugin").values(plugin, "opts", false)) end + -- setup autoformat - require("lazyvim.plugins.lsp.format").setup(opts) - -- setup formatting and keymaps - Util.on_attach(function(client, buffer) + Util.format.register(Util.lsp.formatter()) + + -- deprectaed options + if opts.autoformat ~= nil then + vim.g.autoformat = opts.autoformat + Util.deprecate("nvim-lspconfig.opts.autoformat", "vim.g.autoformat") + end + -- setup keymaps Util.lsp.on_attach(function(client, buffer) require("lazyvim.plugins.lsp.keymaps").on_attach(client, buffer) @@ -215,6 +217,30 @@ return { }, } end, + config = function(_, opts) + require("null-ls").setup(opts) + + -- register the formatter with LazyVim + require("lazyvim.util").format.register({ + name = "none-ls.nvim", + priority = 50, + primary = true, + format = function(buf) + return Util.lsp.format({ + bufnr = buf, + filter = function(client) + return client.name == "null-ls" + end, + }) + end, + sources = function(buf) + local ret = require("null-ls.sources").get_available(vim.bo[buf].filetype, "NULL_LS_FORMATTING") or {} + return vim.tbl_map(function(source) + return source.name + end, ret) + end, + }) + end, }, -- cmdline tools and lsp servers diff --git a/lua/lazyvim/util/format.lua b/lua/lazyvim/util/format.lua new file mode 100644 index 00000000..05f5f5fd --- /dev/null +++ b/lua/lazyvim/util/format.lua @@ -0,0 +1,147 @@ +local Util = require("lazyvim.util") + +---@class lazyvim.util.format +---@overload fun(opts?: {force?:boolean}) +local M = setmetatable({}, { + __call = function(m, ...) + return m.format(...) + end, +}) + +---@class LazyFormatter +---@field name string +---@field primary? boolean +---@field format fun(bufnr:number) +---@field sources fun(bufnr:number):string[] +---@field priority number + +M.formatters = {} ---@type LazyFormatter[] + +---@param formatter LazyFormatter +function M.register(formatter) + M.formatters[#M.formatters + 1] = formatter + table.sort(M.formatters, function(a, b) + return a.priority > b.priority + end) +end + +---@param buf? number +---@return (LazyFormatter|{active:boolean,resolved:string[]})[] +function M.resolve(buf) + buf = buf or vim.api.nvim_get_current_buf() + local have_primary = false + ---@param formatter LazyFormatter + return vim.tbl_map(function(formatter) + local sources = formatter.sources(buf) + local active = #sources > 0 and (not formatter.primary or not have_primary) + have_primary = have_primary or (active and formatter.primary) or false + return setmetatable({ + active = active, + resolved = sources, + }, { __index = formatter }) + end, M.formatters) +end + +---@param buf? number +function M.info(buf) + buf = buf or vim.api.nvim_get_current_buf() + local gaf = vim.g.autoformat == nil or vim.g.autoformat + local baf = vim.b[buf].autoformat + local enabled = M.enabled(buf) + local lines = { + "# Status", + ("- [%s] global **%s**"):format(gaf and "x" or " ", gaf and "enabled" or "disabled"), + ("- [%s] buffer **%s**"):format( + enabled and "x" or " ", + baf == nil and "inherit" or baf and "enabled" or "disabled" + ), + } + local have = false + for _, formatter in ipairs(M.resolve(buf)) do + if #formatter.resolved > 0 then + have = true + lines[#lines + 1] = "\n# " .. formatter.name .. (formatter.active and " ***(active)***" or "") + for _, line in ipairs(formatter.resolved) do + lines[#lines + 1] = ("- [%s] **%s**"):format(formatter.active and "x" or " ", line) + end + end + end + if not have then + lines = { "\nNo formatters available for this buffer" } + end + Util[enabled and "info" or "warn"]( + table.concat(lines, "\n"), + { title = "LazyFormat (" .. (enabled and "enabled" or "disabled") .. ")" } + ) +end + +---@param buf? number +function M.enabled(buf) + buf = (buf == nil or buf == 0) and vim.api.nvim_get_current_buf() or buf + local gaf = vim.g.autoformat + local baf = vim.b[buf].autoformat + + -- If the buffer has a local value, use that + if baf ~= nil then + return baf + end + + -- Otherwise use the global value if set, or true by default + return gaf == nil or gaf +end + +---@param buf? boolean +function M.toggle(buf) + if buf then + vim.b.autoformat = not M.enabled() + else + vim.g.autoformat = not M.enabled() + vim.b.autoformat = nil + end + M.info() +end + +---@param opts? {force?:boolean, buf?:number} +function M.format(opts) + opts = opts or {} + local buf = opts.buf or vim.api.nvim_get_current_buf() + if not ((opts and opts.force) or M.enabled(buf)) then + return + end + + local done = false + for _, formatter in ipairs(M.resolve(buf)) do + if formatter.active then + done = true + Util.try(function() + return formatter.format(buf) + end, { msg = "Formatter `" .. formatter.name .. "` failed" }) + end + end + + if not done and opts and opts.force then + Util.warn("No formatter available", { title = "LazyVim" }) + end +end + +function M.setup() + -- Autoformat autocmd + vim.api.nvim_create_autocmd("BufWritePre", { + group = vim.api.nvim_create_augroup("LazyFormat", {}), + callback = function(event) + M.format({ buf = event.buf }) + end, + }) + + -- Manual format + vim.api.nvim_create_user_command("LazyFormat", function() + M.format({ force = true }) + end, { desc = "Format selection or buffer" }) + + -- Format info + vim.api.nvim_create_user_command("LazyFormatInfo", function() + M.info() + end, { desc = "Show info about the formatters for the current buffer" }) +end + +return M