diff options
author | Steven Arcangeli <stevearc@stevearc.com> | 2023-08-28 18:28:07 -0700 |
---|---|---|
committer | Steven Arcangeli <stevearc@stevearc.com> | 2023-08-28 18:28:07 -0700 |
commit | cddd536e087a9fd3d2c9ea5b0a44e46c7b4b54c2 (patch) | |
tree | 70f6868440596ae90b7f451379c3abfa5678849c /lua | |
parent | 69c4495ab5ad3c07c3a4f3c2bcac2f070718b4cb (diff) |
feat: range formatting
Should work the same as vim.lsp.buf.format(). Additionally, range
formatting is supported for *any* formatter. If the formatter doesn't
have native support for ranges, conform will do its best to only apply the
diffs that affect that range.
Diffstat (limited to 'lua')
43 files changed, 211 insertions, 73 deletions
diff --git a/lua/conform/formatters/autoflake.lua b/lua/conform/formatters/autoflake.lua index 04daf71..c7d0a3b 100644 --- a/lua/conform/formatters/autoflake.lua +++ b/lua/conform/formatters/autoflake.lua @@ -1,4 +1,4 @@ ----@type conform.FormatterConfig +---@type conform.FileFormatterConfig return { meta = { url = "https://github.com/PyCQA/autoflake", diff --git a/lua/conform/formatters/autopep8.lua b/lua/conform/formatters/autopep8.lua index 5ed2f83..3945e47 100644 --- a/lua/conform/formatters/autopep8.lua +++ b/lua/conform/formatters/autopep8.lua @@ -1,4 +1,4 @@ ----@type conform.FormatterConfig +---@type conform.FileFormatterConfig return { meta = { url = "https://github.com/hhatto/autopep8", @@ -6,4 +6,7 @@ return { }, command = "autopep8", args = { "-" }, + range_args = function(ctx) + return { "-", "--line-range", tostring(ctx.range.start[1]), tostring(ctx.range["end"][1]) } + end, } diff --git a/lua/conform/formatters/black.lua b/lua/conform/formatters/black.lua index 0d892a2..e27ed90 100644 --- a/lua/conform/formatters/black.lua +++ b/lua/conform/formatters/black.lua @@ -1,5 +1,5 @@ local util = require("conform.util") ----@type conform.FormatterConfig +---@type conform.FileFormatterConfig return { meta = { url = "https://github.com/psf/black", diff --git a/lua/conform/formatters/clang_format.lua b/lua/conform/formatters/clang_format.lua index d2c6d41..44af877 100644 --- a/lua/conform/formatters/clang_format.lua +++ b/lua/conform/formatters/clang_format.lua @@ -1,4 +1,5 @@ ----@type conform.FormatterConfig +local util = require("conform.util") +---@type conform.FileFormatterConfig return { meta = { url = "https://www.kernel.org/doc/html/latest/process/clang-format.html", @@ -6,4 +7,16 @@ return { }, command = "clang-format", args = { "-assume-filename", "$FILENAME" }, + range_args = function(ctx) + local start_offset, end_offset = util.get_offsets_from_range(ctx.buf, ctx.range) + local length = end_offset - start_offset + return { + "-assume-filename", + "$FILENAME", + "--offset", + tostring(start_offset), + "--length", + tostring(length), + } + end, } diff --git a/lua/conform/formatters/cljstyle.lua b/lua/conform/formatters/cljstyle.lua index ffd8061..21205a5 100644 --- a/lua/conform/formatters/cljstyle.lua +++ b/lua/conform/formatters/cljstyle.lua @@ -1,4 +1,4 @@ ----@type conform.FormatterConfig +---@type conform.FileFormatterConfig return { meta = { url = "https://github.com/greglook/cljstyle", diff --git a/lua/conform/formatters/cmake_format.lua b/lua/conform/formatters/cmake_format.lua index 563bcae..0e98108 100644 --- a/lua/conform/formatters/cmake_format.lua +++ b/lua/conform/formatters/cmake_format.lua @@ -1,4 +1,4 @@ ----@type conform.FormatterConfig +---@type conform.FileFormatterConfig return { meta = { url = "https://github.com/cheshirekow/cmake_format", diff --git a/lua/conform/formatters/dart_format.lua b/lua/conform/formatters/dart_format.lua index eb9b39c..e110b06 100644 --- a/lua/conform/formatters/dart_format.lua +++ b/lua/conform/formatters/dart_format.lua @@ -1,4 +1,4 @@ ----@type conform.FormatterConfig +---@type conform.FileFormatterConfig return { meta = { url = "https://dart.dev/tools/dart-format", diff --git a/lua/conform/formatters/dfmt.lua b/lua/conform/formatters/dfmt.lua index 49c99cb..41dd667 100644 --- a/lua/conform/formatters/dfmt.lua +++ b/lua/conform/formatters/dfmt.lua @@ -1,4 +1,4 @@ ----@type conform.FormatterConfig +---@type conform.FileFormatterConfig return { meta = { url = "https://github.com/dlang-community/dfmt", diff --git a/lua/conform/formatters/elm_format.lua b/lua/conform/formatters/elm_format.lua index 23f1408..5b0db5c 100644 --- a/lua/conform/formatters/elm_format.lua +++ b/lua/conform/formatters/elm_format.lua @@ -1,4 +1,4 @@ ----@type conform.FormatterConfig +---@type conform.FileFormatterConfig return { meta = { url = "https://github.com/avh4/elm-format", diff --git a/lua/conform/formatters/erb_format.lua b/lua/conform/formatters/erb_format.lua index dad08ca..65d7d10 100644 --- a/lua/conform/formatters/erb_format.lua +++ b/lua/conform/formatters/erb_format.lua @@ -1,4 +1,4 @@ ----@type conform.FormatterConfig +---@type conform.FileFormatterConfig return { meta = { url = "https://github.com/nebulab/erb-formatter", diff --git a/lua/conform/formatters/eslint_d.lua b/lua/conform/formatters/eslint_d.lua index e036aae..e7a5227 100644 --- a/lua/conform/formatters/eslint_d.lua +++ b/lua/conform/formatters/eslint_d.lua @@ -1,5 +1,5 @@ local util = require("conform.util") ----@type conform.FormatterConfig +---@type conform.FileFormatterConfig return { meta = { url = "https://github.com/mantoni/eslint_d.js/", diff --git a/lua/conform/formatters/fish_indent.lua b/lua/conform/formatters/fish_indent.lua index 1501072..7e10ed4 100644 --- a/lua/conform/formatters/fish_indent.lua +++ b/lua/conform/formatters/fish_indent.lua @@ -1,4 +1,4 @@ ----@type conform.FormatterConfig +---@type conform.FileFormatterConfig return { meta = { url = "https://fishshell.com/docs/current/cmds/fish_indent.html", diff --git a/lua/conform/formatters/gdformat.lua b/lua/conform/formatters/gdformat.lua index 914bb89..d180f12 100644 --- a/lua/conform/formatters/gdformat.lua +++ b/lua/conform/formatters/gdformat.lua @@ -1,4 +1,4 @@ ----@type conform.FormatterConfig +---@type conform.FileFormatterConfig return { meta = { url = "https://github.com/Scony/godot-gdscript-toolkit", diff --git a/lua/conform/formatters/gofmt.lua b/lua/conform/formatters/gofmt.lua index b0b81c3..2bfd6b4 100644 --- a/lua/conform/formatters/gofmt.lua +++ b/lua/conform/formatters/gofmt.lua @@ -1,4 +1,4 @@ ----@type conform.FormatterConfig +---@type conform.FileFormatterConfig return { meta = { url = "https://pkg.go.dev/cmd/gofmt", diff --git a/lua/conform/formatters/gofumpt.lua b/lua/conform/formatters/gofumpt.lua index 79fb4dc..2d4f9a5 100644 --- a/lua/conform/formatters/gofumpt.lua +++ b/lua/conform/formatters/gofumpt.lua @@ -1,4 +1,4 @@ ----@type conform.FormatterConfig +---@type conform.FileFormatterConfig return { meta = { url = "https://github.com/mvdan/gofumpt", diff --git a/lua/conform/formatters/goimports.lua b/lua/conform/formatters/goimports.lua index 2361e43..64efea6 100644 --- a/lua/conform/formatters/goimports.lua +++ b/lua/conform/formatters/goimports.lua @@ -1,4 +1,4 @@ ----@type conform.FormatterConfig +---@type conform.FileFormatterConfig return { meta = { url = "https://pkg.go.dev/golang.org/x/tools/cmd/goimports", diff --git a/lua/conform/formatters/htmlbeautifier.lua b/lua/conform/formatters/htmlbeautifier.lua index 3182f99..73b9275 100644 --- a/lua/conform/formatters/htmlbeautifier.lua +++ b/lua/conform/formatters/htmlbeautifier.lua @@ -1,4 +1,4 @@ ----@type conform.FormatterConfig +---@type conform.FileFormatterConfig return { meta = { url = "https://github.com/threedaymonk/htmlbeautifier", diff --git a/lua/conform/formatters/isort.lua b/lua/conform/formatters/isort.lua index f6c6e3d..9b02c4e 100644 --- a/lua/conform/formatters/isort.lua +++ b/lua/conform/formatters/isort.lua @@ -1,5 +1,5 @@ local util = require("conform.util") ----@type conform.FormatterConfig +---@type conform.FileFormatterConfig return { meta = { url = "https://github.com/PyCQA/isort", diff --git a/lua/conform/formatters/jq.lua b/lua/conform/formatters/jq.lua index 50a905d..061ed03 100644 --- a/lua/conform/formatters/jq.lua +++ b/lua/conform/formatters/jq.lua @@ -1,4 +1,4 @@ ----@type conform.FormatterConfig +---@type conform.FileFormatterConfig return { meta = { url = "https://github.com/stedolan/jq", diff --git a/lua/conform/formatters/nixfmt.lua b/lua/conform/formatters/nixfmt.lua index 6c5001a..27029d5 100644 --- a/lua/conform/formatters/nixfmt.lua +++ b/lua/conform/formatters/nixfmt.lua @@ -1,4 +1,4 @@ ----@type conform.FormatterConfig +---@type conform.FileFormatterConfig return { meta = { url = "https://github.com/serokell/nixfmt", diff --git a/lua/conform/formatters/nixpkgs_fmt.lua b/lua/conform/formatters/nixpkgs_fmt.lua index 6685fe8..9ca6a8a 100644 --- a/lua/conform/formatters/nixpkgs_fmt.lua +++ b/lua/conform/formatters/nixpkgs_fmt.lua @@ -1,4 +1,4 @@ ----@type conform.FormatterConfig +---@type conform.FileFormatterConfig return { meta = { url = "https://github.com/nix-community/nixpkgs-fmt", diff --git a/lua/conform/formatters/ocamlformat.lua b/lua/conform/formatters/ocamlformat.lua index 4ea1b49..0305083 100644 --- a/lua/conform/formatters/ocamlformat.lua +++ b/lua/conform/formatters/ocamlformat.lua @@ -1,4 +1,4 @@ ----@type conform.FormatterConfig +---@type conform.FileFormatterConfig return { meta = { url = "https://github.com/ocaml-ppx/ocamlformat", diff --git a/lua/conform/formatters/pg_format.lua b/lua/conform/formatters/pg_format.lua index 87aff66..f016454 100644 --- a/lua/conform/formatters/pg_format.lua +++ b/lua/conform/formatters/pg_format.lua @@ -1,4 +1,4 @@ ----@type conform.FormatterConfig +---@type conform.FileFormatterConfig return { meta = { url = "https://github.com/darold/pgFormatter", diff --git a/lua/conform/formatters/prettier.lua b/lua/conform/formatters/prettier.lua index 6f4bbfb..ea45ffb 100644 --- a/lua/conform/formatters/prettier.lua +++ b/lua/conform/formatters/prettier.lua @@ -1,5 +1,5 @@ local util = require("conform.util") ----@type conform.FormatterConfig +---@type conform.FileFormatterConfig return { meta = { url = "https://github.com/prettier/prettier", @@ -7,6 +7,10 @@ return { }, command = util.from_node_modules("prettier"), args = { "--stdin-filepath", "$FILENAME" }, + range_args = function(ctx) + local start_offset, end_offset = util.get_offsets_from_range(ctx.buf, ctx.range) + return { "$FILENAME", "--range-start=" .. start_offset, "--range-end=" .. end_offset } + end, cwd = util.root_file({ -- https://prettier.io/docs/en/configuration.html ".prettierrc", diff --git a/lua/conform/formatters/prettierd.lua b/lua/conform/formatters/prettierd.lua index 0af6baf..3cdc19e 100644 --- a/lua/conform/formatters/prettierd.lua +++ b/lua/conform/formatters/prettierd.lua @@ -1,5 +1,5 @@ local util = require("conform.util") ----@type conform.FormatterConfig +---@type conform.FileFormatterConfig return { meta = { url = "https://github.com/fsouza/prettierd", @@ -7,6 +7,10 @@ return { }, command = util.from_node_modules("prettierd"), args = { "$FILENAME" }, + range_args = function(ctx) + local start_offset, end_offset = util.get_offsets_from_range(ctx.buf, ctx.range) + return { "$FILENAME", "--range-start=" .. start_offset, "--range-end=" .. end_offset } + end, cwd = util.root_file({ -- https://prettier.io/docs/en/configuration.html ".prettierrc", diff --git a/lua/conform/formatters/rubocop.lua b/lua/conform/formatters/rubocop.lua index 492f379..5e2a143 100644 --- a/lua/conform/formatters/rubocop.lua +++ b/lua/conform/formatters/rubocop.lua @@ -1,4 +1,4 @@ ----@type conform.FormatterConfig +---@type conform.FileFormatterConfig return { meta = { url = "https://github.com/rubocop/rubocop", diff --git a/lua/conform/formatters/rustfmt.lua b/lua/conform/formatters/rustfmt.lua index 7b5e322..d8f0f19 100644 --- a/lua/conform/formatters/rustfmt.lua +++ b/lua/conform/formatters/rustfmt.lua @@ -1,4 +1,4 @@ ----@type conform.FormatterConfig +---@type conform.FileFormatterConfig return { meta = { url = "https://github.com/rust-lang/rustfmt", diff --git a/lua/conform/formatters/scalafmt.lua b/lua/conform/formatters/scalafmt.lua index 2b9e451..ea82624 100644 --- a/lua/conform/formatters/scalafmt.lua +++ b/lua/conform/formatters/scalafmt.lua @@ -1,4 +1,4 @@ ----@type conform.FormatterConfig +---@type conform.FileFormatterConfig return { meta = { url = "https://github.com/scalameta/scalafmt", diff --git a/lua/conform/formatters/shfmt.lua b/lua/conform/formatters/shfmt.lua index 6e40f0c..29b8615 100644 --- a/lua/conform/formatters/shfmt.lua +++ b/lua/conform/formatters/shfmt.lua @@ -1,4 +1,4 @@ ----@type conform.FormatterConfig +---@type conform.FileFormatterConfig return { meta = { url = "https://github.com/mvdan/sh", diff --git a/lua/conform/formatters/sql_formatter.lua b/lua/conform/formatters/sql_formatter.lua index bd92851..9452a61 100644 --- a/lua/conform/formatters/sql_formatter.lua +++ b/lua/conform/formatters/sql_formatter.lua @@ -1,4 +1,4 @@ ----@type conform.FormatterConfig +---@type conform.FileFormatterConfig return { meta = { url = "https://github.com/sql-formatter-org/sql-formatter", diff --git a/lua/conform/formatters/stylua.lua b/lua/conform/formatters/stylua.lua index ac482bd..d971932 100644 --- a/lua/conform/formatters/stylua.lua +++ b/lua/conform/formatters/stylua.lua @@ -1,5 +1,5 @@ local util = require("conform.util") ----@type conform.FormatterConfig +---@type conform.FileFormatterConfig return { meta = { url = "https://github.com/JohnnyMorganz/StyLua", @@ -7,6 +7,19 @@ return { }, command = "stylua", args = { "--search-parent-directories", "--stdin-filepath", "$FILENAME", "-" }, + range_args = function(ctx) + local start_offset, end_offset = util.get_offsets_from_range(ctx.buf, ctx.range) + return { + "--search-parent-directories", + "--stdin-filepath", + "$FILENAME", + "--range-start", + tostring(start_offset), + "--range-end", + tostring(end_offset), + "-", + } + end, cwd = util.root_file({ ".stylua.toml", "stylua.toml", diff --git a/lua/conform/formatters/swift_format.lua b/lua/conform/formatters/swift_format.lua index 2b81297..e3e4365 100644 --- a/lua/conform/formatters/swift_format.lua +++ b/lua/conform/formatters/swift_format.lua @@ -1,4 +1,4 @@ ----@type conform.FormatterConfig +---@type conform.FileFormatterConfig return { meta = { url = "https://github.com/apple/swift-format", diff --git a/lua/conform/formatters/swiftformat.lua b/lua/conform/formatters/swiftformat.lua index 821a010..ed142ad 100644 --- a/lua/conform/formatters/swiftformat.lua +++ b/lua/conform/formatters/swiftformat.lua @@ -1,4 +1,4 @@ ----@type conform.FormatterConfig +---@type conform.FileFormatterConfig return { meta = { url = "https://github.com/nicklockwood/SwiftFormat", diff --git a/lua/conform/formatters/terraform_fmt.lua b/lua/conform/formatters/terraform_fmt.lua index 44edc55..fba8a12 100644 --- a/lua/conform/formatters/terraform_fmt.lua +++ b/lua/conform/formatters/terraform_fmt.lua @@ -1,4 +1,4 @@ ----@type conform.FormatterConfig +---@type conform.FileFormatterConfig return { meta = { url = "https://www.terraform.io/docs/cli/commands/fmt.html", diff --git a/lua/conform/formatters/uncrustify.lua b/lua/conform/formatters/uncrustify.lua index 3430063..2782e96 100644 --- a/lua/conform/formatters/uncrustify.lua +++ b/lua/conform/formatters/uncrustify.lua @@ -1,4 +1,4 @@ ----@type conform.FormatterConfig +---@type conform.FileFormatterConfig return { meta = { url = "https://github.com/uncrustify/uncrustify", diff --git a/lua/conform/formatters/xmlformat.lua b/lua/conform/formatters/xmlformat.lua index 25b48e2..d04c9cc 100644 --- a/lua/conform/formatters/xmlformat.lua +++ b/lua/conform/formatters/xmlformat.lua @@ -1,4 +1,4 @@ ----@type conform.FormatterConfig +---@type conform.FileFormatterConfig return { meta = { url = "https://github.com/pamoller/xmlformatter", diff --git a/lua/conform/formatters/yamlfix.lua b/lua/conform/formatters/yamlfix.lua index 1b00e01..7592340 100644 --- a/lua/conform/formatters/yamlfix.lua +++ b/lua/conform/formatters/yamlfix.lua @@ -1,4 +1,4 @@ ----@type conform.FormatterConfig +---@type conform.FileFormatterConfig return { meta = { url = "https://github.com/lyz-code/yamlfix", diff --git a/lua/conform/formatters/yamlfmt.lua b/lua/conform/formatters/yamlfmt.lua index 56c6cb6..8f188ab 100644 --- a/lua/conform/formatters/yamlfmt.lua +++ b/lua/conform/formatters/yamlfmt.lua @@ -1,4 +1,4 @@ ----@type conform.FormatterConfig +---@type conform.FileFormatterConfig return { meta = { url = "https://github.com/google/yamlfmt", diff --git a/lua/conform/formatters/yapf.lua b/lua/conform/formatters/yapf.lua index 5d7e866..fc3cfeb 100644 --- a/lua/conform/formatters/yapf.lua +++ b/lua/conform/formatters/yapf.lua @@ -1,4 +1,4 @@ ----@type conform.FormatterConfig +---@type conform.FileFormatterConfig return { meta = { url = "https://github.com/google/yapf", @@ -6,4 +6,7 @@ return { }, command = "yapf", args = { "--quiet" }, + range_args = function(ctx) + return { "--quiet", "--lines", string.format("%d-%d", ctx.range.start[1], ctx.range["end"][1]) } + end, } diff --git a/lua/conform/formatters/zigfmt.lua b/lua/conform/formatters/zigfmt.lua index 9c93c0b..8097d9c 100644 --- a/lua/conform/formatters/zigfmt.lua +++ b/lua/conform/formatters/zigfmt.lua @@ -1,4 +1,4 @@ ----@type conform.FormatterConfig +---@type conform.FileFormatterConfig return { meta = { url = "https://github.com/ziglang/zig", diff --git a/lua/conform/init.lua b/lua/conform/init.lua index 1bea2ea..9ba56bb 100644 --- a/lua/conform/init.lua +++ b/lua/conform/init.lua @@ -7,26 +7,35 @@ local M = {} ---@field available boolean ---@field available_msg? string ----@class (exact) conform.StaticFormatterConfig +---@class (exact) conform.FormatterConfig ---@field command string|fun(ctx: conform.Context): string ---@field args? string[]|fun(ctx: conform.Context): string[] +---@field range_args? fun(ctx: conform.RangeContext): 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.FormatterConfig : conform.StaticFormatterConfig +---@class (exact) conform.FileFormatterConfig : conform.FormatterConfig ---@field meta conform.FormatterMeta ---@class (exact) conform.FormatterMeta ---@field url string ---@field description string ---- + ---@class (exact) conform.Context ---@field buf integer ---@field filename string ---@field dirname string +---@field range? conform.Range + +---@class (exact) conform.RangeContext : conform.Context +---@field range conform.Range + +---@class (exact) conform.Range +---@field start integer[] +---@field end integer[] ---@class (exact) conform.RunOptions ---@field run_all_formatters nil|boolean Run all listed formatters instead of stopping at the first one. @@ -37,7 +46,7 @@ local M = {} ---@type table<string, string[]|conform.FormatterList> M.formatters_by_ft = {} ----@type table<string, conform.StaticFormatterConfig|fun(): conform.StaticFormatterConfig> +---@type table<string, conform.FormatterConfig|fun(): conform.FormatterConfig> M.formatters = {} M.setup = function(opts) @@ -167,6 +176,37 @@ local function filter_formatters(formatters, run_options) return all_info end +---@param bufnr integer +---@param mode "v"|"V" +---@return table {start={row,col}, end={row,col}} using (1, 0) indexing +local function range_from_selection(bufnr, mode) + -- [bufnum, lnum, col, off]; both row and column 1-indexed + local start = vim.fn.getpos("v") + local end_ = vim.fn.getpos(".") + local start_row = start[2] + local start_col = start[3] + local end_row = end_[2] + local end_col = end_[3] + + -- A user can start visual selection at the end and move backwards + -- Normalize the range to start < end + if start_row == end_row and end_col < start_col then + end_col, start_col = start_col, end_col + elseif end_row < start_row then + start_row, end_row = end_row, start_row + start_col, end_col = end_col, start_col + end + if mode == "V" then + start_col = 1 + local lines = vim.api.nvim_buf_get_lines(bufnr, end_row - 1, end_row, true) + end_col = #lines[1] + end + return { + ["start"] = { start_row, start_col - 1 }, + ["end"] = { end_row, end_col - 1 }, + } +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. @@ -175,9 +215,10 @@ end --- 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. --- quiet nil|boolean Don't show any notifications for warnings or failures. Defaults to false. +--- range nil|table Range to format. Table must contain `start` and `end` keys with {row, col} tuples using (1,0) indexing. Defaults to current selection in visual mode ---@return boolean True if any formatters were attempted M.format = function(opts) - ---@type {timeout_ms: integer, bufnr: integer, async: boolean, lsp_fallback: boolean, quiet: boolean, formatters?: string[]} + ---@type {timeout_ms: integer, bufnr: integer, async: boolean, lsp_fallback: boolean, quiet: boolean, formatters?: string[], range?: conform.Range} opts = vim.tbl_extend("keep", opts or {}, { timeout_ms = 1000, bufnr = 0, @@ -219,10 +260,21 @@ M.format = function(opts) local any_formatters = not vim.tbl_isempty(formatters) if any_formatters then + local mode = vim.api.nvim_get_mode().mode + if not opts.range and mode == "v" or mode == "V" then + opts.range = range_from_selection(opts.bufnr, mode) + end + if opts.async then - require("conform.runner").format_async(opts.bufnr, formatters) + require("conform.runner").format_async(opts.bufnr, formatters, opts.range) else - require("conform.runner").format_sync(opts.bufnr, formatters, opts.timeout_ms, opts.quiet) + require("conform.runner").format_sync( + opts.bufnr, + formatters, + opts.timeout_ms, + opts.quiet, + opts.range + ) end elseif opts.lsp_fallback and supports_lsp_format(opts.bufnr) then log.debug("Running LSP formatter on %s", vim.api.nvim_buf_get_name(opts.bufnr)) @@ -276,7 +328,7 @@ end ---@private ---@param formatter string ----@return nil|conform.StaticFormatterConfig +---@return nil|conform.FormatterConfig M.get_formatter_config = function(formatter) local config = M.formatters[formatter] if not config then diff --git a/lua/conform/runner.lua b/lua/conform/runner.lua index 7ccaec6..843dae1 100644 --- a/lua/conform/runner.lua +++ b/lua/conform/runner.lua @@ -5,35 +5,51 @@ local uv = vim.uv or vim.loop local M = {} ---@param ctx conform.Context ----@param config conform.StaticFormatterConfig +---@param config conform.FormatterConfig M.build_cmd = function(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 + local args = {} + if ctx.range and config.range_args then + ---@cast ctx conform.RangeContext + args = config.range_args(ctx) + elseif config.args then if type(config.args) == "function" then args = config.args(ctx) + else + ---@diagnostic disable-next-line: cast-local-type + args = config.args end - ---@cast args string[] - for _, v in ipairs(args) do - if v == "$FILENAME" then - v = ctx.filename - elseif v == "$DIRNAME" then - v = ctx.dirname - end - table.insert(cmd, v) + end + + ---@diagnostic disable-next-line: param-type-mismatch + for _, v in ipairs(args) do + if v == "$FILENAME" then + v = ctx.filename + elseif v == "$DIRNAME" then + v = ctx.dirname end + table.insert(cmd, v) end return cmd end +---@param range? conform.Range +---@param start_a integer +---@param end_a integer +local function indices_in_range(range, start_a, end_a) + return not range or (start_a <= range["end"][1] and range["start"][1] <= end_a) +end + ---@param bufnr integer ---@param original_lines string[] ---@param new_lines string[] -local function apply_format(bufnr, original_lines, new_lines) +---@param range? conform.Range +---@param only_apply_range boolean +local function apply_format(bufnr, original_lines, new_lines, range, only_apply_range) local original_text = table.concat(original_lines, "\n") -- Trim off the final newline from the formatted text because that is baked in to -- the vim lines representation @@ -68,18 +84,21 @@ local function apply_format(bufnr, original_lines, new_lines) count_b = count_b + 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) + local end_a = start_a + count_a + if not only_apply_range or indices_in_range(range, start_a, end_a) then + vim.api.nvim_buf_set_lines(bufnr, start_a - 1, end_a - 1, true, replacement) + end end end ---@param bufnr integer ---@param formatter conform.FormatterInfo +---@param config conform.FormatterConfig +---@param ctx conform.Context ---@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) +---@return integer job_id +local function run_formatter(bufnr, formatter, config, ctx, input_lines, callback) local cmd = M.build_cmd(ctx, config) local cwd = nil if config.cwd then @@ -159,9 +178,10 @@ local function run_formatter(bufnr, formatter, input_lines, callback) end ---@param bufnr integer ----@param config conform.StaticFormatterConfig +---@param config conform.FormatterConfig +---@param range? conform.Range ---@return conform.Context -M.build_context = function(bufnr, config) +M.build_context = function(bufnr, config, range) if bufnr == 0 then bufnr = vim.api.nvim_get_current_buf() end @@ -193,13 +213,15 @@ M.build_context = function(bufnr, config) buf = bufnr, filename = filename, dirname = dirname, + range = range, } end ---@param bufnr integer ---@param formatters conform.FormatterInfo[] +---@param range? conform.Range ---@param callback? fun(err?: string) -M.format_async = function(bufnr, formatters, callback) +M.format_async = function(bufnr, formatters, range, callback) if bufnr == 0 then bufnr = vim.api.nvim_get_current_buf() end @@ -207,6 +229,7 @@ M.format_async = function(bufnr, formatters, callback) local changedtick = vim.b[bufnr].changedtick local original_lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false) local input_lines = original_lines + local all_support_range_formatting = true -- kill previous jobs for buffer local prev_jid = vim.b[bufnr].conform_jid @@ -221,7 +244,7 @@ M.format_async = function(bufnr, formatters, callback) if not formatter then -- discard formatting if buffer has changed if vim.b[bufnr].changedtick == changedtick then - apply_format(bufnr, original_lines, input_lines) + apply_format(bufnr, original_lines, input_lines, range, not all_support_range_formatting) else log.info( "Async formatter discarding changes for %s: concurrent modification", @@ -235,8 +258,10 @@ M.format_async = function(bufnr, formatters, callback) end idx = idx + 1 + local config = assert(require("conform").get_formatter_config(formatter.name)) + local ctx = M.build_context(bufnr, config, range) local jid - jid = run_formatter(bufnr, formatter, input_lines, function(err, output) + jid = run_formatter(bufnr, formatter, config, ctx, input_lines, function(err, output) if err then -- Only log the error if the job wasn't canceled if vim.api.nvim_buf_is_valid(bufnr) and jid == vim.b[bufnr].conform_jid then @@ -250,6 +275,7 @@ M.format_async = function(bufnr, formatters, callback) input_lines = output run_next_formatter() end) + all_support_range_formatting = all_support_range_formatting and config.range_args ~= nil end run_next_formatter() end @@ -258,7 +284,8 @@ end ---@param formatters conform.FormatterInfo[] ---@param timeout_ms integer ---@param quiet boolean -M.format_sync = function(bufnr, formatters, timeout_ms, quiet) +---@param range? conform.Range +M.format_sync = function(bufnr, formatters, timeout_ms, quiet, range) if bufnr == 0 then bufnr = vim.api.nvim_get_current_buf() end @@ -274,6 +301,7 @@ M.format_sync = function(bufnr, formatters, timeout_ms, quiet) end end + local all_support_range_formatting = true for _, formatter in ipairs(formatters) do local remaining = timeout_ms - (uv.hrtime() / 1e6 - start) if remaining <= 0 then @@ -286,13 +314,16 @@ M.format_sync = function(bufnr, formatters, timeout_ms, quiet) end local done = false local result = nil - run_formatter(bufnr, formatter, input_lines, function(err, output) + local config = assert(require("conform").get_formatter_config(formatter.name)) + local ctx = M.build_context(bufnr, config, range) + local jid = run_formatter(bufnr, formatter, config, ctx, input_lines, function(err, output) if err then log.error(err) end done = true result = output end) + all_support_range_formatting = all_support_range_formatting and config.range_args ~= nil local wait_result, wait_reason = vim.wait(remaining, function() return done @@ -306,6 +337,7 @@ M.format_sync = function(bufnr, formatters, timeout_ms, quiet) vim.notify(string.format("Formatter '%s' timed out", formatter.name), vim.log.levels.WARN) end end + vim.fn.jobstop(jid) return end @@ -317,7 +349,7 @@ M.format_sync = function(bufnr, formatters, timeout_ms, quiet) end local final_result = input_lines - apply_format(bufnr, original_lines, final_result) + apply_format(bufnr, original_lines, final_result, range, not all_support_range_formatting) end return M diff --git a/lua/conform/util.lua b/lua/conform/util.lua index 353c447..d2ca1b9 100644 --- a/lua/conform/util.lua +++ b/lua/conform/util.lua @@ -58,6 +58,20 @@ M.save_win_positions = function(bufnr) end end +---@param bufnr integer +---@param range conform.Range +---@return integer start_offset +---@return integer end_offset +M.get_offsets_from_range = function(bufnr, range) + local row = range.start[1] - 1 + local end_row = range["end"][1] - 1 + local col = range.start[2] + local end_col = range["end"][2] + local start_offset = vim.api.nvim_buf_get_offset(bufnr, row) + col + local end_offset = vim.api.nvim_buf_get_offset(bufnr, end_row) + end_col + return start_offset, end_offset +end + ---@generic T : any ---@param tbl T[] ---@param start_idx? number |