diff --git a/lua/gitsigns/attach.lua b/lua/gitsigns/attach.lua index 4bbc8cbe..f8dad72f 100644 --- a/lua/gitsigns/attach.lua +++ b/lua/gitsigns/attach.lua @@ -362,7 +362,7 @@ local attach_throttled = throttle_by_id(function(cbuf, ctx, aucmd) }) -- Initial update - manager.update(cbuf, cache[cbuf]) + manager.update(cbuf) end) --- Detach Gitsigns from all buffers it is attached to. diff --git a/lua/gitsigns/cache.lua b/lua/gitsigns/cache.lua index f9790df3..8dc2433d 100644 --- a/lua/gitsigns/cache.lua +++ b/lua/gitsigns/cache.lua @@ -41,10 +41,6 @@ function CacheEntry:get_compare_rev(base) return string.format(':%d', stage) end -function CacheEntry:get_staged_compare_rev() - return self.commit and string.format('%s^', self.commit) or 'HEAD' -end - function CacheEntry:get_rev_bufname(rev) rev = rev or self:get_compare_rev() return string.format('gitsigns://%s/%s:%s', self.git_obj.repo.gitdir, rev, self.git_obj.relpath) diff --git a/lua/gitsigns/debounce.lua b/lua/gitsigns/debounce.lua index 1699f1ca..f0d5c3cc 100644 --- a/lua/gitsigns/debounce.lua +++ b/lua/gitsigns/debounce.lua @@ -7,39 +7,31 @@ local M = {} --- @generic F: function --- @param ms number Timeout in ms --- @param fn F Function to debounce +--- @param hash? integer|fun(...): any Function that determines id from arguments to fn --- @return F Debounced function. -function M.debounce_trailing(ms, fn) - local timer = assert(uv.new_timer()) +function M.debounce_trailing(ms, fn, hash) + local running = {} --- @type table + if type(hash) == 'number' then + local hash_i = hash + hash = function(...) + return select(hash_i, ...) + end + end return function(...) + local id = hash and hash(...) or true + if running[id] == nil then + running[id] = assert(uv.new_timer()) + end + local timer = running[id] local argv = { ... } timer:start(ms, 0, function() timer:stop() - fn(unpack(argv)) + running[id] = nil + fn(unpack(argv, 1, table.maxn(argv))) end) end end ---- Throttles a function on the leading edge. ---- ---- @generic F: function ---- @param ms number Timeout in ms ---- @param fn F Function to throttle ---- @return F throttled function. -function M.throttle_leading(ms, fn) - local timer = assert(uv.new_timer()) - local running = false - return function(...) - if not running then - timer:start(ms, 0, function() - running = false - timer:stop() - end) - running = true - fn(...) - end - end -end - --- Throttles a function using the first argument as an ID --- --- If function is already running then the function will be scheduled to run diff --git a/lua/gitsigns/manager.lua b/lua/gitsigns/manager.lua index 6a23ad9c..f72eb5e5 100644 --- a/lua/gitsigns/manager.lua +++ b/lua/gitsigns/manager.lua @@ -12,7 +12,6 @@ local throttle_by_id = require('gitsigns.debounce').throttle_by_id local log = require('gitsigns.debug.log') local dprint = log.dprint local dprintf = log.dprintf -local eprint = log.eprint local subprocess = require('gitsigns.subprocess') local util = require('gitsigns.util') @@ -63,12 +62,13 @@ end --- @param top integer --- @param bot integer --- @param clear boolean ---- @param untracked boolean -local function apply_win_signs(bufnr, top, bot, clear, untracked) +local function apply_win_signs(bufnr, top, bot, clear) local bcache = cache[bufnr] if not bcache then return end + + local untracked = bcache.git_obj.object_name == nil apply_win_signs0(bufnr, signs_normal, bcache.hunks, top, bot, clear, untracked) if signs_staged then apply_win_signs0(bufnr, signs_staged, bcache.hunks_staged, top, bot, clear, false) @@ -106,7 +106,7 @@ function M.on_lines(buf, first, last_orig, last_new) end end - M.update_debounced(buf, cache[buf]) + M.update_debounced(buf) end local ns = api.nvim_create_namespace('gitsigns') @@ -394,6 +394,24 @@ local function update_show_deleted(bufnr) end end +--- @param bufnr? integer +--- @param cb function +M.buf_check = async.wrap(function(bufnr, cb) + vim.schedule(function() + if bufnr then + if not api.nvim_buf_is_valid(bufnr) then + dprint('Buffer not valid, aborting') + return + end + if not cache[bufnr] then + dprint('Has detached, aborting') + return + end + end + cb() + end) +end, 2) + local update_cnt = 0 --- Ensure updates cannot be interleaved. @@ -401,38 +419,36 @@ local update_cnt = 0 --- whilst another one is in progress. If this happens then schedule another --- update after the current one has completed. --- @param bufnr integer ---- @param bcache? Gitsigns.CacheEntry -M.update = throttle_by_id(function(bufnr, bcache) +M.update = throttle_by_id(function(bufnr) local __FUNC__ = 'update' - bcache = bcache or cache[bufnr] - if not bcache then - eprint('Cache for buffer ' .. bufnr .. ' was nil') - return - end + M.buf_check(bufnr) + local bcache = cache[bufnr] local old_hunks, old_hunks_staged = bcache.hunks, bcache.hunks_staged bcache.hunks, bcache.hunks_staged = nil, nil - async.scheduler_if_buf_valid(bufnr) - local buftext = util.buf_lines(bufnr) local git_obj = bcache.git_obj if not bcache.compare_text or config._refresh_staged_on_update then bcache.compare_text = git_obj:get_show_text(bcache:get_compare_rev()) - async.scheduler_if_buf_valid(bufnr) + M.buf_check(bufnr) end + local buftext = util.buf_lines(bufnr) + bcache.hunks = run_diff(bcache.compare_text, buftext) + M.buf_check(bufnr) if config._signs_staged_enable then if not bcache.compare_text_head or config._refresh_staged_on_update then - bcache.compare_text_head = git_obj:get_show_text(bcache:get_staged_compare_rev()) + local staged_compare_rev = bcache.commit and string.format('%s^', bcache.commit) or 'HEAD' + bcache.compare_text_head = git_obj:get_show_text(staged_compare_rev) + M.buf_check(bufnr) end local hunks_head = run_diff(bcache.compare_text_head, buftext) + M.buf_check(bufnr) bcache.hunks_staged = gs_hunks.filter_common(hunks_head, bcache.hunks) end - async.scheduler_if_buf_valid(bufnr) - -- Note the decoration provider may have invalidated bcache.hunks at this -- point if @@ -442,7 +458,7 @@ M.update = throttle_by_id(function(bufnr, bcache) then -- Apply signs to the window. Other signs will be added by the decoration -- provider as they are drawn. - apply_win_signs(bufnr, vim.fn.line('w0'), vim.fn.line('w$'), true, git_obj.object_name == nil) + apply_win_signs(bufnr, vim.fn.line('w0'), vim.fn.line('w$'), true) update_show_deleted(bufnr) bcache.force_next_update = false @@ -497,9 +513,7 @@ local function on_win(_cb, _winid, bufnr, topline, botline_guess) end local botline = math.min(botline_guess, api.nvim_buf_line_count(bufnr)) - local untracked = bcache.git_obj.object_name == nil - - apply_win_signs(bufnr, topline + 1, botline + 1, false, untracked) + apply_win_signs(bufnr, topline + 1, botline + 1, false) if not (config.word_diff and config.diff_opts.internal) then return false diff --git a/lua/gitsigns/watcher.lua b/lua/gitsigns/watcher.lua index 56f7979d..64087d59 100644 --- a/lua/gitsigns/watcher.lua +++ b/lua/gitsigns/watcher.lua @@ -9,28 +9,13 @@ local util = require('gitsigns.util') local cache = require('gitsigns.cache').cache local config = require('gitsigns.config').config local debounce_trailing = require('gitsigns.debounce').debounce_trailing +local manager = require('gitsigns.manager') + +local buf_check = manager.buf_check local dprint = log.dprint local dprintf = log.dprintf ---- @param bufnr? integer ---- @param cb function -local buf_check = async.wrap(function(bufnr, cb) - vim.schedule(function() - if bufnr then - if not api.nvim_buf_is_valid(bufnr) then - dprint('Buffer not valid, aborting') - return - end - if not cache[bufnr] then - dprint('Has detached, aborting') - return - end - end - cb() - end) -end, 2) - --- @param bufnr integer --- @param old_relpath string local function handle_moved(bufnr, old_relpath) @@ -74,7 +59,9 @@ local function handle_moved(bufnr, old_relpath) dprintf('%s buffer %d from %s to %s', msg, bufnr, old_name, bcache.file) end -local watch_gitdir_handler = async.void(function(bufnr) +--- @param bufnr integer +local handler = debounce_trailing(200, async.void(function(bufnr) + local __FUNC__ = 'watcher_handler' buf_check(bufnr) local git_obj = cache[bufnr].git_obj @@ -100,8 +87,8 @@ local watch_gitdir_handler = async.void(function(bufnr) cache[bufnr]:invalidate() - require('gitsigns.manager').update(bufnr, cache[bufnr]) -end) + require('gitsigns.manager').update(bufnr) +end), 1) -- vim.inspect but on one line --- @param x any @@ -112,14 +99,15 @@ end local M = {} +local WATCH_IGNORE = { + ORIG_HEAD = true, + FETCH_HEAD = true +} + --- @param bufnr integer --- @param gitdir string --- @return uv.uv_fs_event_t function M.watch_gitdir(bufnr, gitdir) - -- Setup debounce as we create the luv object so the debounce is independent - -- to each watcher - local watch_gitdir_handler_db = debounce_trailing(200, watch_gitdir_handler) - dprintf('Watching git dir') local w = assert(uv.new_fs_event()) w:start(gitdir, {}, function(err, filename, events) @@ -134,14 +122,14 @@ function M.watch_gitdir(bufnr, gitdir) -- The luv docs say filename is passed as a string but it has been observed -- to sometimes be nil. -- https://github.com/lewis6991/gitsigns.nvim/issues/848 - if filename == nil or vim.endswith(filename, '.lock') then + if filename == nil or WATCH_IGNORE[filename] or vim.endswith(filename, '.lock') then dprintf('%s (ignoring)', info) return end dprint(info) - watch_gitdir_handler_db(bufnr) + handler(bufnr) end) return w end