diff --git a/ci/conform-nvim.nix b/ci/conform-nvim.nix new file mode 100644 index 0000000000..c2d9a4cf7e --- /dev/null +++ b/ci/conform-nvim.nix @@ -0,0 +1,13 @@ +{ + vimPlugins, + lib, + writeText, +}: +lib.pipe "${vimPlugins.conform-nvim}/lua/conform/formatters" [ + builtins.readDir + builtins.attrNames + (builtins.filter (lib.hasSuffix ".lua")) + (map (lib.removeSuffix ".lua")) + builtins.toJSON + (writeText "conform-formatters") +] diff --git a/ci/default.nix b/ci/default.nix index 1c32826b2f..004e41eee6 100644 --- a/ci/default.nix +++ b/ci/default.nix @@ -19,5 +19,6 @@ lib.fix (self: { none-ls-builtins = pkgs.callPackage ./none-ls.nix { }; rust-analyzer-options = pkgs.callPackage ./rust-analyzer { }; lspconfig-servers = pkgs.callPackage ./nvim-lspconfig { }; + conform-formatters = pkgs.callPackage ./conform-nvim.nix { }; version-info = pkgs.callPackage ./version-info { }; }) diff --git a/ci/generate.nix b/ci/generate.nix index 081532c2d0..5e84d5f8d3 100644 --- a/ci/generate.nix +++ b/ci/generate.nix @@ -4,14 +4,15 @@ efmls-configs-sources, none-ls-builtins, lspconfig-servers, - nixfmt-rfc-style, + conform-formatters, + nixfmt, nodePackages, }: writeShellApplication { name = "generate"; runtimeInputs = [ - nixfmt-rfc-style + nixfmt nodePackages.prettier ]; @@ -32,18 +33,24 @@ writeShellApplication { shift done - generate() { + generate_nix() { echo "$2" cp "$1" "$generated_dir/$2.nix" nixfmt "$generated_dir/$2.nix" } + generate_json() { + echo "$2" + prettier --parser=json "$1" >"$generated_dir/$2.json" + } + mkdir -p "$generated_dir" - generate "${rust-analyzer-options}" "rust-analyzer" - generate "${efmls-configs-sources}" "efmls-configs" - generate "${none-ls-builtins}" "none-ls" - echo "lspconfig servers" - prettier --parser=json "${lspconfig-servers}" >"$generated_dir/lspconfig-servers.json" + generate_nix "${rust-analyzer-options}" "rust-analyzer" + generate_nix "${efmls-configs-sources}" "efmls-configs" + generate_nix "${none-ls-builtins}" "none-ls" + + generate_json "${conform-formatters}" "conform-formatters" + generate_json "${lspconfig-servers}" "lspconfig-servers" if [ -n "$commit" ]; then cd "$generated_dir" diff --git a/generated/conform-formatters.json b/generated/conform-formatters.json new file mode 100644 index 0000000000..1d730db799 --- /dev/null +++ b/generated/conform-formatters.json @@ -0,0 +1,247 @@ +[ + "air", + "alejandra", + "ansible-lint", + "asmfmt", + "ast-grep", + "astyle", + "auto_optional", + "autocorrect", + "autoflake", + "autopep8", + "awk", + "bake", + "bean-format", + "beautysh", + "bibtex-tidy", + "bicep", + "biome-check", + "biome-organize-imports", + "biome", + "black", + "blade-formatter", + "blue", + "bpfmt", + "bsfmt", + "buf", + "buildifier", + "cabal_fmt", + "caramel_fmt", + "cbfmt", + "cedar", + "clang-format", + "clang_format", + "cljfmt", + "cljstyle", + "cmake_format", + "codeql", + "codespell", + "commitmsgfmt", + "crlfmt", + "crystal", + "csharpier", + "css_beautify", + "cue_fmt", + "d2", + "darker", + "dart_format", + "dcm_fix", + "dcm_format", + "deno_fmt", + "dfmt", + "dioxus", + "djlint", + "docformatter", + "dockerfmt", + "docstrfmt", + "doctoc", + "dprint", + "easy-coding-standard", + "efmt", + "elm_format", + "erb_format", + "erlfmt", + "eslint_d", + "fantomas", + "findent", + "fish_indent", + "fixjson", + "fnlfmt", + "forge_fmt", + "format-dune-file", + "format-queries", + "fourmolu", + "fprettify", + "gawk", + "gci", + "gdformat", + "gersemi", + "ghdl", + "ghokin", + "gleam", + "gluon_fmt", + "gn", + "gofmt", + "gofumpt", + "goimports-reviser", + "goimports", + "gojq", + "golangci-lint", + "golines", + "google-java-format", + "grain_format", + "hcl", + "hindent", + "hledger-fmt", + "html_beautify", + "htmlbeautifier", + "hurlfmt", + "imba_fmt", + "indent", + "init", + "injected", + "inko", + "isort", + "janet-format", + "joker", + "jq", + "js_beautify", + "jsonnetfmt", + "just", + "kcl", + "kdlfmt", + "keep-sorted", + "ktfmt", + "ktlint", + "kulala-fmt", + "latexindent", + "leptosfmt", + "liquidsoap-prettier", + "llf", + "lua-format", + "mago_format", + "mago_lint", + "markdown-toc", + "markdownfmt", + "markdownlint-cli2", + "markdownlint", + "mdformat", + "mdsf", + "mdslw", + "mix", + "mojo_format", + "nginxfmt", + "nickel", + "nimpretty", + "nixfmt", + "nixpkgs_fmt", + "nomad_fmt", + "nph", + "npm-groovy-lint", + "nufmt", + "ocamlformat", + "ocp-indent", + "odinfmt", + "opa_fmt", + "ormolu", + "packer_fmt", + "pangu", + "perlimports", + "perltidy", + "pg_format", + "php_cs_fixer", + "phpcbf", + "phpinsights", + "pint", + "prettier", + "prettierd", + "pretty-php", + "prettypst", + "prolog", + "puppet-lint", + "purs-tidy", + "pycln", + "pyink", + "pymarkdownlnt", + "pyproject-fmt", + "python-ly", + "pyupgrade", + "qmlformat", + "reformat-gherkin", + "reorder-python-imports", + "rescript-format", + "roc", + "rstfmt", + "rubocop", + "rubyfmt", + "ruff", + "ruff_fix", + "ruff_format", + "ruff_organize_imports", + "rufo", + "runic", + "rustfmt", + "rustywind", + "scalafmt", + "shellcheck", + "shellharden", + "shfmt", + "sleek", + "smlfmt", + "snakefmt", + "spotless_gradle", + "spotless_maven", + "sql_formatter", + "sqlfluff", + "sqlfmt", + "sqruff", + "squeeze_blanks", + "standard-clj", + "standardjs", + "standardrb", + "stylelint", + "styler", + "stylish-haskell", + "stylua", + "superhtml", + "swift", + "swift_format", + "swiftformat", + "swiftlint", + "syntax_tree", + "taplo", + "templ", + "terraform_fmt", + "terragrunt_hclfmt", + "tex-fmt", + "tlint", + "tofu_fmt", + "tombi", + "treefmt", + "trim_newlines", + "trim_whitespace", + "twig-cs-fixer", + "typespec", + "typos", + "typstfmt", + "typstyle", + "ufmt", + "uncrustify", + "usort", + "v", + "verible", + "vsg", + "xmlformat", + "xmlformatter", + "xmllint", + "xmlstarlet", + "yamlfix", + "yamlfmt", + "yapf", + "yew-fmt", + "yq", + "zigfmt", + "ziggy", + "ziggy_schema", + "zprint" +] diff --git a/plugins/by-name/conform-nvim/auto-install.nix b/plugins/by-name/conform-nvim/auto-install.nix new file mode 100644 index 0000000000..2e1cf4a9d9 --- /dev/null +++ b/plugins/by-name/conform-nvim/auto-install.nix @@ -0,0 +1,78 @@ +{ + pkgs, + lib, + ... +}: +let + inherit (lib) elem throwIfNot; + inherit (builtins) + filter + isString + isAttrs + attrValues + attrNames + concatMap + partition + ; + + inherit (import ./formatter-packages.nix { inherit pkgs lib; }) sType formatter-packages; + sTypeList = attrValues sType; + isSTypeAttrSet = x: lib.elem (x.mark or null) sTypeList; +in +rec { + cleanMaybePackageList = filter (x: !isSTypeAttrSet x); + + getPackageByName = + { configuredFormatters, overrides }: + name: + let + permittedNames = attrNames configuredFormatters; + isSType = x: elem x sTypeList; + notFoundMsg = '' + A package for the conform-nvim formatter '${name}' could not be found. + It is not a user defined formatter. Is the formatter name correct? + ''; + maybePackage = + overrides.${name} or formatter-packages.${name} or pkgs.${name} + or (throwIfNot (elem name permittedNames) notFoundMsg null); + in + if isSType maybePackage then + { + inherit name; + mark = maybePackage; + } + else + maybePackage; + + mkWarnsFromMaybePackageList = + opts: list: + let + mkWarn = + { name, mark }: + lib.nixvim.mkWarnings "conform-nvim" [ + { + when = true; + message = '' + You have enabled the '${name}' formatter that relies on a package marked '${mark}'. + Because of that it will not be installed. To disable this warning, explicitly disable installing the package + by setting the '${opts.autoInstall.overrides}.${name}' option to 'null'. You can also disable + all warnings related to packages not installed by 'autoInstall' with '${opts.autoInstall.enableWarnings}'. + ''; + } + ]; + in + concatMap mkWarn (filter isSTypeAttrSet list); + + collectFormatters = + formatters: + let + partitioned = lib.pipe formatters [ + lib.flatten + (filter (x: isString x || isAttrs x)) + (partition isString) + ]; + in + lib.optionals (formatters != [ ]) ( + partitioned.right ++ concatMap (fmts: collectFormatters (attrValues fmts)) partitioned.wrong + ); +} diff --git a/plugins/by-name/conform-nvim/default.nix b/plugins/by-name/conform-nvim/default.nix index 5e411ea985..9a696a5f7c 100644 --- a/plugins/by-name/conform-nvim/default.nix +++ b/plugins/by-name/conform-nvim/default.nix @@ -1,9 +1,11 @@ { lib, + pkgs, ... }: let inherit (lib) types; + inherit (builtins) attrValues; inherit (lib.nixvim) defaultNullOpts; in lib.nixvim.plugins.mkNeovimPlugin { @@ -12,7 +14,10 @@ lib.nixvim.plugins.mkNeovimPlugin { packPathName = "conform.nvim"; description = "Lightweight yet powerful formatter plugin for Neovim."; - maintainers = [ lib.maintainers.khaneliman ]; + maintainers = with lib.maintainers; [ + khaneliman + saygo-png + ]; # TODO: added 2024-08-23 remove after 24.11 deprecateExtraOptions = true; @@ -43,6 +48,36 @@ lib.nixvim.plugins.mkNeovimPlugin { "formatOnSave" ]; + extraOptions = { + autoInstall = { + enable = lib.mkEnableOption '' + automatic installation of formatters listed in `settings.formatters_by_ft` and `settings.formatters` + ''; + + enableWarnings = lib.mkOption { + default = true; + example = false; + description = "Whether to enable warnings."; + type = lib.types.bool; + }; + + overrides = lib.mkOption { + type = with types; attrsOf (nullOr package); + default = { }; + example = lib.literalExpression '' + { + "treefmt" = null; + "pyproject-fmt" = pkgs.python312Packages.pyproject-parser; + }; + ''; + description = '' + Attribute set of formatter names to nix packages. + You can set a formatter to `null` to disable auto-installing its package. + ''; + }; + }; + }; + settingsOptions = let lsp_format = @@ -234,4 +269,27 @@ lib.nixvim.plugins.mkNeovimPlugin { } ``` ''; + + extraConfig = + cfg: opts: + let + inherit (cfg.autoInstall) enable enableWarnings; + inherit (import ./auto-install.nix { inherit pkgs lib; }) + getPackageByName + collectFormatters + cleanMaybePackageList + mkWarnsFromMaybePackageList + ; + getPackageByNameWith = getPackageByName { + configuredFormatters = cfg.settings.formatters; + inherit (cfg.autoInstall) overrides; + }; + names = collectFormatters (attrValues cfg.settings.formatters_by_ft); + packageList = map getPackageByNameWith names; + warns = (mkWarnsFromMaybePackageList opts) packageList; + in + { + warnings = lib.mkIf (enable && warns != [ ] && enableWarnings) warns; + extraPackages = lib.mkIf enable (cleanMaybePackageList packageList); + }; } diff --git a/plugins/by-name/conform-nvim/formatter-packages.nix b/plugins/by-name/conform-nvim/formatter-packages.nix new file mode 100644 index 0000000000..38ccf6c7f9 --- /dev/null +++ b/plugins/by-name/conform-nvim/formatter-packages.nix @@ -0,0 +1,146 @@ +{ + pkgs, + ... +}: +with pkgs; +rec { + sType = { + broken = "broken"; + darwinOnly = "Darwin only"; + unpackaged = "unpackaged"; + }; + + formatter-packages = { + swift_format = if !stdenv.isDarwin then sType.darwinOnly else swift-format; + swiftlint = if !stdenv.isDarwin then sType.darwinOnly else swiftlint; + + # 2025-09-13 build failure + inko = sType.broken; + # 2025-09-13 build failure + commitmsgfmt = sType.broken; + # 2025-09-17 build failure + gci = sType.broken; + + format-queries = null; # Uses neovim itself + init = null; # Internal thingamajig + injected = null; # Internal formatter + trim_newlines = null; # Conform native formatter + trim_whitespace = null; # Conform native formatter + + auto_optional = sType.unpackaged; + bake = sType.unpackaged; + blue = sType.unpackaged; + bpfmt = sType.unpackaged; + bsfmt = sType.unpackaged; + caramel_fmt = sType.unpackaged; + crlfmt = sType.unpackaged; + darker = sType.unpackaged; + dcm_fix = sType.unpackaged; + dcm_format = sType.unpackaged; + easy-coding-standard = sType.unpackaged; + findent = sType.unpackaged; + ghokin = sType.unpackaged; + gluon_fmt = sType.unpackaged; + grain_format = sType.unpackaged; + hledger-fmt = sType.unpackaged; + imba_fmt = sType.unpackaged; + janet-format = sType.unpackaged; + liquidsoap-prettier = sType.unpackaged; + llf = sType.unpackaged; + markdown-toc = sType.unpackaged; + markdownfmt = sType.unpackaged; + mdslw = sType.unpackaged; + mojo_format = sType.unpackaged; + nomad_fmt = sType.unpackaged; + npm-groovy-lint = sType.unpackaged; + packer_fmt = sType.unpackaged; + pangu = sType.unpackaged; + perlimports = sType.unpackaged; + pint = sType.unpackaged; + purs-tidy = sType.unpackaged; + pycln = sType.unpackaged; + pyink = sType.unpackaged; + pymarkdownlnt = sType.unpackaged; + reformat-gherkin = sType.unpackaged; + rescript-format = sType.unpackaged; + runic = sType.unpackaged; + spotless_gradle = sType.unpackaged; + spotless_maven = sType.unpackaged; + standard-clj = sType.unpackaged; + standardjs = sType.unpackaged; + tlint = sType.unpackaged; + twig-cs-fixer = sType.unpackaged; + vsg = sType.unpackaged; + ziggy = sType.unpackaged; + ziggy_schema = sType.unpackaged; + + inherit (python313Packages) autopep8; + awk = gawk; + bean-format = beancount; + biome-check = biome; + biome-organize-imports = biome; + cabal_fmt = haskellPackages.cabal-fmt; + clang_format = clang-tools; + clang-format = clang-tools; + cmake_format = cmake-format; + css_beautify = nodePackages.js-beautify; + cue_fmt = cue; + dart_format = dart; + deno_fmt = deno; + dioxus = dioxus-cli; + inherit (python313Packages) docformatter; + elm_format = elmPackages.elm-format; + erb_format = rubyPackages.erb-formatter; + fish_indent = fishMinimal; + forge_fmt = foundry; + format-dune-file = dune_3; + gdformat = gdtoolkit_4; + gofmt = go; + goimports = gotools; + hcl = hclfmt; + inherit (haskellPackages) hindent; + html_beautify = nodePackages.js-beautify; + inherit (rubyPackages) htmlbeautifier; + hurlfmt = hurl; + js_beautify = nodePackages.js-beautify; + jsonnetfmt = jsonnet; + inherit (texlive.pkgs) latexindent; + lua-format = luaformatter; + mago_format = mago; + mago_lint = mago; + markdownlint = markdownlint-cli; + mix = beamMinimal28Packages.elixir; + nginxfmt = nginx-config-formatter; + nimpretty = nim; + nixpkgs_fmt = nixpkgs-fmt; + inherit (ocamlPackages) ocp-indent; + odinfmt = ols; + opa_fmt = open-policy-agent; + perltidy = perl538Packages.PerlTidy; + pg_format = pgformatter; + php_cs_fixer = php83Packages.php-cs-fixer; + phpcbf = php84Packages.php-codesniffer; + inherit (php84Packages) phpinsights; + prolog = swi-prolog; + pyproject-fmt = python313Packages.pyproject-parser; + inherit (python313Packages) python-ly; + qmlformat = libsForQt5.qt5.qtdeclarative; + inherit (python313Packages) reorder-python-imports; + ruff_fix = ruff; + ruff_format = ruff; + ruff_organize_imports = ruff; + sql_formatter = sql-formatter; + inherit (python313Packages) sqlfmt; + squeeze_blanks = coreutils; + standardrb = rubyPackages.standard; + styler = R; + inherit (rubyPackages) syntax_tree; + terraform_fmt = tenv; + terragrunt_hclfmt = terragrunt; + tofu_fmt = opentofu; + v = vlang; + xmlformatter = xmlformat; + xmllint = libxml2; + zigfmt = zig; + }; +} diff --git a/tests/test-sources/plugins/by-name/conform-nvim/default.nix b/tests/test-sources/plugins/by-name/conform-nvim/default.nix index 9a640b9cb1..12a143498d 100644 --- a/tests/test-sources/plugins/by-name/conform-nvim/default.nix +++ b/tests/test-sources/plugins/by-name/conform-nvim/default.nix @@ -4,6 +4,21 @@ plugins.conform-nvim.enable = true; }; + all-formatters = + let + allFormatters = lib.importJSON ../../../../../generated/conform-formatters.json; + in + { + plugins.conform-nvim = { + enable = true; + autoInstall = { + enable = true; + enableWarnings = false; + }; + settings.formatters_by_ft."*" = allFormatters; + }; + }; + default = { plugins.conform-nvim = { enable = true;