feat: interleave attaches on startup

This commit is contained in:
Lewis Russell 2023-11-23 10:23:00 +00:00 committed by Lewis Russell
parent 59bd933faa
commit 5fc573f2d2
6 changed files with 122 additions and 93 deletions

View File

@ -1,5 +1,4 @@
local void = require('gitsigns.async').void
local scheduler = require('gitsigns.async').scheduler
local async = require('gitsigns.async')
local gs_config = require('gitsigns.config')
local config = gs_config.config
@ -15,7 +14,7 @@ local M = {}
local cwd_watcher ---@type uv.uv_fs_event_t?
local update_cwd_head = void(function()
local update_cwd_head = async.void(function()
local paths = vim.fs.find('.git', {
limit = 1,
upward = true,
@ -56,7 +55,7 @@ local update_cwd_head = void(function()
head = info.abbrev_head
end
scheduler()
async.scheduler()
vim.g.gitsigns_head = head
if not gitdir then
@ -74,9 +73,9 @@ local update_cwd_head = void(function()
local update_head = debounce_trailing(
100,
void(function()
async.void(function()
local new_head = git.get_repo_info(cwd).abbrev_head
scheduler()
async.scheduler()
vim.g.gitsigns_head = new_head
end)
)
@ -85,7 +84,7 @@ local update_cwd_head = void(function()
cwd_watcher:start(
towatch,
{},
void(function(err)
async.void(function(err)
local __FUNC__ = 'cwd_watcher_cb'
if err then
dprintf('Git dir update error: %s', err)
@ -116,27 +115,31 @@ local function setup_debug()
log.verbose = config._verbose
end
--- @async
local function setup_attach()
scheduler()
async.scheduler()
api.nvim_create_autocmd({ 'BufRead', 'BufNewFile', 'BufWritePost' }, {
group = 'gitsigns',
callback = function(data)
M.attach(nil, nil, data.event)
require('gitsigns.attach').attach(data.buf, nil, data.event)
end,
})
-- Attach to all open buffers
for _, buf in ipairs(api.nvim_list_bufs()) do
if api.nvim_buf_is_loaded(buf) and api.nvim_buf_get_name(buf) ~= '' then
M.attach(buf, nil, 'setup')
scheduler()
-- 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')
end
end
end
--- @async
local function setup_cwd_head()
scheduler()
async.scheduler()
update_cwd_head()
-- Need to debounce in case some plugin changes the cwd too often
-- (like vim-grepper)
@ -156,7 +159,7 @@ end
---
--- @param cfg table|nil Configuration for Gitsigns.
--- See |gitsigns-usage| for more details.
M.setup = void(function(cfg)
M.setup = async.void(function(cfg)
gs_config.build(cfg)
if vim.fn.executable('git') == 0 then

View File

@ -191,8 +191,6 @@ end
--- @param staged? boolean
--- @return Gitsigns.Hunk.Hunk[]? hunks
local function get_hunks(bufnr, bcache, greedy, staged)
local hunks --- @type Gitsigns.Hunk.Hunk[]
if greedy then
-- Re-run the diff without linematch
local buftext = util.buf_lines(bufnr)
@ -205,8 +203,8 @@ local function get_hunks(bufnr, bcache, greedy, staged)
if not text then
return
end
hunks = run_diff(text, buftext, false)
async.scheduler()
local hunks = run_diff(text, buftext, false)
manager.buf_check(bufnr)
return hunks
end
@ -225,20 +223,20 @@ end
local function get_hunk(bufnr, range, greedy, staged)
local bcache = cache[bufnr]
local hunks = get_hunks(bufnr, bcache, greedy, staged)
local hunk --- @type Gitsigns.Hunk.Hunk?
if range then
table.sort(range)
local top, bot = range[1], range[2]
hunk = Hunks.create_partial_hunk(hunks or {}, top, bot)
hunk.added.lines = api.nvim_buf_get_lines(bufnr, top - 1, bot, false)
hunk.removed.lines = vim.list_slice(
bcache.compare_text,
hunk.removed.start,
hunk.removed.start + hunk.removed.count - 1
)
else
hunk = get_cursor_hunk(bufnr, hunks)
if not range then
return get_cursor_hunk(bufnr, hunks)
end
table.sort(range)
local top, bot = range[1], range[2]
local hunk = Hunks.create_partial_hunk(hunks or {}, top, bot)
hunk.added.lines = api.nvim_buf_get_lines(bufnr, top - 1, bot, false)
hunk.removed.lines = vim.list_slice(
bcache.compare_text,
hunk.removed.start,
hunk.removed.start + hunk.removed.count - 1
)
return hunk
end

View File

@ -1,7 +1,7 @@
-- Order by highest number of return types
local M = {}
--- @class Gitsigns.Async_T
--- @field _current Gitsigns.Async_T
local Async_T = {}
-- Handle for an object currently running on the event loop.
@ -20,9 +20,9 @@ local Async_T = {}
--
-- We need to handle both.
-- Store all the async threads in a weak table so we don't prevent them from
-- being garbage collected
--- @type table<thread,uv.uv_handle_t>
--- Store all the async threads in a weak table so we don't prevent them from
--- being garbage collected
--- @type table<thread,Gitsigns.Async_T>
local handles = setmetatable({}, { __mode = 'k' })
--- Returns whether the current execution context is async.
@ -31,6 +31,7 @@ function M.running()
if current and handles[current] then
return true
end
return false
end
local function is_Async_T(handle)
@ -44,7 +45,8 @@ local function is_Async_T(handle)
end
end
-- Analogous to uv.close
--- Analogous to uv.close
--- @param cb function
function Async_T:cancel(cb)
-- Cancel anything running on the event loop
if self._current and not self._current:is_cancelled() then
@ -52,17 +54,24 @@ function Async_T:cancel(cb)
end
end
--- @param co thread
--- @return Gitsigns.Async_T
function Async_T.new(co)
local handle = setmetatable({}, { __index = Async_T })
handles[co] = handle
return handle
end
-- Analogous to uv.is_closing
--- Analogous to uv.is_closing
--- @return boolean
function Async_T:is_cancelled()
return self._current and self._current:is_cancelled()
end
--- @param func function
--- @param callback? fun(...: any)
--- @param ... any
--- @return Gitsigns.Async_T
local function run(func, callback, ...)
local co = coroutine.create(func)
local handle = Async_T.new(co)
@ -85,7 +94,7 @@ local function run(func, callback, ...)
return
end
--- @type integer, function
--- @type integer, fun(...: any): any
local nargs, fn = ret[2], ret[3]
assert(type(fn) == 'function', 'type error :: expected func')
@ -95,6 +104,7 @@ local function run(func, callback, ...)
local r = fn(unpack(args, 1, nargs))
if is_Async_T(r) then
--- @cast r Gitsigns.Async_T
handle._current = r
end
end
@ -103,6 +113,10 @@ local function run(func, callback, ...)
return handle
end
--- @param argc integer
--- @param func function
--- @param ... any
--- @return any ...
function M.wait(argc, func, ...)
-- Always run the wrapped functions in xpcall and re-raise the error in the
-- coroutine. This makes pcall work as normal.
@ -121,17 +135,17 @@ function M.wait(argc, func, ...)
local ok = ret[1]
if not ok then
local _, err, traceback = unpack(ret)
local err, traceback = ret[2], ret[3]
error(string.format('Wrapped function failed: %s\n%s', err, traceback))
end
return unpack(ret, 2, table.maxn(ret))
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.
---@return function: Returns an async function
--- 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.
--- @return function: Returns an async function
function M.wrap(func, argc)
assert(argc)
return function(...)
@ -142,13 +156,13 @@ function M.wrap(func, argc)
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
---@param argc integer
---@return F
--- 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
--- @param argc integer
--- @return F
function M.create(func, argc)
argc = argc or 0
return function(...)
@ -160,12 +174,12 @@ function M.create(func, argc)
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
--- 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)
return function(...)
if M.running() then
@ -175,11 +189,20 @@ function M.void(func)
end
end
---An async function that when called will yield to the Neovim scheduler to be
---able to call the API.
--- @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

View File

@ -1,21 +1,18 @@
local async = require('gitsigns.async')
local git = require('gitsigns.git')
local manager = require('gitsigns.manager')
local log = require('gitsigns.debug.log')
local dprintf = log.dprintf
local dprint = log.dprint
local manager = require('gitsigns.manager')
local hl = require('gitsigns.highlight')
local gs_cache = require('gitsigns.cache')
local cache = gs_cache.cache
local Status = require('gitsigns.status')
local gs_config = require('gitsigns.config')
local config = gs_config.config
local config = require('gitsigns.config').config
local void = require('gitsigns.async').void
local util = require('gitsigns.util')
local throttle_by_id = require('gitsigns.debounce').throttle_by_id
@ -165,33 +162,9 @@ local function try_worktrees(_bufnr, file, encoding)
end
end
local done_setup = false
function M._setup()
if done_setup then
return
end
done_setup = true
manager.setup()
hl.setup_highlights()
api.nvim_create_autocmd('ColorScheme', {
group = 'gitsigns',
callback = hl.setup_highlights,
})
api.nvim_create_autocmd('OptionSet', {
group = 'gitsigns',
pattern = { 'fileformat', 'bomb', 'eol' },
callback = function()
require('gitsigns.actions').refresh()
end,
})
-- vimpgrep creates and deletes lots of buffers so attaching to each one will
-- waste lots of resource and even slow down vimgrep.
--- vimpgrep creates and deletes lots of buffers so attaching to each one will
--- waste lots of resource and even slow down vimgrep.
local function setup_vimgrep_autocmds()
api.nvim_create_autocmd('QuickFixCmdPre', {
group = 'gitsigns',
pattern = '*vimgrep*',
@ -207,6 +180,29 @@ function M._setup()
attach_disabled = false
end,
})
end
local done_setup = false
function M._setup()
if done_setup then
return
end
done_setup = true
manager.setup()
require('gitsigns.highlight').setup()
api.nvim_create_autocmd('OptionSet', {
group = 'gitsigns',
pattern = { 'fileformat', 'bomb', 'eol' },
callback = function()
require('gitsigns.actions').refresh()
end,
})
setup_vimgrep_autocmds()
require('gitsigns.current_line_blame').setup()
@ -223,7 +219,7 @@ end
--- @field commit string
--- @field base string
--- Ensure attaches cannot be interleaved.
--- Ensure attaches cannot be interleaved for the same buffer.
--- Since attaches are asynchronous we need to make sure an attach isn't
--- performed whilst another one is in progress.
--- @param cbuf integer
@ -435,7 +431,7 @@ end
--- • {base}: (string|nil)
--- The git revision that the file should be compared to.
--- @param _trigger? string
M.attach = void(function(bufnr, ctx, _trigger)
M.attach = async.void(function(bufnr, ctx, _trigger)
attach_throttled(bufnr or api.nvim_get_current_buf(), ctx, _trigger)
end)

View File

@ -102,6 +102,7 @@ local function check_version(version)
return true
end
--- @async
--- @param version string
function M._set_version(version)
if version ~= 'auto' then

View File

@ -331,4 +331,12 @@ function M.setup_highlights()
end
end
function M.setup()
M.setup_highlights()
api.nvim_create_autocmd('ColorScheme', {
group = 'gitsigns',
callback = M.setup_highlights,
})
end
return M