aboutsummaryrefslogtreecommitdiffstats
path: root/lua
diff options
context:
space:
mode:
Diffstat (limited to 'lua')
-rw-r--r--lua/conform/formatters/autoflake.lua9
-rw-r--r--lua/conform/formatters/autopep8.lua9
-rw-r--r--lua/conform/formatters/black.lua19
-rw-r--r--lua/conform/formatters/clang_format.lua9
-rw-r--r--lua/conform/formatters/cljstyle.lua9
-rw-r--r--lua/conform/formatters/cmake_format.lua9
-rw-r--r--lua/conform/formatters/dart_format.lua9
-rw-r--r--lua/conform/formatters/dfmt.lua8
-rw-r--r--lua/conform/formatters/elm_format.lua9
-rw-r--r--lua/conform/formatters/erb_format.lua9
-rw-r--r--lua/conform/formatters/eslint_d.lua13
-rw-r--r--lua/conform/formatters/gdformat.lua9
-rw-r--r--lua/conform/formatters/gofmt.lua8
-rw-r--r--lua/conform/formatters/gofumpt.lua8
-rw-r--r--lua/conform/formatters/goimports.lua9
-rw-r--r--lua/conform/formatters/htmlbeautifier.lua8
-rw-r--r--lua/conform/formatters/isort.lua24
-rw-r--r--lua/conform/formatters/jq.lua8
-rw-r--r--lua/conform/formatters/nixfmt.lua8
-rw-r--r--lua/conform/formatters/nixpkgs_fmt.lua8
-rw-r--r--lua/conform/formatters/ocamlformat.lua9
-rw-r--r--lua/conform/formatters/pg_format.lua8
-rw-r--r--lua/conform/formatters/prettier.lua24
-rw-r--r--lua/conform/formatters/prettierd.lua24
-rw-r--r--lua/conform/formatters/rubocop.lua16
-rw-r--r--lua/conform/formatters/rustfmt.lua9
-rw-r--r--lua/conform/formatters/scalafmt.lua9
-rw-r--r--lua/conform/formatters/shfmt.lua9
-rw-r--r--lua/conform/formatters/sql_formatter.lua8
-rw-r--r--lua/conform/formatters/stylua.lua9
-rw-r--r--lua/conform/formatters/swift_format.lua8
-rw-r--r--lua/conform/formatters/swiftformat.lua9
-rw-r--r--lua/conform/formatters/terraform_fmt.lua9
-rw-r--r--lua/conform/formatters/uncrustify.lua11
-rw-r--r--lua/conform/formatters/xmlformat.lua9
-rw-r--r--lua/conform/formatters/yamlfix.lua9
-rw-r--r--lua/conform/formatters/yamlfmt.lua9
-rw-r--r--lua/conform/formatters/yapf.lua9
-rw-r--r--lua/conform/formatters/zigfmt.lua9
-rw-r--r--lua/conform/fs.lua18
-rw-r--r--lua/conform/health.lua34
-rw-r--r--lua/conform/init.lua291
-rw-r--r--lua/conform/log.lua108
-rw-r--r--lua/conform/runner.lua283
-rw-r--r--lua/conform/util.lua75
45 files changed, 1218 insertions, 0 deletions
diff --git a/lua/conform/formatters/autoflake.lua b/lua/conform/formatters/autoflake.lua
new file mode 100644
index 0000000..04daf71
--- /dev/null
+++ b/lua/conform/formatters/autoflake.lua
@@ -0,0 +1,9 @@
+---@type conform.FormatterConfig
+return {
+ meta = {
+ url = "https://github.com/PyCQA/autoflake",
+ description = "Removes unused imports and unused variables as reported by pyflakes.",
+ },
+ command = "autoflake",
+ args = { "--stdin-display-name", "$FILENAME", "-" },
+}
diff --git a/lua/conform/formatters/autopep8.lua b/lua/conform/formatters/autopep8.lua
new file mode 100644
index 0000000..5ed2f83
--- /dev/null
+++ b/lua/conform/formatters/autopep8.lua
@@ -0,0 +1,9 @@
+---@type conform.FormatterConfig
+return {
+ meta = {
+ url = "https://github.com/hhatto/autopep8",
+ description = "A tool that automatically formats Python code to conform to the PEP 8 style guide.",
+ },
+ command = "autopep8",
+ args = { "-" },
+}
diff --git a/lua/conform/formatters/black.lua b/lua/conform/formatters/black.lua
new file mode 100644
index 0000000..0d892a2
--- /dev/null
+++ b/lua/conform/formatters/black.lua
@@ -0,0 +1,19 @@
+local util = require("conform.util")
+---@type conform.FormatterConfig
+return {
+ meta = {
+ url = "https://github.com/psf/black",
+ description = "The uncompromising Python code formatter.",
+ },
+ command = "black",
+ args = {
+ "--stdin-filename",
+ "$FILENAME",
+ "--quiet",
+ "-",
+ },
+ cwd = util.root_file({
+ -- https://black.readthedocs.io/en/stable/usage_and_configuration/the_basics.html#configuration-via-a-file
+ "pyproject.toml",
+ }),
+}
diff --git a/lua/conform/formatters/clang_format.lua b/lua/conform/formatters/clang_format.lua
new file mode 100644
index 0000000..d2c6d41
--- /dev/null
+++ b/lua/conform/formatters/clang_format.lua
@@ -0,0 +1,9 @@
+---@type conform.FormatterConfig
+return {
+ meta = {
+ url = "https://www.kernel.org/doc/html/latest/process/clang-format.html",
+ description = "Tool to format C/C++/… code according to a set of rules and heuristics.",
+ },
+ command = "clang-format",
+ args = { "-assume-filename", "$FILENAME" },
+}
diff --git a/lua/conform/formatters/cljstyle.lua b/lua/conform/formatters/cljstyle.lua
new file mode 100644
index 0000000..ffd8061
--- /dev/null
+++ b/lua/conform/formatters/cljstyle.lua
@@ -0,0 +1,9 @@
+---@type conform.FormatterConfig
+return {
+ meta = {
+ url = "https://github.com/greglook/cljstyle",
+ description = "Formatter for Clojure code.",
+ },
+ command = "cljstyle",
+ args = { "pipe" },
+}
diff --git a/lua/conform/formatters/cmake_format.lua b/lua/conform/formatters/cmake_format.lua
new file mode 100644
index 0000000..563bcae
--- /dev/null
+++ b/lua/conform/formatters/cmake_format.lua
@@ -0,0 +1,9 @@
+---@type conform.FormatterConfig
+return {
+ meta = {
+ url = "https://github.com/cheshirekow/cmake_format",
+ description = "Parse cmake listfiles and format them nicely.",
+ },
+ command = "cmake-format",
+ args = { "-" },
+}
diff --git a/lua/conform/formatters/dart_format.lua b/lua/conform/formatters/dart_format.lua
new file mode 100644
index 0000000..eb9b39c
--- /dev/null
+++ b/lua/conform/formatters/dart_format.lua
@@ -0,0 +1,9 @@
+---@type conform.FormatterConfig
+return {
+ meta = {
+ url = "https://dart.dev/tools/dart-format",
+ description = "Replace the whitespace in your program with formatting that follows Dart guidelines.",
+ },
+ command = "dart",
+ args = { "format" },
+}
diff --git a/lua/conform/formatters/dfmt.lua b/lua/conform/formatters/dfmt.lua
new file mode 100644
index 0000000..49c99cb
--- /dev/null
+++ b/lua/conform/formatters/dfmt.lua
@@ -0,0 +1,8 @@
+---@type conform.FormatterConfig
+return {
+ meta = {
+ url = "https://github.com/dlang-community/dfmt",
+ description = "Formatter for D source code.",
+ },
+ command = "dfmt",
+}
diff --git a/lua/conform/formatters/elm_format.lua b/lua/conform/formatters/elm_format.lua
new file mode 100644
index 0000000..23f1408
--- /dev/null
+++ b/lua/conform/formatters/elm_format.lua
@@ -0,0 +1,9 @@
+---@type conform.FormatterConfig
+return {
+ meta = {
+ url = "https://github.com/avh4/elm-format",
+ description = "elm-format formats Elm source code according to a standard set of rules based on the official [Elm Style Guide](https://elm-lang.org/docs/style-guide).",
+ },
+ command = "elm-format",
+ args = { "--stdin" },
+}
diff --git a/lua/conform/formatters/erb_format.lua b/lua/conform/formatters/erb_format.lua
new file mode 100644
index 0000000..dad08ca
--- /dev/null
+++ b/lua/conform/formatters/erb_format.lua
@@ -0,0 +1,9 @@
+---@type conform.FormatterConfig
+return {
+ meta = {
+ url = "https://github.com/nebulab/erb-formatter",
+ description = "Format ERB files with speed and precision.",
+ },
+ command = "erb-format",
+ args = { "--stdin" },
+}
diff --git a/lua/conform/formatters/eslint_d.lua b/lua/conform/formatters/eslint_d.lua
new file mode 100644
index 0000000..e036aae
--- /dev/null
+++ b/lua/conform/formatters/eslint_d.lua
@@ -0,0 +1,13 @@
+local util = require("conform.util")
+---@type conform.FormatterConfig
+return {
+ meta = {
+ url = "https://github.com/mantoni/eslint_d.js/",
+ description = "Like ESLint, but faster.",
+ },
+ command = util.from_node_modules("eslint_d"),
+ args = { "--fix-to-stdout", "--stdin", "--stdin-filename", "$FILENAME" },
+ cwd = util.root_file({
+ "package.json",
+ }),
+}
diff --git a/lua/conform/formatters/gdformat.lua b/lua/conform/formatters/gdformat.lua
new file mode 100644
index 0000000..914bb89
--- /dev/null
+++ b/lua/conform/formatters/gdformat.lua
@@ -0,0 +1,9 @@
+---@type conform.FormatterConfig
+return {
+ meta = {
+ url = "https://github.com/Scony/godot-gdscript-toolkit",
+ description = "A formatter for Godot's gdscript.",
+ },
+ command = "gdformat",
+ args = { "-" },
+}
diff --git a/lua/conform/formatters/gofmt.lua b/lua/conform/formatters/gofmt.lua
new file mode 100644
index 0000000..b0b81c3
--- /dev/null
+++ b/lua/conform/formatters/gofmt.lua
@@ -0,0 +1,8 @@
+---@type conform.FormatterConfig
+return {
+ meta = {
+ url = "https://pkg.go.dev/cmd/gofmt",
+ description = "Formats go programs.",
+ },
+ command = "gofmt",
+}
diff --git a/lua/conform/formatters/gofumpt.lua b/lua/conform/formatters/gofumpt.lua
new file mode 100644
index 0000000..79fb4dc
--- /dev/null
+++ b/lua/conform/formatters/gofumpt.lua
@@ -0,0 +1,8 @@
+---@type conform.FormatterConfig
+return {
+ meta = {
+ url = "https://github.com/mvdan/gofumpt",
+ description = "Enforce a stricter format than gofmt, while being backwards compatible. That is, gofumpt is happy with a subset of the formats that gofmt is happy with.",
+ },
+ command = "gofumpt",
+}
diff --git a/lua/conform/formatters/goimports.lua b/lua/conform/formatters/goimports.lua
new file mode 100644
index 0000000..2361e43
--- /dev/null
+++ b/lua/conform/formatters/goimports.lua
@@ -0,0 +1,9 @@
+---@type conform.FormatterConfig
+return {
+ meta = {
+ url = "https://pkg.go.dev/golang.org/x/tools/cmd/goimports",
+ description = "Updates your Go import lines, adding missing ones and removing unreferenced ones.",
+ },
+ command = "goimports",
+ args = { "-srcdir", "$DIRNAME" },
+}
diff --git a/lua/conform/formatters/htmlbeautifier.lua b/lua/conform/formatters/htmlbeautifier.lua
new file mode 100644
index 0000000..3182f99
--- /dev/null
+++ b/lua/conform/formatters/htmlbeautifier.lua
@@ -0,0 +1,8 @@
+---@type conform.FormatterConfig
+return {
+ meta = {
+ url = "https://github.com/threedaymonk/htmlbeautifier",
+ description = "A normaliser/beautifier for HTML that also understands embedded Ruby. Ideal for tidying up Rails templates.",
+ },
+ command = "htmlbeautifier",
+}
diff --git a/lua/conform/formatters/isort.lua b/lua/conform/formatters/isort.lua
new file mode 100644
index 0000000..f6c6e3d
--- /dev/null
+++ b/lua/conform/formatters/isort.lua
@@ -0,0 +1,24 @@
+local util = require("conform.util")
+---@type conform.FormatterConfig
+return {
+ meta = {
+ url = "https://github.com/PyCQA/isort",
+ description = "Python utility / library to sort imports alphabetically and automatically separate them into sections and by type.",
+ },
+ command = "isort",
+ args = {
+ "--stdout",
+ "--filename",
+ "$FILENAME",
+ "-",
+ },
+ cwd = util.root_file({
+ -- https://pycqa.github.io/isort/docs/configuration/config_files.html
+ ".isort.cfg",
+ "pyproject.toml",
+ "setup.py",
+ "setup.cfg",
+ "tox.ini",
+ ".editorconfig",
+ }),
+}
diff --git a/lua/conform/formatters/jq.lua b/lua/conform/formatters/jq.lua
new file mode 100644
index 0000000..50a905d
--- /dev/null
+++ b/lua/conform/formatters/jq.lua
@@ -0,0 +1,8 @@
+---@type conform.FormatterConfig
+return {
+ meta = {
+ url = "https://github.com/stedolan/jq",
+ description = "Command-line JSON processor.",
+ },
+ command = "jq",
+}
diff --git a/lua/conform/formatters/nixfmt.lua b/lua/conform/formatters/nixfmt.lua
new file mode 100644
index 0000000..6c5001a
--- /dev/null
+++ b/lua/conform/formatters/nixfmt.lua
@@ -0,0 +1,8 @@
+---@type conform.FormatterConfig
+return {
+ meta = {
+ url = "https://github.com/serokell/nixfmt",
+ description = "nixfmt is a formatter for Nix code, intended to apply a uniform style.",
+ },
+ command = "nixfmt",
+}
diff --git a/lua/conform/formatters/nixpkgs_fmt.lua b/lua/conform/formatters/nixpkgs_fmt.lua
new file mode 100644
index 0000000..6685fe8
--- /dev/null
+++ b/lua/conform/formatters/nixpkgs_fmt.lua
@@ -0,0 +1,8 @@
+---@type conform.FormatterConfig
+return {
+ meta = {
+ url = "https://github.com/nix-community/nixpkgs-fmt",
+ description = "nixpkgs-fmt is a Nix code formatter for nixpkgs.",
+ },
+ command = "nixpkgs-fmt",
+}
diff --git a/lua/conform/formatters/ocamlformat.lua b/lua/conform/formatters/ocamlformat.lua
new file mode 100644
index 0000000..4ea1b49
--- /dev/null
+++ b/lua/conform/formatters/ocamlformat.lua
@@ -0,0 +1,9 @@
+---@type conform.FormatterConfig
+return {
+ meta = {
+ url = "https://github.com/ocaml-ppx/ocamlformat",
+ description = "Auto-formatter for OCaml code.",
+ },
+ command = "ocamlformat",
+ args = { "--enable-outside-detected-project", "--name", "$FILENAME", "-" },
+}
diff --git a/lua/conform/formatters/pg_format.lua b/lua/conform/formatters/pg_format.lua
new file mode 100644
index 0000000..87aff66
--- /dev/null
+++ b/lua/conform/formatters/pg_format.lua
@@ -0,0 +1,8 @@
+---@type conform.FormatterConfig
+return {
+ meta = {
+ url = "https://github.com/darold/pgFormatter",
+ description = "PostgreSQL SQL syntax beautifier.",
+ },
+ command = "pg_format",
+}
diff --git a/lua/conform/formatters/prettier.lua b/lua/conform/formatters/prettier.lua
new file mode 100644
index 0000000..6f4bbfb
--- /dev/null
+++ b/lua/conform/formatters/prettier.lua
@@ -0,0 +1,24 @@
+local util = require("conform.util")
+---@type conform.FormatterConfig
+return {
+ meta = {
+ url = "https://github.com/prettier/prettier",
+ description = [[Prettier is an opinionated code formatter. It enforces a consistent style by parsing your code and re-printing it with its own rules that take the maximum line length into account, wrapping code when necessary.]],
+ },
+ command = util.from_node_modules("prettier"),
+ args = { "--stdin-filepath", "$FILENAME" },
+ cwd = util.root_file({
+ -- https://prettier.io/docs/en/configuration.html
+ ".prettierrc",
+ ".prettierrc.json",
+ ".prettierrc.yml",
+ ".prettierrc.yaml",
+ ".prettierrc.json5",
+ ".prettierrc.js",
+ ".prettierrc.cjs",
+ ".prettierrc.toml",
+ "prettier.config.js",
+ "prettier.config.cjs",
+ "package.json",
+ }),
+}
diff --git a/lua/conform/formatters/prettierd.lua b/lua/conform/formatters/prettierd.lua
new file mode 100644
index 0000000..0af6baf
--- /dev/null
+++ b/lua/conform/formatters/prettierd.lua
@@ -0,0 +1,24 @@
+local util = require("conform.util")
+---@type conform.FormatterConfig
+return {
+ meta = {
+ url = "https://github.com/fsouza/prettierd",
+ description = "prettier, as a daemon, for ludicrous formatting speed.",
+ },
+ command = util.from_node_modules("prettierd"),
+ args = { "$FILENAME" },
+ cwd = util.root_file({
+ -- https://prettier.io/docs/en/configuration.html
+ ".prettierrc",
+ ".prettierrc.json",
+ ".prettierrc.yml",
+ ".prettierrc.yaml",
+ ".prettierrc.json5",
+ ".prettierrc.js",
+ ".prettierrc.cjs",
+ ".prettierrc.toml",
+ "prettier.config.js",
+ "prettier.config.cjs",
+ "package.json",
+ }),
+}
diff --git a/lua/conform/formatters/rubocop.lua b/lua/conform/formatters/rubocop.lua
new file mode 100644
index 0000000..492f379
--- /dev/null
+++ b/lua/conform/formatters/rubocop.lua
@@ -0,0 +1,16 @@
+---@type conform.FormatterConfig
+return {
+ meta = {
+ url = "https://github.com/rubocop/rubocop",
+ description = "Ruby static code analyzer and formatter, based on the community Ruby style guide.",
+ },
+ command = "rubocop",
+ args = {
+ "-a",
+ "-f",
+ "quiet",
+ "--stderr",
+ "--stdin",
+ "$FILENAME",
+ },
+}
diff --git a/lua/conform/formatters/rustfmt.lua b/lua/conform/formatters/rustfmt.lua
new file mode 100644
index 0000000..7b5e322
--- /dev/null
+++ b/lua/conform/formatters/rustfmt.lua
@@ -0,0 +1,9 @@
+---@type conform.FormatterConfig
+return {
+ meta = {
+ url = "https://github.com/rust-lang/rustfmt",
+ description = "A tool for formatting rust code according to style guidelines.",
+ },
+ command = "rustfmt",
+ args = { "--emit=stdout" },
+}
diff --git a/lua/conform/formatters/scalafmt.lua b/lua/conform/formatters/scalafmt.lua
new file mode 100644
index 0000000..2b9e451
--- /dev/null
+++ b/lua/conform/formatters/scalafmt.lua
@@ -0,0 +1,9 @@
+---@type conform.FormatterConfig
+return {
+ meta = {
+ url = "https://github.com/scalameta/scalafmt",
+ description = "Code formatter for Scala.",
+ },
+ command = "scalafmt",
+ args = { "--stdin" },
+}
diff --git a/lua/conform/formatters/shfmt.lua b/lua/conform/formatters/shfmt.lua
new file mode 100644
index 0000000..6e40f0c
--- /dev/null
+++ b/lua/conform/formatters/shfmt.lua
@@ -0,0 +1,9 @@
+---@type conform.FormatterConfig
+return {
+ meta = {
+ url = "https://github.com/mvdan/sh",
+ description = "A shell parser, formatter, and interpreter with `bash` support.",
+ },
+ command = "shfmt",
+ args = { "-filename", "$FILENAME" },
+}
diff --git a/lua/conform/formatters/sql_formatter.lua b/lua/conform/formatters/sql_formatter.lua
new file mode 100644
index 0000000..bd92851
--- /dev/null
+++ b/lua/conform/formatters/sql_formatter.lua
@@ -0,0 +1,8 @@
+---@type conform.FormatterConfig
+return {
+ meta = {
+ url = "https://github.com/sql-formatter-org/sql-formatter",
+ description = "A whitespace formatter for different query languages.",
+ },
+ command = "sql-formatter",
+}
diff --git a/lua/conform/formatters/stylua.lua b/lua/conform/formatters/stylua.lua
new file mode 100644
index 0000000..f2cc412
--- /dev/null
+++ b/lua/conform/formatters/stylua.lua
@@ -0,0 +1,9 @@
+---@type conform.FormatterConfig
+return {
+ meta = {
+ url = "https://github.com/JohnnyMorganz/StyLua",
+ description = "An opinionated code formatter for Lua.",
+ },
+ command = "stylua",
+ args = { "--search-parent-directories", "--stdin-filepath", "$FILENAME", "-" },
+}
diff --git a/lua/conform/formatters/swift_format.lua b/lua/conform/formatters/swift_format.lua
new file mode 100644
index 0000000..2b81297
--- /dev/null
+++ b/lua/conform/formatters/swift_format.lua
@@ -0,0 +1,8 @@
+---@type conform.FormatterConfig
+return {
+ meta = {
+ url = "https://github.com/apple/swift-format",
+ description = "Swift formatter from apple. Requires building from source with `swift build`.",
+ },
+ command = "swift-format",
+}
diff --git a/lua/conform/formatters/swiftformat.lua b/lua/conform/formatters/swiftformat.lua
new file mode 100644
index 0000000..821a010
--- /dev/null
+++ b/lua/conform/formatters/swiftformat.lua
@@ -0,0 +1,9 @@
+---@type conform.FormatterConfig
+return {
+ meta = {
+ url = "https://github.com/nicklockwood/SwiftFormat",
+ description = "SwiftFormat is a code library and command-line tool for reformatting `swift` code on macOS or Linux.",
+ },
+ command = "swiftformat",
+ args = { "--stdinpath", "$FILENAME" },
+}
diff --git a/lua/conform/formatters/terraform_fmt.lua b/lua/conform/formatters/terraform_fmt.lua
new file mode 100644
index 0000000..44edc55
--- /dev/null
+++ b/lua/conform/formatters/terraform_fmt.lua
@@ -0,0 +1,9 @@
+---@type conform.FormatterConfig
+return {
+ meta = {
+ url = "https://www.terraform.io/docs/cli/commands/fmt.html",
+ description = "The terraform-fmt command rewrites `terraform` configuration files to a canonical format and style.",
+ },
+ command = "terraform",
+ args = { "fmt", "-" },
+}
diff --git a/lua/conform/formatters/uncrustify.lua b/lua/conform/formatters/uncrustify.lua
new file mode 100644
index 0000000..3430063
--- /dev/null
+++ b/lua/conform/formatters/uncrustify.lua
@@ -0,0 +1,11 @@
+---@type conform.FormatterConfig
+return {
+ meta = {
+ url = "https://github.com/uncrustify/uncrustify",
+ description = "A source code beautifier for C, C++, C#, ObjectiveC, D, Java, Pawn and Vala.",
+ },
+ command = "uncrustify",
+ args = function(ctx)
+ return { "-q", "-l", vim.bo[ctx.buf].filetype:upper() }
+ end,
+}
diff --git a/lua/conform/formatters/xmlformat.lua b/lua/conform/formatters/xmlformat.lua
new file mode 100644
index 0000000..25b48e2
--- /dev/null
+++ b/lua/conform/formatters/xmlformat.lua
@@ -0,0 +1,9 @@
+---@type conform.FormatterConfig
+return {
+ meta = {
+ url = "https://github.com/pamoller/xmlformatter",
+ description = "xmlformatter is an Open Source Python package, which provides formatting of XML documents.",
+ },
+ command = "xmlformat",
+ args = { "-" },
+}
diff --git a/lua/conform/formatters/yamlfix.lua b/lua/conform/formatters/yamlfix.lua
new file mode 100644
index 0000000..1b00e01
--- /dev/null
+++ b/lua/conform/formatters/yamlfix.lua
@@ -0,0 +1,9 @@
+---@type conform.FormatterConfig
+return {
+ meta = {
+ url = "https://github.com/lyz-code/yamlfix",
+ description = "A configurable YAML formatter that keeps comments.",
+ },
+ command = "yamlfix",
+ args = { "-" },
+}
diff --git a/lua/conform/formatters/yamlfmt.lua b/lua/conform/formatters/yamlfmt.lua
new file mode 100644
index 0000000..56c6cb6
--- /dev/null
+++ b/lua/conform/formatters/yamlfmt.lua
@@ -0,0 +1,9 @@
+---@type conform.FormatterConfig
+return {
+ meta = {
+ url = "https://github.com/google/yamlfmt",
+ description = "yamlfmt is an extensible command line tool or library to format yaml files.",
+ },
+ command = "yamlfmt",
+ args = { "-" },
+}
diff --git a/lua/conform/formatters/yapf.lua b/lua/conform/formatters/yapf.lua
new file mode 100644
index 0000000..5d7e866
--- /dev/null
+++ b/lua/conform/formatters/yapf.lua
@@ -0,0 +1,9 @@
+---@type conform.FormatterConfig
+return {
+ meta = {
+ url = "https://github.com/google/yapf",
+ description = "Yet Another Python Formatter.",
+ },
+ command = "yapf",
+ args = { "--quiet" },
+}
diff --git a/lua/conform/formatters/zigfmt.lua b/lua/conform/formatters/zigfmt.lua
new file mode 100644
index 0000000..9c93c0b
--- /dev/null
+++ b/lua/conform/formatters/zigfmt.lua
@@ -0,0 +1,9 @@
+---@type conform.FormatterConfig
+return {
+ meta = {
+ url = "https://github.com/ziglang/zig",
+ description = "Reformat Zig source into canonical form.",
+ },
+ command = "zig",
+ args = { "fmt", "--stdin" },
+}
diff --git a/lua/conform/fs.lua b/lua/conform/fs.lua
new file mode 100644
index 0000000..d303dbd
--- /dev/null
+++ b/lua/conform/fs.lua
@@ -0,0 +1,18 @@
+local M = {}
+
+local uv = vim.uv or vim.loop
+
+---@type boolean
+M.is_windows = uv.os_uname().version:match("Windows")
+
+M.is_mac = uv.os_uname().sysname == "Darwin"
+
+---@type string
+M.sep = M.is_windows and "\\" or "/"
+
+---@param ... string
+M.join = function(...)
+ return table.concat({ ... }, M.sep)
+end
+
+return M
diff --git a/lua/conform/health.lua b/lua/conform/health.lua
new file mode 100644
index 0000000..76309be
--- /dev/null
+++ b/lua/conform/health.lua
@@ -0,0 +1,34 @@
+local M = {}
+
+M.check = function()
+ local conform = require("conform")
+ vim.health.report_start("conform.nvim report")
+
+ local log = require("conform.log")
+ vim.health.info(string.format("Log file: %s", log.get_logfile()))
+
+ local all_formatters = conform.list_all_formatters()
+ for _, formatter in ipairs(all_formatters) do
+ if not formatter.available then
+ vim.health.report_warn(
+ string.format("%s unavailable: %s", formatter.name, formatter.available_msg)
+ )
+ else
+ local filetypes = {}
+ for filetype, formatters in pairs(conform.formatters_by_ft) do
+ if not vim.tbl_islist(formatters) then
+ formatters = formatters.formatters
+ end
+ if vim.tbl_contains(formatters, formatter.name) then
+ table.insert(filetypes, filetype)
+ end
+ end
+
+ vim.health.report_ok(
+ string.format("%s ready (%s)", formatter.name, table.concat(filetypes, ", "))
+ )
+ end
+ end
+end
+
+return M
diff --git a/lua/conform/init.lua b/lua/conform/init.lua
new file mode 100644
index 0000000..f721587
--- /dev/null
+++ b/lua/conform/init.lua
@@ -0,0 +1,291 @@
+local M = {}
+
+---@class (exact) conform.FormatterInfo
+---@field name string
+---@field command string
+---@field cwd? string
+---@field available boolean
+---@field available_msg? string
+
+---@class (exact) conform.FormatterConfig
+---@field meta conform.FormatterMeta
+---@field command string|fun(ctx: conform.Context): string
+---@field args? string[]|fun(ctx: conform.Context): string[]
+---@field cwd? fun(ctx: conform.Context): nil|string
+---@field require_cwd? boolean When cwd is not found, don't run the formatter (default false)
+---@field stdin? boolean Send buffer contents to stdin (default true)
+---@field condition? fun(ctx: conform.Context): boolean
+---@field exit_codes? integer[] Exit codes that indicate success (default {0})
+
+---@class (exact) conform.FormatterMeta
+---@field url string
+---@field description string
+---
+---@class (exact) conform.Context
+---@field buf integer
+---@field filename string
+---@field dirname string
+
+---@class (exact) conform.RunOptions
+---@field run_all_formatters nil|boolean Run all listed formatters instead of stopping at the first one.
+---@field format_on_save nil|boolean Run these formatters in the built-in format_on_save autocmd.
+
+---@class (exact) conform.FormatterList : conform.RunOptions
+---@field formatters string[]
+
+---@type table<string, string[]|conform.FormatterList>
+M.formatters_by_ft = {}
+
+---@type table<string, conform.FormatterConfig|fun(): conform.FormatterConfig>
+M.formatters = {}
+
+M.setup = function(opts)
+ opts = opts or {}
+
+ M.formatters = vim.tbl_extend("force", M.formatters, opts.formatters or {})
+ M.formatters_by_ft = vim.tbl_extend("force", M.formatters_by_ft, opts.formatters_by_ft or {})
+
+ if opts.log_level then
+ require("conform.log").level = opts.log_level
+ end
+
+ if opts.format_on_save then
+ if type(opts.format_on_save) == "boolean" then
+ opts.format_on_save = {}
+ end
+ local aug = vim.api.nvim_create_augroup("Conform", { clear = true })
+ vim.api.nvim_create_autocmd("BufWritePre", {
+ pattern = "*",
+ group = aug,
+ callback = function(args)
+ local format_opts = vim.tbl_deep_extend("keep", opts.format_on_save, {
+ buf = args.buf,
+ })
+ local filetypes = vim.split(vim.bo[args.buf].filetype, ".", { plain = true })
+ for _, ft in ipairs(filetypes) do
+ local ft_formatters = M.formatters_by_ft[ft]
+ if ft_formatters and ft_formatters.format_on_save == false then
+ return
+ end
+ end
+ M.format(format_opts)
+ end,
+ })
+ end
+end
+
+---Format a buffer
+---@param opts? table
+--- timeout_ms nil|integer Time in milliseconds to block for formatting. Defaults to 1000. No effect if async = true.
+--- bufnr nil|integer Format this buffer (default 0)
+--- async nil|boolean If true the method won't block. Defaults to false.
+--- formatters nil|string[] List of formatters to run. Defaults to all formatters for the buffer filetype.
+--- lsp_fallback nil|boolean Attempt LSP formatting if no formatters are available. Defaults to false.
+---@return boolean True if any formatters were attempted
+M.format = function(opts)
+ opts = vim.tbl_extend("keep", opts or {}, {
+ timeout_ms = 1000,
+ bufnr = 0,
+ async = false,
+ lsp_fallback = false,
+ })
+
+ local formatters = {}
+ if opts.formatters then
+ for _, formatter in ipairs(opts.formatters) do
+ local info = M.get_formatter_info(formatter)
+ if info.available then
+ table.insert(formatters, info)
+ else
+ vim.notify(
+ string.format("Formatter '%s' unavailable: %s", info.name, info.available_msg),
+ vim.log.levels.WARN
+ )
+ end
+ end
+ else
+ formatters = M.list_formatters(opts.bufnr)
+ end
+ local any_formatters = not vim.tbl_isempty(formatters)
+ if any_formatters then
+ if opts.async then
+ require("conform.runner").format_async(opts.bufnr, formatters)
+ else
+ require("conform.runner").format_sync(opts.bufnr, formatters, opts.timeout_ms)
+ end
+ end
+
+ if not any_formatters and opts.lsp_fallback then
+ local supports_lsp_formatting = false
+ for _, client in ipairs(vim.lsp.get_active_clients({ bufnr = opts.bufnr })) do
+ if client.server_capabilities.documentFormattingProvider then
+ supports_lsp_formatting = true
+ break
+ end
+ end
+
+ if supports_lsp_formatting then
+ local restore = require("conform.util").save_win_positions(opts.bufnr)
+ vim.lsp.buf.format(opts)
+ restore()
+ end
+ else
+ vim.notify("No formatters found for buffer. See :checkhealth conform", vim.log.levels.WARN)
+ end
+
+ return any_formatters
+end
+
+---Retried the available formatters for a buffer
+---@param bufnr? integer
+---@return conform.FormatterInfo[]
+M.list_formatters = function(bufnr)
+ if not bufnr or bufnr == 0 then
+ bufnr = vim.api.nvim_get_current_buf()
+ end
+ local formatters = {}
+ local run_options = {
+ run_all_formatters = false,
+ format_on_save = true,
+ }
+ local filetypes = vim.split(vim.bo[bufnr].filetype, ".", { plain = true })
+ table.insert(filetypes, "*")
+ for _, filetype in ipairs(filetypes) do
+ local ft_formatters = M.formatters_by_ft[filetype]
+ if ft_formatters then
+ if not vim.tbl_islist(ft_formatters) then
+ for k, v in pairs(ft_formatters) do
+ if k ~= "formatters" then
+ run_options[k] = v
+ end
+ end
+ ft_formatters = ft_formatters.formatters
+ end
+ for _, formatter in ipairs(ft_formatters) do
+ formatters[formatter] = true
+ end
+ end
+ end
+
+ ---@type conform.FormatterInfo[]
+ local all_info = {}
+ for formatter in pairs(formatters) do
+ local info = M.get_formatter_info(formatter)
+ if info.available then
+ table.insert(all_info, assert(info))
+ if not run_options.run_all_formatters then
+ break
+ end
+ else
+ vim.notify_once(
+ string.format("conform.nvim: missing configuration for formatter '%s'", formatter),
+ vim.log.levels.WARN
+ )
+ end
+ end
+
+ return all_info
+end
+
+---List information about all filetype-configured formatters
+---@return conform.FormatterInfo[]
+M.list_all_formatters = function()
+ local formatters = {}
+ for _, ft_formatters in pairs(M.formatters_by_ft) do
+ if not vim.tbl_islist(ft_formatters) then
+ ft_formatters = ft_formatters.formatters
+ end
+ for _, formatter in ipairs(ft_formatters) do
+ formatters[formatter] = true
+ end
+ end
+
+ ---@type conform.FormatterInfo[]
+ local all_info = {}
+ for formatter in pairs(formatters) do
+ local info = M.get_formatter_info(formatter)
+ table.insert(all_info, info)
+ end
+
+ table.sort(all_info, function(a, b)
+ return a.name < b.name
+ end)
+ return all_info
+end
+
+---@private
+---@param formatter string
+---@return nil|conform.FormatterConfig
+M.get_formatter_config = function(formatter)
+ local config = M.formatters[formatter]
+ if not config then
+ local ok
+ ok, config = pcall(require, "conform.formatters." .. formatter)
+ if not ok then
+ return nil
+ end
+ end
+ if type(config) == "function" then
+ config = config()
+ end
+
+ if config.stdin == nil then
+ config.stdin = true
+ end
+ return config
+end
+
+---@private
+---@param formatter string
+---@param bufnr? integer
+---@return conform.FormatterInfo
+M.get_formatter_info = function(formatter, bufnr)
+ if not bufnr or bufnr == 0 then
+ bufnr = vim.api.nvim_get_current_buf()
+ end
+ local config = M.get_formatter_config(formatter)
+ if not config then
+ return {
+ name = formatter,
+ command = formatter,
+ available = false,
+ available_msg = "No config found",
+ }
+ end
+
+ local ctx = require("conform.runner").build_context(bufnr, config)
+
+ local command = config.command
+ if type(command) == "function" then
+ command = command(ctx)
+ end
+
+ local available = true
+ local available_msg = nil
+ if vim.fn.executable(command) == 0 then
+ available = false
+ available_msg = "Command not found"
+ elseif config.condition and not config.condition(ctx) then
+ available = false
+ available_msg = "Condition failed"
+ end
+ local cwd = nil
+ if config.cwd then
+ cwd = config.cwd(ctx)
+ if available and not cwd and config.require_cwd then
+ available = false
+ available_msg = "Root directory not found"
+ end
+ end
+
+ ---@type conform.FormatterInfo
+ return {
+ name = formatter,
+ command = command,
+ cwd = cwd,
+ available = available,
+ available_msg = available_msg,
+ }
+end
+
+return M
diff --git a/lua/conform/log.lua b/lua/conform/log.lua
new file mode 100644
index 0000000..b42d1f8
--- /dev/null
+++ b/lua/conform/log.lua
@@ -0,0 +1,108 @@
+local uv = vim.uv or vim.loop
+local levels = vim.deepcopy(vim.log.levels)
+vim.tbl_add_reverse_lookup(levels)
+
+local Log = {}
+
+---@type integer
+Log.level = vim.log.levels.ERROR
+
+---@return string
+Log.get_logfile = function()
+ local fs = require("conform.fs")
+
+ local ok, stdpath = pcall(vim.fn.stdpath, "log")
+ if not ok then
+ stdpath = vim.fn.stdpath("cache")
+ end
+ return fs.join(stdpath, "conform.log")
+end
+
+---@param level integer
+---@param msg string
+---@param ... any[]
+---@return string
+local function format(level, msg, ...)
+ local args = vim.F.pack_len(...)
+ for i = 1, args.n do
+ local v = args[i]
+ if type(v) == "table" then
+ args[i] = vim.inspect(v)
+ elseif v == nil then
+ args[i] = "nil"
+ end
+ end
+ local ok, text = pcall(string.format, msg, vim.F.unpack_len(args))
+ if ok then
+ local str_level = levels[level]
+ return string.format("[%s] %s", str_level, text)
+ else
+ return string.format("[ERROR] error formatting log line: '%s' args %s", msg, vim.inspect(args))
+ end
+end
+
+---@param line string
+local function write(line)
+ -- This will be replaced during initialization
+end
+
+local initialized = false
+local function initialize()
+ if initialized then
+ return
+ end
+ initialized = true
+ local filepath = Log.get_logfile()
+
+ local stat = uv.fs_stat(filepath)
+ if stat and stat.size > 10 * 1024 * 1024 then
+ local backup = filepath .. ".1"
+ uv.fs_unlink(backup)
+ uv.fs_rename(filepath, backup)
+ end
+
+ local parent = vim.fs.dirname(filepath)
+ vim.fn.mkdir(parent, "p")
+
+ local logfile, openerr = io.open(filepath, "a+")
+ if not logfile then
+ local err_msg = string.format("Failed to open conform.nvim log file: %s", openerr)
+ vim.notify(err_msg, vim.log.levels.ERROR)
+ else
+ write = function(line)
+ logfile:write(line)
+ logfile:write("\n")
+ logfile:flush()
+ end
+ end
+end
+
+function Log.log(level, msg, ...)
+ if Log.level <= level then
+ initialize()
+ local text = format(level, msg, ...)
+ write(text)
+ end
+end
+
+function Log.trace(...)
+ Log.log(vim.log.levels.TRACE, ...)
+end
+
+function Log.debug(...)
+ Log.log(vim.log.levels.DEBUG, ...)
+end
+
+function Log.info(...)
+ Log.log(vim.log.levels.INFO, ...)
+end
+
+function Log.warn(...)
+ Log.log(vim.log.levels.WARN, ...)
+end
+
+function Log.error(...)
+ Log.log(vim.log.levels.ERROR, ...)
+end
+
+return Log
diff --git a/lua/conform/runner.lua b/lua/conform/runner.lua
new file mode 100644
index 0000000..fd806ce
--- /dev/null
+++ b/lua/conform/runner.lua
@@ -0,0 +1,283 @@
+local fs = require("conform.fs")
+local log = require("conform.log")
+local util = require("conform.util")
+local uv = vim.uv or vim.loop
+local M = {}
+
+---@param ctx conform.Context
+---@param config conform.FormatterConfig
+local function build_cmd(ctx, config)
+ local command = config.command
+ if type(command) == "function" then
+ command = command(ctx)
+ end
+ local cmd = { command }
+ if config.args then
+ local args = config.args
+ if type(config.args) == "function" then
+ args = config.args(ctx)
+ end
+ ---@cast args string[]
+ for _, v in ipairs(args) do
+ if v == "$FILENAME" then
+ v = ctx.filename
+ elseif v == "$DIRNAME" then
+ v = vim.fs.dirname(ctx.filename)
+ end
+ table.insert(cmd, v)
+ end
+ end
+ return cmd
+end
+
+---@param bufnr integer
+---@param original_lines string[]
+---@param new_lines string[]
+local function apply_format(bufnr, original_lines, new_lines)
+ local restore = util.save_win_positions(bufnr)
+
+ local original_text = table.concat(original_lines, "\n")
+ local new_text = table.concat(new_lines, "\n")
+ local indices = vim.diff(original_text, new_text, {
+ result_type = "indices",
+ algorithm = "minimal",
+ })
+ assert(indices)
+ for i = #indices, 1, -1 do
+ local start_a, count_a, start_b, count_b = unpack(indices[i])
+ -- When count_a is 0, the diff is an insert after the line
+ if count_a == 0 then
+ start_a = start_a + 1
+ end
+ local replacement = util.tbl_slice(new_lines, start_b, start_b + count_b - 1)
+ vim.api.nvim_buf_set_lines(bufnr, start_a - 1, start_a - 1 + count_a, true, replacement)
+ end
+
+ restore()
+end
+
+---@param bufnr integer
+---@param formatter conform.FormatterInfo
+---@param input_lines string[]
+---@param callback fun(err?: string, output?: string[])
+---@return integer
+local function run_formatter(bufnr, formatter, input_lines, callback)
+ local config = assert(require("conform").get_formatter_config(formatter.name))
+ local ctx = M.build_context(bufnr, config)
+ local cmd = build_cmd(ctx, config)
+ local cwd = nil
+ if config.cwd then
+ cwd = config.cwd(ctx)
+ end
+ log.info("Running formatter %s on buffer %d", formatter.name, bufnr)
+ if not config.stdin then
+ log.debug("Creating temp file %s", ctx.filename)
+ local fd = assert(uv.fs_open(ctx.filename, "w", 448)) -- 0700
+ uv.fs_write(fd, table.concat(input_lines, "\n"))
+ uv.fs_close(fd)
+ local final_cb = callback
+ callback = function(...)
+ log.debug("Cleaning up temp file %s", ctx.filename)
+ uv.fs_unlink(ctx.filename)
+ final_cb(...)
+ end
+ end
+ log.debug("Running command: %s", cmd)
+ if cwd then
+ log.debug("Running in CWD: %s", cwd)
+ end
+ local stdout
+ local stderr
+ local exit_codes = config.exit_codes or { 0 }
+ local jid = vim.fn.jobstart(cmd, {
+ cwd = cwd,
+ stdout_buffered = true,
+ stderr_buffered = true,
+ stdin = config.stdin and "pipe" or "null",
+ on_stdout = function(_, data)
+ if config.stdin then
+ stdout = data
+ end
+ end,
+ on_stderr = function(_, data)
+ stderr = data
+ end,
+ on_exit = function(_, code)
+ local output
+ if not config.stdin then
+ local fd = assert(uv.fs_open(ctx.filename, "r", 448)) -- 0700
+ local stat = assert(uv.fs_fstat(fd))
+ local content = assert(uv.fs_read(fd, stat.size))
+ uv.fs_close(fd)
+ output = vim.split(content, "\n", { plain = true })
+ else
+ output = stdout
+ end
+ if vim.tbl_contains(exit_codes, code) then
+ log.debug("Formatter %s exited with code %d", formatter.name, code)
+ callback(nil, output)
+ else
+ log.error("Formatter %s exited with code %d", formatter.name, code)
+ log.warn("Formatter %s stdout:", formatter.name, stdout)
+ log.warn("Formatter %s stderr:", formatter.name, stderr)
+ callback(
+ string.format("Formatter '%s' error: %s", formatter.name, table.concat(stderr, "\n"))
+ )
+ end
+ end,
+ })
+ if jid == 0 then
+ callback(string.format("Formatter '%s' invalid arguments", formatter.name))
+ elseif jid == -1 then
+ callback(string.format("Formatter '%s' command is not executable", formatter.name))
+ elseif config.stdin then
+ local text = table.concat(input_lines, "\n")
+ vim.api.nvim_chan_send(jid, text)
+ vim.fn.chanclose(jid, "stdin")
+ end
+ vim.b[bufnr].conform_jid = jid
+
+ return jid
+end
+
+---@param bufnr integer
+---@param config conform.FormatterConfig
+---@return conform.Context
+M.build_context = function(bufnr, config)
+ if bufnr == 0 then
+ bufnr = vim.api.nvim_get_current_buf()
+ end
+ local filename = vim.api.nvim_buf_get_name(bufnr)
+
+ -- Hack around checkhealth. For buffers that are not files, we need to fabricate a filename
+ if vim.bo[bufnr].buftype ~= "" then
+ filename = ""
+ end
+ local dirname
+ if filename == "" then
+ dirname = vim.fn.getcwd()
+ filename = fs.join(dirname, "unnamed_temp")
+ local ft = vim.bo[bufnr].filetype
+ if ft and ft ~= "" then
+ filename = filename .. "." .. ft
+ end
+ else
+ dirname = vim.fs.dirname(filename)
+ end
+
+ if not config.stdin then
+ local basename = vim.fs.basename(filename)
+ local tmpname = string.format(".conform.%d.%s", math.random(1000000, 9999999), basename)
+ local parent = vim.fs.dirname(filename)
+ filename = fs.join(parent, tmpname)
+ end
+ return {
+ buf = bufnr,
+ filename = filename,
+ dirname = dirname,
+ }
+end
+
+---@param bufnr integer
+---@param formatters conform.FormatterInfo[]
+---@param callback? fun(err?: string)
+M.format_async = function(bufnr, formatters, callback)
+ local idx = 1
+ local changedtick = vim.b[bufnr].changedtick
+ local original_lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false)
+ local input_lines = original_lines
+
+ -- kill previous jobs for buffer
+ local prev_jid = vim.b[bufnr].conform_jid
+ if prev_jid then
+ if vim.fn.jobstop(prev_jid) == 1 then
+ log.info("Canceled previous format job for buffer %d", bufnr)
+ end
+ end
+
+ local function run_next_formatter()
+ local formatter = formatters[idx]
+ if not formatter then
+ -- discard formatting if buffer has changed
+ if vim.b[bufnr].changedtick == changedtick then
+ apply_format(bufnr, original_lines, input_lines)
+ else
+ log.warn("Async formatter discarding changes for buffer %d: concurrent modification", bufnr)
+ end
+ if callback then
+ callback()
+ end
+ return
+ end
+ idx = idx + 1
+
+ local jid
+ jid = run_formatter(bufnr, formatter, input_lines, function(err, output)
+ if err then
+ -- Only display the error if the job wasn't canceled
+ if jid == vim.b[bufnr].conform_jid then
+ vim.notify(err, vim.log.levels.ERROR)
+ end
+ if callback then
+ callback(err)
+ end
+ return
+ end
+ input_lines = output
+ run_next_formatter()
+ end)
+ end
+ run_next_formatter()
+end
+
+---@param bufnr integer
+---@param formatters conform.FormatterInfo[]
+---@param timeout_ms integer
+M.format_sync = function(bufnr, formatters, timeout_ms)
+ local start = uv.hrtime() / 1e6
+ local original_lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false)
+ local input_lines = original_lines
+
+ -- kill previous jobs for buffer
+ local prev_jid = vim.b[bufnr].conform_jid
+ if prev_jid then
+ if vim.fn.jobstop(prev_jid) == 1 then
+ log.info("Canceled previous format job for buffer %d", bufnr)
+ end
+ end
+
+ for _, formatter in ipairs(formatters) do
+ local remaining = timeout_ms - (uv.hrtime() / 1e6 - start)
+ local done = false
+ local result = nil
+ run_formatter(bufnr, formatter, input_lines, function(err, output)
+ if err then
+ vim.notify(err, vim.log.levels.ERROR)
+ end
+ done = true
+ result = output
+ end)
+
+ local wait_result, wait_reason = vim.wait(remaining, function()
+ return done
+ end, 5)
+
+ if not wait_result then
+ if wait_reason == -1 then
+ vim.notify(string.format("Formatter '%s' timed out", formatter.name), vim.log.levels.WARN)
+ end
+ return
+ end
+
+ if not result then
+ return
+ end
+
+ input_lines = result
+ end
+
+ local final_result = input_lines
+ apply_format(bufnr, original_lines, final_result)
+end
+
+return M
diff --git a/lua/conform/util.lua b/lua/conform/util.lua
new file mode 100644
index 0000000..8cbc013
--- /dev/null
+++ b/lua/conform/util.lua
@@ -0,0 +1,75 @@
+local M = {}
+
+---@param cmd string
+---@return fun(ctx: conform.Context): string
+M.from_node_modules = function(cmd)
+ return function(ctx)
+ local fs = require("conform.fs")
+ local found =
+ vim.fs.find("node_modules", { upward = true, type = "directory", path = ctx.dirname })
+ for _, dir in ipairs(found) do
+ local executable = fs.join(dir, ".bin", cmd)
+ if vim.fn.executable(executable) == 1 then
+ return executable
+ end
+ end
+ return cmd
+ end
+end
+
+---@param files string|string[]
+---@return fun(ctx: conform.Context): nil|string
+M.root_file = function(files)
+ return function(ctx)
+ local found = vim.fs.find(files, { upward = true, path = ctx.dirname })[1]
+ if found then
+ return vim.fs.dirname(found)
+ end
+ end
+end
+
+---@param bufnr? integer
+---@return fun() Function that restores the window positions
+M.save_win_positions = function(bufnr)
+ if bufnr == nil or bufnr == 0 then
+ bufnr = vim.api.nvim_get_current_buf()
+ end
+ local win_positions = {}
+ for _, winid in ipairs(vim.api.nvim_list_wins()) do
+ if vim.api.nvim_win_get_buf(winid) == bufnr then
+ vim.api.nvim_win_call(winid, function()
+ local view = vim.fn.winsaveview()
+ win_positions[winid] = view
+ end)
+ end
+ end
+
+ return function()
+ for winid, view in pairs(win_positions) do
+ vim.api.nvim_win_call(winid, function()
+ pcall(vim.fn.winrestview, view)
+ end)
+ end
+ end
+end
+
+---@generic T : any
+---@param tbl T[]
+---@param start_idx? number
+---@param end_idx? number
+---@return T[]
+M.tbl_slice = function(tbl, start_idx, end_idx)
+ local ret = {}
+ if not start_idx then
+ start_idx = 1
+ end
+ if not end_idx then
+ end_idx = #tbl
+ end
+ for i = start_idx, end_idx do
+ table.insert(ret, tbl[i])
+ end
+ return ret
+end
+
+return M