diff --git a/doc/gitsigns.txt b/doc/gitsigns.txt index 14e87964d..c6f49199e 100644 --- a/doc/gitsigns.txt +++ b/doc/gitsigns.txt @@ -86,11 +86,11 @@ Most actions can be repeated with `.` if you have |vim-repeat| installed. ============================================================================== FUNCTIONS *gitsigns-functions* -Note functions with the {async} attribute are run asynchronously and are -non-blocking (return immediately). +Note functions with the {async} attribute are run asynchronously and accept +an optional {callback} argument. -setup({cfg}) *gitsigns.setup()* +setup({cfg}, {callback?}) *gitsigns.setup()* Setup and start Gitsigns. Attributes: ~ @@ -100,7 +100,7 @@ setup({cfg}) *gitsigns.setup()* {cfg} (table|nil): Configuration for Gitsigns. See |gitsigns-usage| for more details. -attach({bufnr}, {ctx}) *gitsigns.attach()* +attach({bufnr}, {ctx}, {callback?}) *gitsigns.attach()* Attach Gitsigns to the buffer. Attributes: ~ @@ -163,7 +163,7 @@ setloclist({nr}, {target}) *gitsigns.setloclist()* `0` for the current window (default). {target} (integer|string): See |gitsigns.setqflist()|. -setqflist({target}, {opts}) *gitsigns.setqflist()* +setqflist({target}, {opts}, {callback?}) *gitsigns.setqflist()* Populate the quickfix list with hunks. Automatically opens the quickfix window. @@ -251,7 +251,7 @@ reset_base({global}) *gitsigns.reset_base()* Alias for `change_base(nil, {global})` . -change_base({base}, {global}) *gitsigns.change_base()* +change_base({base}, {global}, {callback?}) *gitsigns.change_base()* Change the base revision to diff against. If {base} is not given, then the original base is used. If {global} is given and true, then change the base revision of all buffers, @@ -286,7 +286,7 @@ change_base({base}, {global}) *gitsigns.change_base()* {base} (string|nil): The object/revision to diff against. {global} (boolean|nil): Change the base of all buffers. -blame_line({opts}) *gitsigns.blame_line()* +blame_line({opts}, {callback?}) *gitsigns.blame_line()* Run git blame on the current line and show the results in a floating window. If already open, calling this will cause the window to get focus. @@ -335,19 +335,25 @@ preview_hunk() *gitsigns.preview_hunk()* window. If the preview is already open, calling this will cause the window to get focus. -prev_hunk({opts}) *gitsigns.prev_hunk()* +prev_hunk({opts}, {callback?}) *gitsigns.prev_hunk()* Jump to the previous hunk in the current buffer. If a hunk preview (popup or inline) was previously opened, it will be re-opened at the previous hunk. + Attributes: ~ + {async} + Parameters: ~ See |gitsigns.next_hunk()|. -next_hunk({opts}) *gitsigns.next_hunk()* +next_hunk({opts}, {callback?}) *gitsigns.next_hunk()* Jump to the next hunk in the current buffer. If a hunk preview (popup or inline) was previously opened, it will be re-opened at the next hunk. + Attributes: ~ + {async} + Parameters: ~ {opts} (table|nil): Configuration table. Keys: • {wrap}: (boolean) @@ -393,7 +399,7 @@ undo_stage_hunk() *gitsigns.undo_stage_hunk()* reset_buffer() *gitsigns.reset_buffer()* Reset the lines of all hunks in the buffer. -reset_hunk({range}, {opts}) *gitsigns.reset_hunk()* +reset_hunk({range}, {opts}, {callback?}) *gitsigns.reset_hunk()* Reset the lines of the hunk at the cursor position, or all lines in the given range. If {range} is provided, all lines in the given range are reset. This supports partial-hunks, @@ -410,7 +416,7 @@ reset_hunk({range}, {opts}) *gitsigns.reset_hunk()* Stage all contiguous hunks. Only useful if 'diff_opts' contains `linematch`. Defaults to `true`. -stage_hunk({range}, {opts}) *gitsigns.stage_hunk()* +stage_hunk({range}, {opts}, {callback?}) *gitsigns.stage_hunk()* Stage the hunk at the cursor position, or all lines in the given range. If {range} is provided, all lines in the given range are staged. This supports partial-hunks, meaning if a diff --git a/etc/doc_template.txt b/etc/doc_template.txt index ef11dcc5c..9083057a5 100644 --- a/etc/doc_template.txt +++ b/etc/doc_template.txt @@ -47,8 +47,8 @@ Most actions can be repeated with `.` if you have |vim-repeat| installed. ============================================================================== FUNCTIONS *gitsigns-functions* -Note functions with the {async} attribute are run asynchronously and are -non-blocking (return immediately). +Note functions with the {async} attribute are run asynchronously and accept +an optional {callback} argument. {{FUNCTIONS}} diff --git a/gen_help.lua b/gen_help.lua index b26cd6a80..f0cbae46e 100755 --- a/gen_help.lua +++ b/gen_help.lua @@ -95,8 +95,9 @@ local function gen_config_doc_field(field, out) if v.description then local d --- @type string - if v.default_help ~= nil then - d = v.default_help + local default_help = v.default_help + if default_help ~= nil then + d = default_help else d = inspect(v.default):gsub('\n', '\n ') d = ('`%s`'):format(d) @@ -160,6 +161,11 @@ local function parse_func_header(line) args[#args + 1] = string.format('{%s}', k) end end + + if line:match('async.create%(%d, function%(') then + args[#args + 1] = '{callback?}' + end + return string.format( '%-40s%38s', string.format('%s(%s)', func, table.concat(args, ', ')), @@ -486,6 +492,7 @@ local function main() if type(sub) == 'function' then sub = sub() end + --- @type string sub = sub:gsub('%%', '%%%%') l = l:gsub('{{' .. marker .. '}}', sub) end diff --git a/lua/gitsigns.lua b/lua/gitsigns.lua index 9f281ad1d..867ce205d 100644 --- a/lua/gitsigns.lua +++ b/lua/gitsigns.lua @@ -8,13 +8,14 @@ local dprintf = log.dprintf local dprint = log.dprint local api = vim.api -local uv = vim.loop +local uv = vim.uv or vim.loop local M = {} local cwd_watcher ---@type uv.uv_fs_event_t? -local update_cwd_head = async.void(function() +--- @async +local function update_cwd_head() local paths = vim.fs.find('.git', { limit = 1, upward = true, @@ -31,7 +32,7 @@ local update_cwd_head = async.void(function() cwd_watcher = assert(uv.new_fs_event()) end - local cwd = assert(vim.loop.cwd()) + local cwd = assert(uv.cwd()) --- @type string, string local gitdir, head @@ -73,7 +74,7 @@ local update_cwd_head = async.void(function() local update_head = debounce_trailing( 100, - async.void(function() + async.create(function() local new_head = git.get_repo_info(cwd).abbrev_head async.scheduler() vim.g.gitsigns_head = new_head @@ -84,7 +85,7 @@ local update_cwd_head = async.void(function() cwd_watcher:start( towatch, {}, - async.void(function(err) + async.create(function(err) local __FUNC__ = 'cwd_watcher_cb' if err then dprintf('Git dir update error: %s', err) @@ -95,7 +96,7 @@ local update_cwd_head = async.void(function() update_head() end) ) -end) +end local function setup_cli() api.nvim_create_user_command('Gitsigns', function(params) @@ -132,7 +133,7 @@ local function setup_attach() -- Make sure to run each attach in its on async context in case one of the -- attaches is aborted. local attach = require('gitsigns.attach') - async.run(attach.attach, buf, nil, 'setup') + attach.attach(buf, nil, 'setup') end end end @@ -141,13 +142,16 @@ end local function setup_cwd_head() async.scheduler() update_cwd_head() + + local debounce = require('gitsigns.debounce').debounce_trailing + local update_cwd_head_debounced = debounce(100, async.create(update_cwd_head)) + -- Need to debounce in case some plugin changes the cwd too often -- (like vim-grepper) api.nvim_create_autocmd('DirChanged', { group = 'gitsigns', callback = function() - local debounce = require('gitsigns.debounce').debounce_trailing - debounce(100, update_cwd_head) + update_cwd_head_debounced() end, }) end @@ -159,7 +163,7 @@ end --- --- @param cfg table|nil Configuration for Gitsigns. --- See |gitsigns-usage| for more details. -M.setup = async.void(function(cfg) +M.setup = async.create(1, function(cfg) gs_config.build(cfg) if vim.fn.executable('git') == 0 then diff --git a/lua/gitsigns/actions.lua b/lua/gitsigns/actions.lua index f1233bc69..452033f0f 100644 --- a/lua/gitsigns/actions.lua +++ b/lua/gitsigns/actions.lua @@ -172,7 +172,9 @@ end --- @param bufnr integer local function update(bufnr) manager.update(bufnr) - async.scheduler_if_buf_valid(bufnr) + if not manager.schedule(bufnr) then + return + end if vim.wo.diff then require('gitsigns.diffthis').update(bufnr) end @@ -206,7 +208,9 @@ local function get_hunks(bufnr, bcache, greedy, staged) return end local hunks = run_diff(text, buftext, false) - manager.buf_check(bufnr) + if not manager.schedule(bufnr) then + return + end return hunks end @@ -260,7 +264,7 @@ end --- • {greedy}: (boolean) --- Stage all contiguous hunks. Only useful if 'diff_opts' --- contains `linematch`. Defaults to `true`. -M.stage_hunk = mk_repeatable(async.void(function(range, opts) +M.stage_hunk = mk_repeatable(async.create(2, function(range, opts) opts = opts or {} local bufnr = current_buf() local bcache = cache[bufnr] @@ -326,7 +330,7 @@ end --- • {greedy}: (boolean) --- Stage all contiguous hunks. Only useful if 'diff_opts' --- contains `linematch`. Defaults to `true`. -M.reset_hunk = mk_repeatable(async.void(function(range, opts) +M.reset_hunk = mk_repeatable(async.create(2, function(range, opts) opts = opts or {} local bufnr = current_buf() local bcache = cache[bufnr] @@ -374,7 +378,7 @@ end --- --- Attributes: ~ --- {async} -M.undo_stage_hunk = async.void(function() +M.undo_stage_hunk = async.create(function() local bufnr = current_buf() local bcache = cache[bufnr] if not bcache then @@ -396,7 +400,7 @@ end) --- --- Attributes: ~ --- {async} -M.stage_buffer = async.void(function() +M.stage_buffer = async.create(function() local bufnr = current_buf() local bcache = cache[bufnr] @@ -432,7 +436,7 @@ end) --- --- Attributes: ~ --- {async} -M.reset_buffer_index = async.void(function() +M.reset_buffer_index = async.create(function() local bufnr = current_buf() local bcache = cache[bufnr] if not bcache then @@ -509,9 +513,10 @@ local function has_preview_inline(bufnr) return #api.nvim_buf_get_extmarks(bufnr, ns_inline, 0, -1, { limit = 1 }) > 0 end +--- @async --- @param opts? Gitsigns.NavOpts --- @param forwards boolean -local nav_hunk = async.void(function(opts, forwards) +local function nav_hunk(opts, forwards) opts = process_nav_opts(opts) local bufnr = current_buf() local bcache = cache[bufnr] @@ -569,12 +574,15 @@ local nav_hunk = async.void(function(opts, forwards) api.nvim_echo({ { string.format('Hunk %d of %d', index, #hunks), 'None' } }, false, {}) end end -end) +end --- Jump to the next hunk in the current buffer. If a hunk preview --- (popup or inline) was previously opened, it will be re-opened --- at the next hunk. --- +--- Attributes: ~ +--- {async} +--- --- @param opts table|nil Configuration table. Keys: --- • {wrap}: (boolean) --- Whether to loop around file or not. Defaults @@ -592,9 +600,9 @@ end) --- • {greedy}: (boolean) --- Only navigate between non-contiguous hunks. Only useful if --- 'diff_opts' contains `linematch`. Defaults to `true`. -M.next_hunk = function(opts) +M.next_hunk = async.create(1, function(opts) nav_hunk(opts, true) -end +end) C.next_hunk = function(args, _) M.next_hunk(args) @@ -604,11 +612,14 @@ end --- (popup or inline) was previously opened, it will be re-opened --- at the previous hunk. --- +--- Attributes: ~ +--- {async} +--- --- Parameters: ~ --- See |gitsigns.next_hunk()|. -M.prev_hunk = function(opts) +M.prev_hunk = async.create(1, function(opts) nav_hunk(opts, false) -end +end) C.prev_hunk = function(args, _) M.prev_hunk(args) @@ -752,7 +763,7 @@ local function get_hunk_with_staged(bufnr, greedy) end --- Preview the hunk at the cursor position inline in the buffer. -M.preview_hunk_inline = async.void(function() +M.preview_hunk_inline = async.create(function() local bufnr = current_buf() local hunk, staged = get_hunk_with_staged(bufnr, true) @@ -887,7 +898,7 @@ end --- Display full commit message with hunk. --- • {ignore_whitespace}: (boolean) --- Ignore whitespace when running blame. -M.blame_line = async.void(function(opts) +M.blame_line = async.create(1, function(opts) if popup.focus_open('blame') then return end @@ -904,7 +915,10 @@ M.blame_line = async.void(function(opts) popup.create({ { { 'Loading...', 'Title' } } }, config.preview_config) end, 1000) - async.scheduler_if_buf_valid() + if not manager.schedule(bufnr) then + return + end + local fileformat = vim.bo[bufnr].fileformat local lnum = api.nvim_win_get_cursor(0)[1] local result = bcache:get_blame(lnum, opts) @@ -912,7 +926,7 @@ M.blame_line = async.void(function(opts) loading:close() end) - if not vim.api.nvim_buf_is_valid(bufnr) then + if not api.nvim_buf_is_valid(bufnr) then return end @@ -943,7 +957,9 @@ M.blame_line = async.void(function(opts) }) end - async.scheduler_if_buf_valid(bufnr) + if not manager.schedule(bufnr) then + return + end popup.create(lines_format(blame_linespec, result), config.preview_config, 'blame') end) @@ -992,7 +1008,7 @@ end --- --- @param base string|nil The object/revision to diff against. --- @param global boolean|nil Change the base of all buffers. -M.change_base = async.void(function(base, global) +M.change_base = async.create(2, function(base, global) base = util.calc_base(base) if global then @@ -1219,7 +1235,7 @@ end --- • {open}: (boolean) --- Open the quickfix/location list viewer. --- Defaults to `true`. -M.setqflist = async.void(function(target, opts) +M.setqflist = async.create(2, function(target, opts) opts = opts or {} if opts.open == nil then opts.open = true @@ -1332,7 +1348,7 @@ end --- --- Attributes: ~ --- {async} -M.refresh = async.void(function() +M.refresh = async.create(function() manager.reset_signs() require('gitsigns.highlight').setup_highlights() require('gitsigns.current_line_blame').setup() diff --git a/lua/gitsigns/async.lua b/lua/gitsigns/async.lua index 3826e8131..7e195a776 100644 --- a/lua/gitsigns/async.lua +++ b/lua/gitsigns/async.lua @@ -135,6 +135,7 @@ function M.wait(argc, func, ...) local ok = ret[1] if not ok then + --- @type string, string local err, traceback = ret[2], ret[3] error(string.format('Wrapped function failed: %s\n%s', err, traceback)) end @@ -143,73 +144,49 @@ function M.wait(argc, func, ...) end --- Creates an async function with a callback style function. ---- @param func function: A callback style function to be converted. The last argument must be the callback. ---- @param argc number: The number of arguments of func. Must be included. +--- @param argc number The number of arguments of func. Must be included. +--- @param func function A callback style function to be converted. The last argument must be the callback. --- @return function: Returns an async function -function M.wrap(func, argc) +function M.wrap(argc, func) assert(type(func) == 'function') assert(type(argc) == 'number') return function(...) - if not M.running() then - return func(...) - end return M.wait(argc, func, ...) end end +--- create([argc, ] func) +--- --- Use this to create a function which executes in an async context but --- called from a non-async context. Inherently this cannot return anything --- since it is non-blocking +--- +--- If argc is not provided, then the created async function cannot be continued +--- --- @generic F: function ---- @param func F ---- @param argc integer +--- @param argc_or_func F|integer +--- @param func? F --- @return F -function M.create(func, argc) - argc = argc or 0 - return function(...) - if M.running() then - return func(...) - end - local callback = select(argc + 1, ...) - return run(func, callback, unpack({ ... }, 1, argc)) +function M.create(argc_or_func, func) + local argc --- @type integer + if type(argc_or_func) == 'function' then + assert(not func) + func = argc_or_func + elseif type(argc_or_func) == 'number' then + assert(type(func) == 'function') + argc = argc_or_func end -end ---- Use this to create a function which executes in an async context but ---- called from a non-async context. Inherently this cannot return anything ---- since it is non-blocking ---- @generic F: function ---- @param func F ---- @return async F -function M.void(func) + --- @cast func function + return function(...) - if M.running() then - return func(...) - end - return run(func, nil, ...) + local callback = argc and select(argc + 1, ...) or nil + return run(func, callback, unpack({ ... }, 1, argc)) end end ---- @generic F: function ---- @param func F ---- @param ... any ---- @return async F -function M.run(func, ...) - return run(func, nil, ...) -end - --- An async function that when called will yield to the Neovim scheduler to be --- able to call the API. -M.scheduler = M.wrap(vim.schedule, 1) - ---- @param buf? integer ---- @param cb function -M.scheduler_if_buf_valid = M.wrap(function(buf, cb) - vim.schedule(function() - if not buf or vim.api.nvim_buf_is_valid(buf) then - cb() - end - end) -end, 2) +M.scheduler = M.wrap(1, vim.schedule) return M diff --git a/lua/gitsigns/attach.lua b/lua/gitsigns/attach.lua index efd7aa003..d8e1c301b 100644 --- a/lua/gitsigns/attach.lua +++ b/lua/gitsigns/attach.lua @@ -130,7 +130,7 @@ local function on_attach_pre(bufnr) local gitdir, toplevel if config._on_attach_pre then --- @type {gitdir: string?, toplevel: string?} - local res = async.wrap(config._on_attach_pre, 2)(bufnr) + local res = async.wait(2, config._on_attach_pre, bufnr) dprintf('ran on_attach_pre with result %s', vim.inspect(res)) if type(res) == 'table' then if type(res.gitdir) == 'string' then @@ -291,7 +291,10 @@ local attach_throttled = throttle_by_id(function(cbuf, ctx, aucmd) if not git_obj and not ctx then git_obj = try_worktrees(cbuf, file, encoding) - async.scheduler_if_buf_valid(cbuf) + async.scheduler() + if not api.nvim_buf_is_valid(cbuf) then + return + end end if not git_obj then @@ -300,7 +303,11 @@ local attach_throttled = throttle_by_id(function(cbuf, ctx, aucmd) end local repo = git_obj.repo - async.scheduler_if_buf_valid(cbuf) + async.scheduler() + if not api.nvim_buf_is_valid(cbuf) then + return + end + Status:update(cbuf, { head = repo.abbrev_head, root = repo.toplevel, @@ -329,7 +336,10 @@ local attach_throttled = throttle_by_id(function(cbuf, ctx, aucmd) -- On windows os.tmpname() crashes in callback threads so initialise this -- variable on the main thread. - async.scheduler_if_buf_valid(cbuf) + async.scheduler() + if not api.nvim_buf_is_valid(cbuf) then + return + end if config.on_attach and config.on_attach(cbuf) == false then dprint('User on_attach() returned false') @@ -431,7 +441,7 @@ end --- • {base}: (string|nil) --- The git revision that the file should be compared to. --- @param _trigger? string -M.attach = async.void(function(bufnr, ctx, _trigger) +M.attach = async.create(3, function(bufnr, ctx, _trigger) attach_throttled(bufnr or api.nvim_get_current_buf(), ctx, _trigger) end) diff --git a/lua/gitsigns/cache.lua b/lua/gitsigns/cache.lua index 0fa566104..8fe597e73 100644 --- a/lua/gitsigns/cache.lua +++ b/lua/gitsigns/cache.lua @@ -70,9 +70,9 @@ function M.new(o) return setmetatable(o, { __index = CacheEntry }) end -local sleep = async.wrap(function(duration, cb) +local sleep = async.wrap(2, function(duration, cb) vim.defer_fn(cb, duration) -end, 2) +end) --- @private function CacheEntry:wait_for_hunks() @@ -93,15 +93,19 @@ local BLAME_THRESHOLD_LEN = 1000000 --- @param opts Gitsigns.CurrentLineBlameOpts --- @return table? function CacheEntry:run_blame(lnum, opts) + local bufnr = self.bufnr local blame_cache --- @type table? repeat - local buftext = util.buf_lines(self.bufnr) - local tick = vim.b[self.bufnr].changedtick + local buftext = util.buf_lines(bufnr) + local tick = vim.b[bufnr].changedtick local lnum0 = #buftext > BLAME_THRESHOLD_LEN and lnum or nil -- TODO(lewis6991): Cancel blame on changedtick blame_cache = self.git_obj:run_blame(buftext, lnum0, opts.ignore_whitespace) - async.scheduler_if_buf_valid(self.bufnr) - until vim.b[self.bufnr].changedtick == tick + async.scheduler() + if not vim.api.nvim_buf_is_valid(bufnr) then + return + end + until vim.b[bufnr].changedtick == tick return blame_cache end diff --git a/lua/gitsigns/cli.lua b/lua/gitsigns/cli.lua index 8b2d8161f..89f1f9072 100644 --- a/lua/gitsigns/cli.lua +++ b/lua/gitsigns/cli.lua @@ -1,5 +1,4 @@ local async = require('gitsigns.async') -local void = require('gitsigns.async').void local log = require('gitsigns.debug.log') local dprintf = log.dprintf @@ -69,16 +68,14 @@ local function print_nonnil(x) end end -local select = async.wrap(vim.ui.select, 3) - -M.run = void(function(params) +M.run = async.create(1, function(params) local __FUNC__ = 'cli.run' local pos_args_raw, named_args_raw = parse_args(params.args) local func = pos_args_raw[1] if not func then - func = select(M.complete('', 'Gitsigns '), {}) --[[@as string]] + func = async.wait(3, vim.ui.select, M.complete('', 'Gitsigns '), {}) --[[@as string]] if not func then return end diff --git a/lua/gitsigns/current_line_blame.lua b/lua/gitsigns/current_line_blame.lua index 700c9408a..ce6692793 100644 --- a/lua/gitsigns/current_line_blame.lua +++ b/lua/gitsigns/current_line_blame.lua @@ -141,7 +141,10 @@ end --- Update function, must be called in async context --- @param bufnr integer local function update0(bufnr) - async.scheduler_if_buf_valid(bufnr) + async.scheduler() + if not api.nvim_buf_is_valid(bufnr) then + return + end if insert_mode() then return @@ -182,7 +185,7 @@ local function update0(bufnr) handle_blame_info(bufnr, lnum, blame_info, opts) end -local update = async.void(debounce.throttle_by_id(update0)) +local update = async.create(1, debounce.throttle_by_id(update0)) --- @type fun(bufnr: integer) local update_debounced diff --git a/lua/gitsigns/diff_int.lua b/lua/gitsigns/diff_int.lua index 96c391ff7..f9445273c 100644 --- a/lua/gitsigns/diff_int.lua +++ b/lua/gitsigns/diff_int.lua @@ -26,6 +26,7 @@ end --- @type Gitsigns.RawDifffn local run_diff_xdl_async = async.wrap( + 4, --- @param a string --- @param b string --- @param linematch? integer @@ -55,7 +56,7 @@ local run_diff_xdl_async = async.wrap( return bit.band(flags0, bit.lshift(1, pos)) ~= 0 end - --- @diagnostic disable-next-line:return-type-mismatch + --- @diagnostic disable-next-line:redundant-return-value return vim.mpack.encode(vim.diff(a0, b0, { result_type = 'indices', algorithm = algorithm, @@ -73,8 +74,7 @@ local run_diff_xdl_async = async.wrap( end ) :queue(a, b, opts.algorithm, flags, linematch) - end, - 4 + end ) --- @param fa string[] diff --git a/lua/gitsigns/diffthis.lua b/lua/gitsigns/diffthis.lua index d82a70407..65b864659 100644 --- a/lua/gitsigns/diffthis.lua +++ b/lua/gitsigns/diffthis.lua @@ -8,15 +8,12 @@ local message = require('gitsigns.message') local throttle_by_id = require('gitsigns.debounce').throttle_by_id ---- @type fun(opts: table): string -local input = async.wrap(vim.ui.input, 2) - local M = {} --- @param bufnr integer --- @param dbufnr integer --- @param base string? -local bufread = async.void(function(bufnr, dbufnr, base) +local bufread = async.create(3, function(bufnr, dbufnr, base) local bcache = cache[bufnr] local comp_rev = bcache:get_compare_rev(util.calc_base(base)) local text --- @type string[] @@ -28,7 +25,10 @@ local bufread = async.void(function(bufnr, dbufnr, base) if err then error(err, 2) end - async.scheduler_if_buf_valid(bufnr) + async.scheduler() + if not api.nvim_buf_is_valid(bufnr) then + return + end end vim.bo[dbufnr].fileformat = vim.bo[bufnr].fileformat @@ -47,11 +47,14 @@ end) --- @param bufnr integer --- @param dbufnr integer --- @param base string? -local bufwrite = async.void(function(bufnr, dbufnr, base) +local bufwrite = async.create(3, function(bufnr, dbufnr, base) local bcache = cache[bufnr] local buftext = util.buf_lines(dbufnr) bcache.git_obj:stage_lines(buftext) - async.scheduler_if_buf_valid(bufnr) + async.scheduler() + if not api.nvim_buf_is_valid(bufnr) then + return + end vim.bo[dbufnr].modified = false -- If diff buffer base matches the bcache base then also update the -- signs. @@ -140,7 +143,7 @@ end --- @param base string? --- @param opts Gitsigns.DiffthisOpts -M.diffthis = async.void(function(base, opts) +M.diffthis = async.create(2, function(base, opts) if vim.wo.diff then return end @@ -165,7 +168,7 @@ end) --- @param bufnr integer --- @param base string -M.show = async.void(function(bufnr, base) +M.show = async.create(2, function(bufnr, base) local bufname = create_show_buf(bufnr, base) if not bufname then return @@ -182,7 +185,7 @@ local function should_reload(bufnr) end local response --- @type string? while not vim.tbl_contains({ 'O', 'L' }, response) do - response = input({ + response = async.wait(2, vim.ui.input, { prompt = 'Warning: The git index has changed and the buffer was changed as well. [O]K, (L)oad File:', }) end @@ -191,7 +194,7 @@ end -- This function needs to be throttled as there is a call to vim.ui.input --- @param bufnr integer -M.update = throttle_by_id(async.void(function(bufnr) +M.update = throttle_by_id(async.create(1, function(bufnr) if not vim.wo.diff then return end diff --git a/lua/gitsigns/git.lua b/lua/gitsigns/git.lua index 3710c5bf4..9df6ad280 100644 --- a/lua/gitsigns/git.lua +++ b/lua/gitsigns/git.lua @@ -19,7 +19,7 @@ local check_version = require('gitsigns.git.version').check local M = {} --- @type fun(cmd: string[], opts?: vim.SystemOpts): vim.SystemCompleted -local asystem = async.wrap(system, 3) +local asystem = async.wrap(3, system) --- @param file string --- @return boolean @@ -62,10 +62,11 @@ M.Repo = Repo --- @field command? string --- @field ignore_error? boolean +--- @async --- @param args string[] --- @param spec? Gitsigns.Git.JobSpec --- @return string[] stdout, string? stderr -local git_command = async.create(function(args, spec) +local function git_command(args, spec) spec = spec or {} local cmd = { @@ -116,8 +117,9 @@ local git_command = async.create(function(args, spec) end return stdout_lines, stderr -end, 2) +end +--- @async --- @param file_cmp string --- @param file_buf string --- @param indent_heuristic? boolean @@ -141,6 +143,7 @@ function M.diff(file_cmp, file_buf, indent_heuristic, diff_algo) }) end +--- @async --- @param gitdir string --- @param head_str string --- @param path string @@ -193,6 +196,7 @@ local function normalize_path(path) return path end +--- @async --- @param path string --- @param cmd? string --- @param gitdir? string @@ -251,6 +255,7 @@ end -------------------------------------------------------------------------------- --- Run git command the with the objects gitdir and toplevel +--- @async --- @param args string[] --- @param spec? Gitsigns.Git.JobSpec --- @return string[] stdout, string? stderr @@ -315,10 +320,12 @@ function Repo:get_show_text(object, encoding) return stdout, stderr end +--- @async function Repo:update_abbrev_head() self.abbrev_head = M.get_repo_info(self.toplevel).abbrev_head end +--- @async --- @param dir string --- @param gitdir? string --- @param toplevel? string diff --git a/lua/gitsigns/git/version.lua b/lua/gitsigns/git/version.lua index 5360f815a..fedcc954d 100644 --- a/lua/gitsigns/git/version.lua +++ b/lua/gitsigns/git/version.lua @@ -8,7 +8,7 @@ local system = require('gitsigns.system').system local M = {} --- @type fun(cmd: string[], opts?: vim.SystemOpts): vim.SystemCompleted -local asystem = async.wrap(system, 3) +local asystem = async.wrap(3, system) --- @class (exact) Gitsigns.Version --- @field major integer diff --git a/lua/gitsigns/manager.lua b/lua/gitsigns/manager.lua index ed8f277f9..f0a9a000a 100644 --- a/lua/gitsigns/manager.lua +++ b/lua/gitsigns/manager.lua @@ -434,28 +434,27 @@ local function update_show_deleted(bufnr) end end ---- @param bufnr? integer +--- @async +--- @nodiscard +--- @param bufnr integer --- @param check_compare_text? boolean ---- @param cb fun(boolean) -M.buf_check = async.wrap(function(bufnr, check_compare_text, cb) - vim.schedule(function() - if bufnr then - if not api.nvim_buf_is_valid(bufnr) then - dprint('Buffer not valid, aborting') - return cb(false) - end - if not cache[bufnr] then - dprint('Has detached, aborting') - return cb(false) - end - if check_compare_text and not cache[bufnr].compare_text then - dprint('compare_text was invalid, aborting') - return cb(false) - end - end - cb(true) - end) -end, 3) +--- @return boolean +function M.schedule(bufnr, check_compare_text) + async.scheduler() + if not api.nvim_buf_is_valid(bufnr) then + dprint('Buffer not valid, aborting') + return false + end + if not cache[bufnr] then + dprint('Has detached, aborting') + return false + end + if check_compare_text and not cache[bufnr].compare_text then + dprint('compare_text was invalid, aborting') + return false + end + return true +end local update_cnt = 0 @@ -466,7 +465,7 @@ local update_cnt = 0 --- @param bufnr integer M.update = throttle_by_id(function(bufnr) local __FUNC__ = 'update' - if not M.buf_check(bufnr) then + if not M.schedule(bufnr) then return end local bcache = cache[bufnr] @@ -481,7 +480,7 @@ M.update = throttle_by_id(function(bufnr) if not bcache.compare_text or config._refresh_staged_on_update or file_mode then bcache.compare_text = git_obj:get_show_text(compare_rev) - if not M.buf_check(bufnr, true) then + if not M.schedule(bufnr, true) then return end end @@ -489,7 +488,7 @@ M.update = throttle_by_id(function(bufnr) local buftext = util.buf_lines(bufnr) bcache.hunks = run_diff(bcache.compare_text, buftext) - if not M.buf_check(bufnr) then + if not M.schedule(bufnr) then return end @@ -497,12 +496,12 @@ M.update = throttle_by_id(function(bufnr) if not bcache.compare_text_head or config._refresh_staged_on_update then 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) - if not M.buf_check(bufnr, true) then + if not M.schedule(bufnr, true) then return end end local hunks_head = run_diff(bcache.compare_text_head, buftext) - if not M.buf_check(bufnr) then + if not M.schedule(bufnr) then return end bcache.hunks_staged = gs_hunks.filter_common(hunks_head, bcache.hunks) @@ -600,7 +599,7 @@ function M.setup() signs_staged = Signs.new(config._signs_staged, 'staged') end - M.update_debounced = debounce_trailing(config.update_debounce, async.void(M.update)) + M.update_debounced = debounce_trailing(config.update_debounce, async.create(1, M.update)) end return M diff --git a/lua/gitsigns/watcher.lua b/lua/gitsigns/watcher.lua index fffd5ee03..951a162c7 100644 --- a/lua/gitsigns/watcher.lua +++ b/lua/gitsigns/watcher.lua @@ -11,8 +11,6 @@ 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 @@ -46,7 +44,9 @@ local function handle_moved(bufnr, old_relpath) git_obj.file = git_obj.repo.toplevel .. util.path_sep .. git_obj.relpath bcache.file = git_obj.file git_obj:update_file_info() - async.scheduler_if_buf_valid(bufnr) + if not manager.schedule(bufnr) then + return + end local bufexists = util.bufexists(bcache.file) local old_name = api.nvim_buf_get_name(bufnr) @@ -59,45 +59,49 @@ local function handle_moved(bufnr, old_relpath) dprintf('%s buffer %d from %s to %s', msg, bufnr, old_name, bcache.file) end -local handler = debounce_trailing( - 200, - --- @param bufnr integer - async.void(function(bufnr) - local __FUNC__ = 'watcher_handler' +--- @param bufnr integer +local watcher_handler = async.create(1, function(bufnr) + local __FUNC__ = 'watcher_handler' - -- Avoid cache hit for detached buffer - -- ref: https://github.com/lewis6991/gitsigns.nvim/issues/956 - if not buf_check(bufnr) then - return - end + -- Avoid cache hit for detached buffer + -- ref: https://github.com/lewis6991/gitsigns.nvim/issues/956 + if not manager.schedule(bufnr) then + return + end - local git_obj = cache[bufnr].git_obj + local git_obj = cache[bufnr].git_obj - git_obj.repo:update_abbrev_head() + git_obj.repo:update_abbrev_head() - buf_check(bufnr) + if not manager.schedule(bufnr) then + return + end - Status:update(bufnr, { head = git_obj.repo.abbrev_head }) + Status:update(bufnr, { head = git_obj.repo.abbrev_head }) - local was_tracked = git_obj.object_name ~= nil - local old_relpath = git_obj.relpath + local was_tracked = git_obj.object_name ~= nil + local old_relpath = git_obj.relpath - git_obj:update_file_info() - buf_check(bufnr) + git_obj:update_file_info() + if not manager.schedule(bufnr) then + return + end - if config.watch_gitdir.follow_files and was_tracked and not git_obj.object_name then - -- File was tracked but is no longer tracked. Must of been removed or - -- moved. Check if it was moved and switch to it. - handle_moved(bufnr, old_relpath) - buf_check(bufnr) + if config.watch_gitdir.follow_files and was_tracked and not git_obj.object_name then + -- File was tracked but is no longer tracked. Must of been removed or + -- moved. Check if it was moved and switch to it. + handle_moved(bufnr, old_relpath) + if not manager.schedule(bufnr) then + return end + end + + cache[bufnr]:invalidate(true) - cache[bufnr]:invalidate(true) + require('gitsigns.manager').update(bufnr) +end) - require('gitsigns.manager').update(bufnr) - end), - 1 -) +local watcher_handler_debounced = debounce_trailing(200, watcher_handler, 1) --- vim.inspect but on one line --- @param x any @@ -138,7 +142,7 @@ function M.watch_gitdir(bufnr, gitdir) dprint(info) - handler(bufnr) + watcher_handler_debounced(bufnr) end) return w end