Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
LUAROCKS_PATH_CMD = luarocks path --no-bin --lua-version 5.1
BUSTED = eval $$(luarocks path --no-bin --lua-version 5.1) && busted --lua nlua
TEST_DIR = spec

.PHONY: test
test:
@echo "Running tests..."
@if [ -n "$(file)" ]; then \
$(BUSTED) $(file); \
else \
$(BUSTED) $(TEST_DIR); \
fi
110 changes: 110 additions & 0 deletions lua/guard/_async.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
local M = {}

local max_timeout = 30000
local copcall = package.loaded.jit and pcall or require('coxpcall').pcall

--- @param thread thread
--- @param on_finish fun(err: string?, ...:any)
--- @param ... any
local function resume(thread, on_finish, ...)
--- @type {n: integer, [1]:boolean, [2]:string|function}
local ret = vim.F.pack_len(coroutine.resume(thread, ...))
local stat = ret[1]

if not stat then
-- Coroutine had error
on_finish(ret[2] --[[@as string]])
elseif coroutine.status(thread) == 'dead' then
-- Coroutine finished
on_finish(nil, unpack(ret, 2, ret.n))
else
local fn = ret[2]
--- @cast fn -string

--- @type boolean, string?
local ok, err = copcall(fn, function(...)
resume(thread, on_finish, ...)
end)

if not ok then
on_finish(err)
end
end
end

--- @param func async fun(): ...:any
--- @param on_finish? fun(err: string?, ...:any)
function M.run(func, on_finish)
local res --- @type {n:integer, [integer]:any}?
resume(coroutine.create(func), function(err, ...)
res = vim.F.pack_len(err, ...)
if on_finish then
on_finish(err, ...)
end
end)

return {
--- @param timeout? integer
--- @return any ... return values of `func`
wait = function(_self, timeout)
vim.wait(timeout or max_timeout, function()
return res ~= nil
end)
assert(res, 'timeout')
if res[1] then
error(res[1])
end
return unpack(res, 2, res.n)
end,
}
end

--- Asynchronous blocking wait
--- @async
--- @param argc integer
--- @param fun function
--- @param ... any func arguments
--- @return any ...
function M.await(argc, fun, ...)
assert(coroutine.running(), 'Async.await() must be called from an async function')
local args = vim.F.pack_len(...) --- @type {n:integer, [integer]:any}

--- @param callback fun(...:any)
return coroutine.yield(function(callback)
args[argc] = assert(callback)
fun(unpack(args, 1, math.max(argc, args.n)))
end)
end

--- @async
--- @param max_jobs integer
--- @param funs (async fun())[]
function M.join(max_jobs, funs)
if #funs == 0 then
return
end

max_jobs = math.min(max_jobs, #funs)

--- @type (async fun())[]
local remaining = { select(max_jobs + 1, unpack(funs)) }
local to_go = #funs

M.await(1, function(on_finish)
local function run_next()
to_go = to_go - 1
if to_go == 0 then
on_finish()
elseif #remaining > 0 then
local next_fun = table.remove(remaining)
M.run(next_fun, run_next)
end
end

for i = 1, max_jobs do
M.run(funs[i], run_next)
end
end)
end

return M
Loading
Loading