feat(format): new LazyVim formatter with integrations for lsp/none-ls/conform/eslint/...

This commit is contained in:
Folke Lemaitre 2023-10-10 19:31:08 +02:00
parent aa0e4f52e4
commit eac18246d4
No known key found for this signature in database
GPG key ID: 41F8B1FBACAE2040
7 changed files with 225 additions and 148 deletions

View file

@ -95,13 +95,14 @@ end
-- formatting
map({ "n", "v" }, "<leader>cf", function()
require("lazyvim.plugins.lsp.format").format({ force = true })
Util.format({ force = true })
end, { desc = "Format" })
-- stylua: ignore start
-- toggle options
map("n", "<leader>uf", require("lazyvim.plugins.lsp.format").toggle, { desc = "Toggle format on Save" })
map("n", "<leader>uf", function() Util.format.toggle() end, { desc = "Toggle auto format (global)" })
map("n", "<leader>uF", function() Util.format.toggle(true) end, { desc = "Toggle auto format (buffer)" })
map("n", "<leader>us", function() Util.toggle("spell") end, { desc = "Toggle Spelling" })
map("n", "<leader>uw", function() Util.toggle("wrap") end, { desc = "Toggle Word Wrap" })
map("n", "<leader>uL", function() Util.toggle("relativenumber") end, { desc = "Toggle Relative Line Numbers" })

View file

@ -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

View file

@ -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 = {

View file

@ -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
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,
},
},

View file

@ -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

View file

@ -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

147
lua/lazyvim/util/format.lua Normal file
View file

@ -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