diff --git a/lua/neogit/buffers/status/actions.lua b/lua/neogit/buffers/status/actions.lua index a28d2fec6..c5387b77c 100644 --- a/lua/neogit/buffers/status/actions.lua +++ b/lua/neogit/buffers/status/actions.lua @@ -212,7 +212,7 @@ M.v_discard = function(self) end end - self:dispatch_refresh({ update_diff = invalidated_diffs }, "v_discard") + self:dispatch_refresh({ update_diffs = invalidated_diffs }, "v_discard") end end) end diff --git a/lua/neogit/buffers/status/init.lua b/lua/neogit/buffers/status/init.lua index 387d7d23f..42f1842c0 100644 --- a/lua/neogit/buffers/status/init.lua +++ b/lua/neogit/buffers/status/init.lua @@ -22,6 +22,21 @@ M.__index = M local instances = {} +vim.api.nvim_create_autocmd("BufWritePost", { + pattern = "*", + callback = function(args) + if not config.values.status.fast then + return + end + + for _, buf in pairs(instances) do + buf:refresh { + update_diffs = { "*:" .. args.file }, + } + end + end, +}) + ---@param instance StatusBuffer ---@param dir string function M.register(instance, dir) diff --git a/lua/neogit/config.lua b/lua/neogit/config.lua index 69023ea4c..defab8691 100644 --- a/lua/neogit/config.lua +++ b/lua/neogit/config.lua @@ -324,6 +324,7 @@ end ---@field HEAD_folded? boolean Whether or not this section should be open or closed by default ---@field mode_text? { [string]: string } The text to display for each mode ---@field show_head_commit_hash? boolean Show the commit hash for HEADs in the status buffer +---@field fast? boolean Performs in large repositories, but only identifies file renames after manual refresh ---@class NeogitConfigMappings Consult the config file or documentation for values ---@field finder? { [string]: NeogitConfigMappingsFinder } A dictionary that uses finder commands to set multiple keybinds diff --git a/lua/neogit/lib/git/repository.lua b/lua/neogit/lib/git/repository.lua index a8e55fcc4..9954aac49 100644 --- a/lua/neogit/lib/git/repository.lua +++ b/lua/neogit/lib/git/repository.lua @@ -248,13 +248,18 @@ function Repo:git_path(...) return Path:new(self.git_dir):joinpath(...) end +---@param name string +function Repo:task_with_log(name, state, filter) + local start = vim.uv.now() + self.lib[name](state, filter) + logger.debug(("[REPO]: Refreshed %s in %d ms"):format(name, vim.uv.now() - start)) +end + function Repo:tasks(filter, state) local tasks = {} for name, fn in pairs(self.lib) do table.insert(tasks, function() - local start = vim.uv.now() - fn(state, filter) - logger.debug(("[REPO]: Refreshed %s in %d ms"):format(name, vim.uv.now() - start)) + self:task_with_log(name, state, filter) end) end @@ -293,6 +298,15 @@ function Repo:set_state(id) self.state = self:current_state(id) end +local function count_entries(table) + local count = 0 + for _, _ in pairs(table) do + count = count + 1 + end + + return count +end + function Repo:refresh(opts) if self.worktree_root == "" then logger.debug("[REPO] No git root found - skipping refresh") @@ -337,7 +351,13 @@ function Repo:refresh(opts) self:run_callbacks(start) end) - a.util.run_all(self:tasks(filter, self:current_state(start)), on_complete) + local state = self:current_state(start) + if opts.partial and count_entries(opts.partial) == 1 and opts.partial.update_diffs then + self:task_with_log("update_status", state, filter) + on_complete() + else + a.util.run_all(self:tasks(filter, state), on_complete) + end end Repo.dispatch_refresh = a.void(function(self, opts) diff --git a/lua/neogit/lib/git/status.lua b/lua/neogit/lib/git/status.lua index 6383393cb..c8b96f4de 100644 --- a/lua/neogit/lib/git/status.lua +++ b/lua/neogit/lib/git/status.lua @@ -3,6 +3,7 @@ local git = require("neogit.lib.git") local util = require("neogit.lib.util") local Collection = require("neogit.lib.collection") local logger = require("neogit.logger") +local config = require("neogit.config") ---@class StatusItem ---@field mode string @@ -94,11 +95,35 @@ local function update_status(state, filter) untracked_files = item_collection(state, "untracked", filter), } - state.staged.items = {} - state.untracked.items = {} - state.unstaged.items = {} + ---@param items StatusItem[] + ---@param predicate fun(item: StatusItem):boolean + local function remove_first_matching(items, predicate) + for i, v in ipairs(items) do + if predicate(v) then + table.remove(items, i) + return + end + end + end - local result = git.cli.status.null_separated.porcelain(2).call { hidden = true, remove_ansi = false } + local status_cmd = git.cli.status.null_separated.porcelain(2) + local file = filter[1].file + if config.values.status.fast and file ~= "*" then + status_cmd.args(file) + ---@param item StatusItem + ---@return boolean + local function is_changed_file(item) + return item.name == file or item.original_name == file + end + remove_first_matching(state.staged.items, is_changed_file) + remove_first_matching(state.unstaged.items, is_changed_file) + remove_first_matching(state.untracked.items, is_changed_file) + else + state.staged.items = {} + state.untracked.items = {} + state.unstaged.items = {} + end + local result = status_cmd.call { hidden = true, remove_ansi = false } result = vim.split(result.stdout[1] or "", "\n") result = util.collect(result, function(line, collection) if line == "" then