Compare commits

...
1 Commits
Author SHA1 Message Date
DarthMooMancer 132a3d0630 REFACTOR: Updated from older config 2026-10-10 05:54:27 -04:00
9 changed files with 444 additions and 84 deletions
+7
View File
@@ -0,0 +1,7 @@
column_width = 120
line_endings = "Unix"
indent_type = "Spaces"
indent_width = 4
quote_style = "AutoPreferDouble"
call_parentheses = "Always"
collapse_simple_statement = "Never"
+46
View File
@@ -2,8 +2,16 @@
Automatically install formatters registered with [conform.nvim](https://github.com/stevearc/conform.nvim) via [Mason](https://github.com/williamboman/mason.nvim).
# Requirements
- Neovim 0.11+
- [mason.nvim](https://github.com/mason-org/mason.nvim) 2.x
- [conform.nvim](https://github.com/stevearc/conform.nvim) (9.x or newer)
# Install
With [lazy.nvim](https://github.com/folke/lazy.nvim):
```lua
require("lazy").setup({
"williamboman/mason.nvim",
@@ -12,6 +20,16 @@ require("lazy").setup({
})
```
With Neovim's built-in package manager (`:h vim.pack`, Neovim 0.12+):
```lua
vim.pack.add({
{ src = "https://github.com/mason-org/mason.nvim" },
{ src = "https://github.com/stevearc/conform.nvim" },
{ src = "https://github.com/zapling/mason-conform.nvim" },
})
```
# Setup
It's crucial to setup plugins in the following order:
@@ -31,12 +49,40 @@ require("mason-conform").setup({
})
```
All the `formatters_by_ft` shapes supported by conform are understood:
```lua
require("conform").setup({
formatters_by_ft = {
-- Plain list
lua = { "stylua" },
-- List with inline format options (`lsp_format`, `stop_after_first`, ...)
python = { "autopep8", lsp_format = "fallback" },
-- Legacy nested list ("run the first available formatter")
markdown = { { "prettierd", "prettier" }, "cbfmt" },
-- Function returning a list (conform "first available" recipe)
javascript = function(bufnr)
return { require("conform").get_formatter_info("prettier", bufnr).available and "prettier" or "prettierd" }
end,
},
})
```
Functions are evaluated without a buffer so that every candidate formatter can
be discovered and installed. A function that requires a buffer is skipped
instead of raising an error.
# Available formatters
Only formatters that are available in the [mason registry](https://github.com/mason-org/mason-registry)
can be downloaded automatically. If the formatter is available in the registry and it's not being
downloaded, this plugin might be missing a `conform` => `mason` mapping in the file [lua/mason-conform/mapping.lua](lua/mason-conform/mapping.lua).
# Healthcheck
Run `:checkhealth mason-conform` to verify that Neovim, `mason.nvim` and `conform.nvim` are set up
correctly, and to see which configured formatters cannot be installed automatically.
# License
`mason-conform.nvim` is a blatant copy of [mason-nvim-lint](https://github.com/rshkarin/mason-nvim-lint)
+14 -31
View File
@@ -1,42 +1,25 @@
local registry = require("mason-registry")
local mapping = require("mason-conform.mapping")
local function auto_install()
local config = require("mason-conform").config
local formatters_by_ft = require("conform").formatters_by_ft;
local mapping = require("mason-conform.mapping")
local install = require("mason-conform.install")
local formatters_to_install = {}
for _, formatters in pairs(formatters_by_ft) do
for _, formatter in pairs(formatters) do
-- Support case where the user has defined multiple formatters
-- for said filetype. E.g javascript = { { "prettierd", "prettier" } }
if type(formatter) == "table" then
for _, f in pairs(formatter) do
formatters_to_install[f] = 1
end
else
formatters_to_install[formatter] = 1
end
end
local ignored = {}
for _, name in ipairs(config.ignore_install) do
ignored[name] = true
end
-- Filter out formatters that the user wants to ignore
for _, formatter_to_ignore in pairs(config.ignore_install) do
formatters_to_install[formatter_to_ignore] = nil
end
for conformFormatter, _ in pairs(formatters_to_install) do
local package = mapping.conform_to_package[conformFormatter]
for _, name in ipairs(require("mason-conform.formatters").configured()) do
if not ignored[name] then
local package = mapping.conform_to_package[name]
if package ~= nil then
require("mason-conform.install").try_install(package)
install.try_install(package)
end
end
end
end
if registry.refresh then
return function()
registry.refresh(vim.schedule_wrap(auto_install))
end
else
return auto_install
---Refresh the mason registry, then install every configured formatter that is
---missing. Returns immediately; installation happens on the main loop.
return function()
require("mason-registry").refresh(vim.schedule_wrap(auto_install))
end
+63
View File
@@ -0,0 +1,63 @@
local M = {}
---Collect formatter names from a single `formatters_by_ft` entry.
---
---A conform filetype formatter can be any of:
--- * a list of formatter names, optionally followed by string-keyed format
--- options (e.g. `{ "stylua", lsp_format = "fallback" }`)
--- * a function `fun(bufnr): string[]`, used by the "run the first available
--- formatter" recipe
--- * the legacy nested list `{ { "prettierd", "prettier" } }`
---
---Only the array part of the table is traversed, so format options such as
---`lsp_format` and `stop_after_first` are ignored.
---@param value any
---@param out table<string, boolean>
local function collect_entry(value, out)
local list = value
if type(list) == "function" then
-- A function may depend on a buffer and/or on run-time availability.
-- We don't have a buffer to pass here, so evaluate it best-effort and
-- silently skip it when it cannot be called without one.
local ok, result = pcall(list)
if not ok or type(result) ~= "table" then
return
end
list = result
end
if type(list) ~= "table" then
return
end
for _, formatter in ipairs(list) do
if type(formatter) == "table" then
-- Legacy "run the first available formatter" syntax.
collect_entry(formatter, out)
elseif type(formatter) == "string" then
out[formatter] = true
end
end
end
---List every formatter configured in conform's `formatters_by_ft`.
---
---Understands all `formatters_by_ft` shapes supported by conform, including
---functions returning a list and the legacy nested "run the first available"
---syntax.
---@return string[] names Sorted, de-duplicated formatter names.
function M.configured()
local out = {}
for _, entry in pairs(require("conform").formatters_by_ft or {}) do
collect_entry(entry, out)
end
local names = {}
for name in pairs(out) do
names[#names + 1] = name
end
table.sort(names)
return names
end
return M
+76
View File
@@ -0,0 +1,76 @@
local M = {}
---@return boolean
local function check_mason()
local ok, mason_version = pcall(require, "mason.version")
if not ok then
vim.health.error("mason.nvim was not found. Is it installed and loaded?")
return false
end
if mason_version.MAJOR_VERSION ~= 2 then
vim.health.error(("mason.nvim v2 is required (found v%d)."):format(mason_version.MAJOR_VERSION))
return false
end
vim.health.ok("mason.nvim v2")
return true
end
function M.check()
vim.health.start("mason-conform.nvim")
if vim.fn.has("nvim-0.11") == 1 then
vim.health.ok("Neovim v0.11+")
else
vim.health.error("Neovim v0.11 or higher is required.")
end
local mason_installed = check_mason()
if mason_installed then
local registry_ok = pcall(require, "mason-registry")
if registry_ok then
vim.health.ok("mason registry is accessible")
else
vim.health.error("mason-registry could not be loaded.")
end
end
local conform_ok = pcall(require, "conform")
if not conform_ok then
vim.health.error("conform.nvim was not found. Is it installed and loaded?")
return
end
vim.health.ok("conform.nvim")
local formatters = require("mason-conform.formatters").configured()
if #formatters == 0 then
vim.health.warn("No formatters are configured in conform's formatters_by_ft, so there is nothing to install.")
return
end
vim.health.ok(("%d formatter(s) configured in formatters_by_ft"):format(#formatters))
local mapping = require("mason-conform.mapping")
local unmapped = {}
for _, name in ipairs(formatters) do
if mapping.conform_to_package[name] == nil then
unmapped[#unmapped + 1] = name
end
end
if #unmapped > 0 then
vim.health.info(
("No mason package mapping for: %s. These will not be installed automatically."):format(
table.concat(unmapped, ", ")
)
)
end
local ignored = {}
for _, name in ipairs(require("mason-conform").config.ignore_install) do
ignored[#ignored + 1] = name
end
if #ignored > 0 then
vim.health.info(("Ignored by 'ignore_install': %s"):format(table.concat(ignored, ", ")))
end
end
return M
+10 -5
View File
@@ -1,13 +1,18 @@
local M = {}
---@class MasonConformConfig
---@field ignore_install string[] Formatters that should never be installed automatically.
---@type MasonConformConfig
M.config = {
ignore_install = {}
ignore_install = {},
}
function M.setup(cfg)
if cfg and cfg.ignore_install then
M.config.ignore_install = cfg.ignore_install
end
---@param opts? MasonConformConfig Options overriding the defaults.
function M.setup(opts)
vim.validate("opts", opts, "table", true)
M.config = vim.tbl_deep_extend("force", { ignore_install = {} }, opts or {})
require("mason-conform.auto_install")()
end
+23 -37
View File
@@ -1,31 +1,19 @@
local registry = require "mason-registry"
local M = {}
local function resolve_package(mason_package_name)
local Optional = require "mason-core.optional"
local ok, pkg = pcall(registry.get_package, mason_package_name)
if ok then
return Optional.of_nilable(pkg)
end
end
---@param pkg Package The mason package to install.
---@param version? string Pin the installation to a specific version.
local function install_package(pkg, version)
local linter_name = pkg.name
vim.notify(("[mason-conform] installing %s"):format(pkg.name))
vim.notify(("[mason-conform] installing %s"):format(linter_name))
return pkg:install({ version = version }):once(
pkg:install({ version = version }):once(
"closed",
vim.schedule_wrap(function()
if pkg:is_installed() then
vim.notify(("[mason-conform] %s was successfully installed"):format(linter_name))
vim.notify(("[mason-conform] %s was successfully installed"):format(pkg.name))
else
vim.notify(
("[mason-conform] failed to install %s. Installation logs are available in :Mason and :MasonLog")
:format(
linter_name
("[mason-conform] failed to install %s. Installation logs are available in :Mason and :MasonLog"):format(
pkg.name
),
vim.log.levels.ERROR
)
@@ -34,32 +22,30 @@ local function install_package(pkg, version)
)
end
---Install the mason package if it exists in the registry and is not already
---installed or installing.
---@param mason_package_name string Mason package identifier, optionally `name@version`.
function M.try_install(mason_package_name)
local Package = require "mason-core.package"
local Package = require("mason-core.package")
local registry = require("mason-registry")
local package_name, version = Package.Parse(mason_package_name)
resolve_package(package_name)
:if_present(
function(pkg)
if not pkg:is_installed() then
if require("mason.version").MAJOR_VERSION == 2 then
if pkg:is_installing() then
return
end
end
install_package(pkg, version)
end
end
)
:if_not_present(function()
local ok, pkg = pcall(registry.get_package, package_name)
if not ok or pkg == nil then
vim.notify(
("[mason-conform] Formatter %q is not a valid entry in ensure_installed. Make sure to only provide valid formatter names.")
:format(
("[mason-conform] Formatter %q is not available in the mason registry. Make sure it is a valid formatter name, or add it to 'ignore_install' to silence this warning."):format(
package_name
),
vim.log.levels.WARN
)
end)
return
end
if pkg:is_installed() or pkg:is_installing() then
return
end
install_package(pkg, version)
end
return M
+17 -5
View File
@@ -1,7 +1,8 @@
local M = {}
-- conform formatter to mason package mapping
-- https://mason-registry.dev/registry/list
---Conform formatter name -> mason package name.
---https://mason-registry.dev/registry/list
---@type table<string, string>
M.conform_to_package = {
["air"] = "air",
["alejandra"] = "alejandra",
@@ -53,6 +54,7 @@ M.conform_to_package = {
-- dioxus
["djlint"] = "djlint",
["docformatter"] = "docformatter",
["dockerfmt"] = "dockerfmt",
-- docstrfmt
["doctoc"] = "doctoc",
["dprint"] = "dprint",
@@ -75,6 +77,7 @@ M.conform_to_package = {
-- gawk
["gci"] = "gci",
["gdformat"] = "gdtoolkit",
["gdscript-formatter"] = "gdscript-formatter",
["gersemi"] = "gersemi",
-- ghokin
-- gleam
@@ -118,6 +121,7 @@ M.conform_to_package = {
-- liquidsoap-prettier
-- llf
["lua-format"] = "luaformatter",
["luafmt"] = "luafmt",
-- mago_format
-- mago_lint
["markdown-toc"] = "markdown-toc",
@@ -143,7 +147,12 @@ M.conform_to_package = {
-- odinfmt
["opa_fmt"] = "opa",
["ormolu"] = "ormolu",
["oxfmt"] = "oxfmt",
["oxlint"] = "oxlint",
-- packer_fmt
["palantir-java-format"] = "palantir-java-format",
["panache"] = "panache",
["panache-fix"] = "panache",
-- pangu
-- perlimports
-- perltidy
@@ -160,9 +169,10 @@ M.conform_to_package = {
["purs-tidy"] = "purescript-tidy",
-- pycln
["pyink"] = "pyink",
["pymarkdownlnt"] = "pymarkdownlnt",
["pyproject-fmt"] = "pyproject-fmt",
-- python-ly
-- pyupgrade
["pyupgrade"] = "pyupgrade",
["reformat-gherkin"] = "reformat-gherkin",
["reorder-python-imports"] = "reorder-python-imports",
-- rescript-format
@@ -170,10 +180,12 @@ M.conform_to_package = {
-- rstfmt
["rubocop"] = "rubocop",
["rubyfmt"] = "rubyfmt",
["ruff"] = "ruff",
["ruff_fix"] = "ruff",
["ruff_format"] = "ruff",
["ruff_organize_imports"] = "ruff",
["rufo"] = "rufo",
["rumdl"] = "rumdl",
-- runic
-- ["rustfmt"] = "rustfmt", Deprecated by mason
["rustywind"] = "rustywind",
@@ -199,7 +211,7 @@ M.conform_to_package = {
["superhtml"] = "superhtml",
-- swift
-- swift_format
-- swiftformat
["swiftformat"] = "swiftformat",
["swiftlint"] = "swiftlint",
-- syntax_tree
["taplo"] = "taplo",
@@ -216,7 +228,7 @@ M.conform_to_package = {
["twig-cs-fixer"] = "twig-cs-fixer",
-- typespec
["typos"] = "typos",
-- typstyle
["typstyle"] = "typstyle",
-- ufmt
-- uncrustify
["usort"] = "usort",
+182
View File
@@ -0,0 +1,182 @@
-- Smoke test for mason-conform.nvim with stubbed mason/conform.
-- Run from the repository root: nvim -l tests/smoke_test.lua
local script = (arg and arg[0]) or "tests/smoke_test.lua"
local root = script:match("^(.*)/tests/") or "."
package.path = ("%s/lua/?.lua;%s/lua/?/init.lua;"):format(root, root) .. package.path
local failures = {}
local function check(cond, msg)
if not cond then
failures[#failures + 1] = msg
print("FAIL: " .. msg)
else
print("ok: " .. msg)
end
end
-- ---- stubs -------------------------------------------------------------
local notifications = {}
local install_calls = {}
local installed = {}
local installing = {}
package.preload["conform"] = function()
return {
formatters_by_ft = {
lua = { "stylua" },
python = { "autopep8", lsp_format = "fallback", stop_after_first = true },
markdown = { { "prettierd", "prettier" }, "cbfmt" },
javascript = function()
error("needs a buffer")
end,
go = function()
return { "gofumpt" }
end,
},
}
end
package.preload["mason-registry"] = function()
local registry = {}
function registry.refresh(cb)
if cb then
cb(true, {})
end
end
function registry.get_package(name)
if name == "not-in-registry" then
error(('Cannot find package "%q".'):format(name))
end
local pkg = { name = name }
function pkg:is_installed()
return installed[name] or false
end
function pkg:is_installing()
return installing[name] or false
end
function pkg:install(opts)
install_calls[#install_calls + 1] = { name = name, version = opts and opts.version }
local handle = {}
function handle:once(event, cb2)
assert(event == "closed")
installed[name] = true
cb2()
end
return handle
end
return pkg
end
return registry
end
package.preload["mason-core.package"] = function()
local Package = {}
function Package.Parse(identifier)
local name, version = identifier:match("^([^@]+)@?(.*)$")
if version == "" then
version = nil
end
return name, version
end
return Package
end
package.preload["mason.version"] = function()
return { MAJOR_VERSION = 2, MINOR_VERSION = 3 }
end
local original_notify = vim.notify
vim.notify = function(msg, level)
notifications[#notifications + 1] = { msg = msg, level = level or vim.log.levels.INFO }
end
-- ---- tests -------------------------------------------------------------
-- setup() with defaults
local mc = require("mason-conform")
mc.setup()
vim.wait(200)
check(vim.deep_equal(mc.config, { ignore_install = {} }), "setup() without opts keeps defaults")
local function installed_names()
local names = {}
for _, call in ipairs(install_calls) do
names[#names + 1] = call.name
end
table.sort(names)
return names
end
check(
vim.deep_equal(installed_names(), { "autopep8", "cbfmt", "gofumpt", "prettier", "prettierd", "stylua" }),
"installs mapped formatters from every formatters_by_ft shape; skips broken function: "
.. table.concat(installed_names(), ",")
)
-- setup() again with ignore_install
install_calls = {}
notifications = {}
installed = {}
mc.setup({ ignore_install = { "prettierd" } })
vim.wait(200)
check(
vim.deep_equal(installed_names(), { "autopep8", "cbfmt", "gofumpt", "prettier", "stylua" }),
"ignore_install is respected: " .. table.concat(installed_names(), ",")
)
check(vim.deep_equal(mc.config.ignore_install, { "prettierd" }), "ignore_install stored")
-- installed / installing packages are skipped
install_calls = {}
installed = {}
installing = {}
installed["stylua"] = true
installing["autopep8"] = true
mc.setup({ ignore_install = {} })
vim.wait(200)
check(
vim.deep_equal(installed_names(), { "cbfmt", "gofumpt", "prettier", "prettierd" }),
"already installed/installing packages are skipped: " .. table.concat(installed_names(), ",")
)
-- unknown mason package -> warning, no error
local install = require("mason-conform.install")
notifications = {}
local ok = pcall(install.try_install, "not-in-registry")
check(ok, "try_install on unknown package does not raise")
check(#notifications == 1 and notifications[1].level == vim.log.levels.WARN, "unknown package emits a warning")
check(
notifications[1] and notifications[1].msg:find("not%-in%-registry", 1, false) ~= nil,
"warning names the formatter"
)
-- version pinning passes through Package.Parse
installed = {}
install_calls = {}
install.try_install("stylua@1.2.0")
check(install_calls[1] and install_calls[1].version == "1.2.0", "version suffix forwarded to mason")
-- setup() validates its argument
ok = pcall(mc.setup, "not a table")
check(not ok, "setup() rejects non-table opts")
-- health check runs
local health_ok, err = pcall(function()
require("mason-conform.health").check()
end)
check(health_ok, "health check runs without error" .. (health_ok and "" or (": " .. tostring(err))))
-- formatters module
local formatters = require("mason-conform.formatters").configured()
check(
vim.deep_equal(formatters, { "autopep8", "cbfmt", "gofumpt", "prettier", "prettierd", "stylua" }),
"formatters.configured() returns sorted unique names"
)
vim.notify = original_notify
print(("-"):rep(60))
if #failures > 0 then
print(("%d FAILURE(S)"):format(#failures))
os.exit(1)
end
print("all tests passed")