From 2ea8bd564ff8eb4ab93955765e41c1635c857373 Mon Sep 17 00:00:00 2001 From: Antoine U Date: Fri, 15 Nov 2024 14:02:50 +0100 Subject: [PATCH] feat(format) adds mode for autoformat to apply either to the file or only the modified lines --- lua/lazyvim/config/keymaps.lua | 2 + lua/lazyvim/config/options.lua | 7 ++ lua/lazyvim/plugins/editor.lua | 1 + lua/lazyvim/plugins/formatting.lua | 4 +- lua/lazyvim/util/format.lua | 115 +++++++++++++++++++++++++++-- lua/lazyvim/util/lsp.lua | 5 +- 6 files changed, 123 insertions(+), 11 deletions(-) diff --git a/lua/lazyvim/config/keymaps.lua b/lua/lazyvim/config/keymaps.lua index fbc7f5d1..814840c9 100644 --- a/lua/lazyvim/config/keymaps.lua +++ b/lua/lazyvim/config/keymaps.lua @@ -139,6 +139,8 @@ map("n", "[w", diagnostic_goto(false, "WARN"), { desc = "Prev Warning" }) -- toggle options LazyVim.format.snacks_toggle():map("uf") LazyVim.format.snacks_toggle(true):map("uF") +LazyVim.format.snacks_mode_toggle():map("um") +LazyVim.format.snacks_mode_toggle(true):map("uM") Snacks.toggle.option("spell", { name = "Spelling" }):map("us") Snacks.toggle.option("wrap", { name = "Wrap" }):map("uw") Snacks.toggle.option("relativenumber", { name = "Relative Number" }):map("uL") diff --git a/lua/lazyvim/config/options.lua b/lua/lazyvim/config/options.lua index 0bbaacba..d794902d 100644 --- a/lua/lazyvim/config/options.lua +++ b/lua/lazyvim/config/options.lua @@ -4,6 +4,13 @@ vim.g.maplocalleader = "\\" -- LazyVim auto format vim.g.autoformat = true +-- LazyVim auto format mode (files | changes) +-- if set to changes only format actual changes based on git hunks +-- otherwise format all the file +vim.g.formatmode = "file" +--- LazyVim auto format changes only mode will be ignored +--- for these filetypes and fallback to file +vim.g.format_range_exclude_ft = { "lua" } -- Snacks animations -- Set to `false` to globally disable all snacks animations diff --git a/lua/lazyvim/plugins/editor.lua b/lua/lazyvim/plugins/editor.lua index 4aaf3d1f..5ffe0d8b 100644 --- a/lua/lazyvim/plugins/editor.lua +++ b/lua/lazyvim/plugins/editor.lua @@ -139,6 +139,7 @@ return { topdelete = { text = "" }, changedelete = { text = "▎" }, }, + attach_to_untracked = true, on_attach = function(buffer) local gs = package.loaded.gitsigns diff --git a/lua/lazyvim/plugins/formatting.lua b/lua/lazyvim/plugins/formatting.lua index ae32c0ef..b6bd98a2 100644 --- a/lua/lazyvim/plugins/formatting.lua +++ b/lua/lazyvim/plugins/formatting.lua @@ -40,8 +40,8 @@ return { name = "conform.nvim", priority = 100, primary = true, - format = function(buf) - require("conform").format({ bufnr = buf }) + format = function(buf, range) + require("conform").format({ bufnr = buf, range = range }) end, sources = function(buf) local ret = require("conform").list_formatters(buf) diff --git a/lua/lazyvim/util/format.lua b/lua/lazyvim/util/format.lua index 7b0ccfd2..4209de53 100644 --- a/lua/lazyvim/util/format.lua +++ b/lua/lazyvim/util/format.lua @@ -9,12 +9,17 @@ local M = setmetatable({}, { ---@class LazyFormatter ---@field name string ---@field primary? boolean ----@field format fun(bufnr:number) +---@field format fun(bufnr:number, range?:{start:integer[],end:integer[]}) ---@field sources fun(bufnr:number):string[] ---@field priority number M.formatters = {} ---@type LazyFormatter[] +--- Exclude lua from range formatting as stylua does not handle it well +--- It will fallback to file formatting +---@type string[] +M.format_range_exclude_ft = { "lua" } + ---@param formatter LazyFormatter function M.register(formatter) M.formatters[#M.formatters + 1] = formatter @@ -52,13 +57,19 @@ function M.info(buf) buf = buf or vim.api.nvim_get_current_buf() local gaf = vim.g.autoformat == nil or vim.g.autoformat local baf = vim.b[buf].autoformat + local gmode = (vim.g.formatmode == nil and "file") or vim.g.formatmode + local bmode = vim.b[buf].formatmode + if vim.tbl_contains(vim.g.format_range_exclude_ft or M.format_range_exclude_ft, vim.bo[buf].filetype) then + bmode = "forced file" + end local enabled = M.enabled(buf) local lines = { - "# Status", - ("- [%s] global **%s**"):format(gaf and "x" or " ", gaf and "enabled" or "disabled"), - ("- [%s] buffer **%s**"):format( + "\n# Status", + ("- [%s] global **%s** (`%s`)"):format(gaf and "x" or " ", gaf and "enabled" or "disabled", gmode), + ("- [%s] buffer **%s** (`%s`)"):format( enabled and "x" or " ", - baf == nil and "inherit" or baf and "enabled" or "disabled" + baf == nil and "inherit" or baf and "enabled" or "disabled", + bmode == nil and "inherit" or bmode ), } local have = false @@ -115,20 +126,26 @@ function M.enable(enable, buf) M.info() end ----@param opts? {force?:boolean, buf?:number} +---@param opts? {force?:boolean, buf?:number, changes?:boolean} function M.format(opts) opts = opts or {} local buf = opts.buf or vim.api.nvim_get_current_buf() if not ((opts and opts.force) or M.enabled(buf)) then return end + local range_exclude_ft = vim.g.format_range_exclude_ft or M.format_range_exclude_ft + local format = M._format_file + local mode = (opts.changes and "changes") or M.mode(buf) + if mode == "changes" and not vim.tbl_contains(range_exclude_ft, vim.bo[buf].filetype) then + format = M._format_changes + end local done = false for _, formatter in ipairs(M.resolve(buf)) do if formatter.active then done = true LazyVim.try(function() - return formatter.format(buf) + return format(formatter, buf) end, { msg = "Formatter `" .. formatter.name .. "` failed" }) end end @@ -138,6 +155,46 @@ function M.format(opts) end end +---@param formatter LazyFormatter +---@param buf number +function M._format_changes(formatter, buf) + if not LazyVim.has("gitsigns.nvim") then + return + end + local hunks = require("gitsigns").get_hunks(buf) + if hunks == nil then + return + end + for i = #hunks, 1, -1 do + local hunk = hunks[i] + if hunk ~= nil and hunk.type ~= "delete" then + local start = hunk.added.start + local last = start + hunk.added.count + local last_hunk_line = vim.api.nvim_buf_get_lines(0, last - 2, last - 1, true)[1] + local range = { start = { start, 0 }, ["end"] = { last - 1, last_hunk_line:len() } } + formatter.format(buf, range) + end + end +end + +---@param formatter LazyFormatter +---@param buf number +function M._format_file(formatter, buf) + return formatter.format(buf) +end + +---@param mode "changes" | "file" +---@param buf? boolean +function M.setmode(mode, buf) + if buf then + vim.b.formatmode = mode + else + vim.g.formatmode = mode + vim.b.formatmode = nil + end + M.info() +end + function M.health() local Config = require("lazy.core.config") local has_plugin = Config.spec.plugins["none-ls.nvim"] @@ -155,6 +212,29 @@ function M.health() end end +---@param buf? boolean +function M.togglemode(buf) + local mode = M.mode() + if mode == "file" then + mode = "changes" + else + mode = "file" + end + M.setmode(mode, buf) +end + +---@param buf? number +---@return "changes" | "file" +function M.mode(buf) + buf = buf or vim.api.nvim_get_current_buf() + local gmode = (vim.g.formatmode == nil and "file") or vim.g.formatmode + local bmode = vim.b[buf].formatmode + if bmode == nil then + return gmode + end + return bmode +end + function M.setup() M.health() @@ -177,6 +257,27 @@ function M.setup() end, { desc = "Show info about the formatters for the current buffer" }) end +---@param buf? boolean +function M.snacks_mode_toggle(buf) + return Snacks.toggle({ + name = "Format Changes only (" .. (buf and "Buffer" or "Global") .. ")", + get = function() + if not buf then + local mode = vim.g.formatmode == nil and "file" or vim.g.formatmode + return mode == "changes" + end + return LazyVim.format.mode() == "changes" + end, + set = function(state) + if state then + LazyVim.format.setmode("changes", buf) + else + LazyVim.format.setmode("file", buf) + end + end, + }) +end + ---@param buf? boolean function M.snacks_toggle(buf) return Snacks.toggle({ diff --git a/lua/lazyvim/util/lsp.lua b/lua/lazyvim/util/lsp.lua index e868dcf0..c4d4fc40 100644 --- a/lua/lazyvim/util/lsp.lua +++ b/lua/lazyvim/util/lsp.lua @@ -162,8 +162,9 @@ function M.formatter(opts) name = "LSP", primary = true, priority = 1, - format = function(buf) - M.format(LazyVim.merge({}, filter, { bufnr = buf })) + format = function(buf, range) + local fopts = LazyVim.merge({}, filter, { bufnr = buf }, { range = range }) + M.format(fopts) end, sources = function(buf) local clients = M.get_clients(LazyVim.merge({}, filter, { bufnr = buf }))