Skip to content

Commit dcbd245

Browse files
committed
fix(async): revert back to previous version 'async' module
1 parent df0c4e3 commit dcbd245

File tree

7 files changed

+257
-17
lines changed

7 files changed

+257
-17
lines changed

lua/gitlinker.lua

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ local str = require("gitlinker.commons.str")
33
local num = require("gitlinker.commons.num")
44
local LogLevels = require("gitlinker.commons.logging").LogLevels
55
local logging = require("gitlinker.commons.logging")
6-
local async = require("gitlinker.commons.async")
76

7+
local async = require("gitlinker.async")
88
local configs = require("gitlinker.configs")
99
local range = require("gitlinker.range")
1010
local linker = require("gitlinker.linker")
@@ -221,7 +221,7 @@ local _link = function(opts)
221221
lk.rev = opts.rev
222222
end
223223

224-
async.schedule()
224+
async.scheduler()
225225
local ok, url = pcall(opts.router, lk, true)
226226
-- logger:debug(
227227
-- "|link| ok:%s, url:%s, router:%s",
@@ -273,7 +273,7 @@ local _link = function(opts)
273273
end
274274

275275
--- @type fun(opts:{action:gitlinker.Action?,router:gitlinker.Router,lstart:integer,lend:integer,remote:string?,file:string?,rev:string?}):string?
276-
local _sync_link = async.sync(1, _link)
276+
local _void_link = async.void(_link)
277277

278278
--- @param args string?
279279
--- @return {router_type:string,remote:string?,file:string?,rev:string?}
@@ -332,7 +332,7 @@ local function setup(opts)
332332
local lstart = math.min(r.lstart, r.lend, command_opts.line1, command_opts.line2)
333333
local lend = math.max(r.lstart, r.lend, command_opts.line1, command_opts.line2)
334334
local parsed = _parse_args(args)
335-
_sync_link({
335+
_void_link({
336336
action = command_opts.bang and require("gitlinker.actions").system
337337
or require("gitlinker.actions").clipboard,
338338
router = function(lk)
@@ -392,7 +392,7 @@ local function link_api(opts)
392392
opts.lend = math.max(r.lstart, r.lend)
393393
end
394394

395-
_sync_link({
395+
_void_link({
396396
action = opts.action,
397397
router = opts.router,
398398
lstart = opts.lstart,
@@ -408,7 +408,7 @@ end
408408
local M = {
409409
_url_template_engine = _url_template_engine,
410410
_worker = _worker,
411-
_sync_link = _sync_link,
411+
_void_link = _void_link,
412412
_router = _router,
413413
_browse = _browse,
414414
_blame = _blame,

lua/gitlinker/async.lua

Lines changed: 239 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,239 @@
1+
---@diagnostic disable: luadoc-miss-module-name, undefined-doc-name
2+
--- Small async library for Neovim plugins
3+
--- @module async
4+
-- Store all the async threads in a weak table so we don't prevent them from
5+
-- being garbage collected
6+
local handles = setmetatable({}, { __mode = "k" })
7+
local M = {}
8+
-- Note: coroutine.running() was changed between Lua 5.1 and 5.2:
9+
-- - 5.1: Returns the running coroutine, or nil when called by the main thread.
10+
-- - 5.2: Returns the running coroutine plus a boolean, true when the running
11+
-- coroutine is the main one.
12+
--
13+
-- For LuaJIT, 5.2 behaviour is enabled with LUAJIT_ENABLE_LUA52COMPAT
14+
--
15+
-- We need to handle both.
16+
--- Returns whether the current execution context is async.
17+
---
18+
--- @treturn boolean?
19+
function M.running()
20+
local current = coroutine.running()
21+
if current and handles[current] then
22+
return true
23+
end
24+
end
25+
local function is_Async_T(handle)
26+
if
27+
handle
28+
and type(handle) == "table"
29+
and vim.is_callable(handle.cancel)
30+
and vim.is_callable(handle.is_cancelled)
31+
then
32+
return true
33+
end
34+
end
35+
local Async_T = {}
36+
-- Analogous to uv.close
37+
function Async_T:cancel(cb)
38+
-- Cancel anything running on the event loop
39+
if self._current and not self._current:is_cancelled() then
40+
self._current:cancel(cb)
41+
end
42+
end
43+
function Async_T.new(co)
44+
local handle = setmetatable({}, { __index = Async_T })
45+
handles[co] = handle
46+
return handle
47+
end
48+
-- Analogous to uv.is_closing
49+
function Async_T:is_cancelled()
50+
return self._current and self._current:is_cancelled()
51+
end
52+
--- Run a function in an async context.
53+
--- @tparam function func
54+
--- @tparam function callback
55+
--- @tparam any ... Arguments for func
56+
--- @treturn async_t Handle
57+
function M.run(func, callback, ...)
58+
vim.validate({
59+
func = { func, "function" },
60+
callback = { callback, "function", true },
61+
})
62+
local co = coroutine.create(func)
63+
local handle = Async_T.new(co)
64+
local function step(...)
65+
local ret = { coroutine.resume(co, ...) }
66+
local ok = ret[1]
67+
if not ok then
68+
local err = ret[2]
69+
error(
70+
string.format("The coroutine failed with this message:\n%s\n%s", err, debug.traceback(co))
71+
)
72+
end
73+
if coroutine.status(co) == "dead" then
74+
if callback then
75+
callback(unpack(ret, 4, table.maxn(ret)))
76+
end
77+
return
78+
end
79+
local nargs, fn = ret[2], ret[3]
80+
local args = { select(4, unpack(ret)) }
81+
assert(type(fn) == "function", "type error :: expected func")
82+
args[nargs] = step
83+
local r = fn(unpack(args, 1, nargs))
84+
if is_Async_T(r) then
85+
handle._current = r
86+
end
87+
end
88+
step(...)
89+
return handle
90+
end
91+
local function wait(argc, func, ...)
92+
vim.validate({
93+
argc = { argc, "number" },
94+
func = { func, "function" },
95+
})
96+
-- Always run the wrapped functions in xpcall and re-raise the error in the
97+
-- coroutine. This makes pcall work as normal.
98+
local function pfunc(...)
99+
local args = { ... }
100+
local cb = args[argc]
101+
args[argc] = function(...)
102+
cb(true, ...)
103+
end
104+
xpcall(func, function(err)
105+
cb(false, err, debug.traceback())
106+
end, unpack(args, 1, argc))
107+
end
108+
local ret = { coroutine.yield(argc, pfunc, ...) }
109+
local ok = ret[1]
110+
if not ok then
111+
local _, err, traceback = unpack(ret)
112+
error(string.format("Wrapped function failed: %s\n%s", err, traceback))
113+
end
114+
return unpack(ret, 2, table.maxn(ret))
115+
end
116+
--- Wait on a callback style function
117+
---
118+
--- @tparam integer? argc The number of arguments of func.
119+
--- @tparam function func callback style function to execute
120+
--- @tparam any ... Arguments for func
121+
function M.wait(...)
122+
if type(select(1, ...)) == "number" then
123+
return wait(...)
124+
end
125+
-- Assume argc is equal to the number of passed arguments.
126+
return wait(select("#", ...) - 1, ...)
127+
end
128+
--- Use this to create a function which executes in an async context but
129+
--- called from a non-async context. Inherently this cannot return anything
130+
--- since it is non-blocking
131+
--- @tparam function func
132+
--- @tparam number argc The number of arguments of func. Defaults to 0
133+
--- @tparam boolean strict Error when called in non-async context
134+
--- @treturn function(...):async_t
135+
function M.create(func, argc, strict)
136+
vim.validate({
137+
func = { func, "function" },
138+
argc = { argc, "number", true },
139+
})
140+
argc = argc or 0
141+
return function(...)
142+
if M.running() then
143+
if strict then
144+
error("This function must run in a non-async context")
145+
end
146+
return func(...)
147+
end
148+
local callback = select(argc + 1, ...)
149+
return M.run(func, callback, unpack({ ... }, 1, argc))
150+
end
151+
end
152+
--- Create a function which executes in an async context but
153+
--- called from a non-async context.
154+
--- @tparam function func
155+
--- @tparam boolean strict Error when called in non-async context
156+
function M.void(func, strict)
157+
vim.validate({ func = { func, "function" } })
158+
return function(...)
159+
if M.running() then
160+
if strict then
161+
error("This function must run in a non-async context")
162+
end
163+
return func(...)
164+
end
165+
return M.run(func, nil, ...)
166+
end
167+
end
168+
--- Creates an async function with a callback style function.
169+
---
170+
--- @tparam function func A callback style function to be converted. The last argument must be the callback.
171+
--- @tparam integer argc The number of arguments of func. Must be included.
172+
--- @tparam boolean strict Error when called in non-async context
173+
--- @treturn function Returns an async function
174+
function M.wrap(func, argc, strict)
175+
vim.validate({
176+
argc = { argc, "number" },
177+
})
178+
return function(...)
179+
if not M.running() then
180+
if strict then
181+
error("This function must run in an async context")
182+
end
183+
return func(...)
184+
end
185+
return M.wait(argc, func, ...)
186+
end
187+
end
188+
--- Run a collection of async functions (`thunks`) concurrently and return when
189+
--- all have finished.
190+
--- @tparam function[] thunks
191+
--- @tparam integer n Max number of thunks to run concurrently
192+
--- @tparam function interrupt_check Function to abort thunks between calls
193+
function M.join(thunks, n, interrupt_check)
194+
local function run(finish)
195+
if #thunks == 0 then
196+
return finish()
197+
end
198+
local remaining = { select(n + 1, unpack(thunks)) }
199+
local to_go = #thunks
200+
local ret = {}
201+
local function cb(...)
202+
ret[#ret + 1] = { ... }
203+
to_go = to_go - 1
204+
if to_go == 0 then
205+
finish(ret)
206+
elseif not interrupt_check or not interrupt_check() then
207+
if #remaining > 0 then
208+
local next_task = table.remove(remaining)
209+
next_task(cb)
210+
end
211+
end
212+
end
213+
for i = 1, math.min(n, #thunks) do
214+
thunks[i](cb)
215+
end
216+
end
217+
if not M.running() then
218+
return run
219+
end
220+
return M.wait(1, false, run)
221+
end
222+
--- Partially applying arguments to an async function
223+
--- @tparam function fn
224+
--- @param ... arguments to apply to `fn`
225+
function M.curry(fn, ...)
226+
local args = { ... }
227+
local nargs = select("#", ...)
228+
return function(...)
229+
local other = { ... }
230+
for i = 1, select("#", ...) do
231+
args[nargs + i] = other[i]
232+
end
233+
fn(unpack(args))
234+
end
235+
end
236+
--- An async function that when called will yield to the Neovim scheduler to be
237+
--- able to call the neovim API.
238+
M.scheduler = M.wrap(vim.schedule, 1, false)
239+
return M

lua/gitlinker/git.lua

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
local logging = require("gitlinker.commons.logging")
22
local spawn = require("gitlinker.commons.spawn")
33
local uv = require("gitlinker.commons.uv")
4-
local async = require("gitlinker.commons.async")
4+
5+
local async = require("gitlinker.async")
56

67
--- @class gitlinker.CmdResult
78
--- @field stdout string[]
@@ -42,7 +43,7 @@ function CmdResult:print_err(default)
4243
end
4344

4445
--- NOTE: async functions can't have optional parameters so wrap it into another function without '_'
45-
local _run_cmd = async.wrap(3, function(args, cwd, callback)
46+
local _run_cmd = async.wrap(function(args, cwd, callback)
4647
local result = CmdResult:new()
4748
local logger = logging.get("gitlinker")
4849
logger:debug(string.format("|_run_cmd| args:%s, cwd:%s", vim.inspect(args), vim.inspect(cwd)))
@@ -63,7 +64,7 @@ local _run_cmd = async.wrap(3, function(args, cwd, callback)
6364
logger:debug(string.format("|_run_cmd| result:%s", vim.inspect(result)))
6465
callback(result)
6566
end)
66-
end)
67+
end, 3)
6768

6869
-- wrap the git command to do the right thing always
6970
--- @package

lua/gitlinker/linker.lua

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
local logging = require("gitlinker.commons.logging")
22
local str = require("gitlinker.commons.str")
3-
local async = require("gitlinker.commons.async")
43

4+
local async = require("gitlinker.async")
55
local git = require("gitlinker.git")
66
local path = require("gitlinker.path")
77
local giturlparser = require("gitlinker.giturlparser")
@@ -89,7 +89,7 @@ local function make_linker(remote, file, rev)
8989
end
9090
-- logger.debug("|linker - Linker:make| rev:%s", vim.inspect(rev))
9191

92-
async.schedule()
92+
async.scheduler()
9393

9494
if not file_provided then
9595
local buf_path_on_root = path.buffer_relpath(root) --[[@as string]]
@@ -114,7 +114,7 @@ local function make_linker(remote, file, rev)
114114
-- vim.inspect(file_in_rev_result)
115115
-- )
116116

117-
async.schedule()
117+
async.scheduler()
118118

119119
local file_changed = false
120120
if not file_provided then

spec/gitlinker/git_spec.lua

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ describe("gitlinker.git", function()
1111
vim.cmd([[ edit lua/gitlinker.lua ]])
1212
end)
1313

14-
local async = require("gitlinker.commons.async")
14+
local async = require("gitlinker.async")
1515
local git = require("gitlinker.git")
1616
local path = require("gitlinker.path")
1717
local gitlinker = require("gitlinker")

spec/gitlinker/linker_spec.lua

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ describe("gitlinker.linker", function()
1313
vim.cmd([[ edit lua/gitlinker.lua ]])
1414
end)
1515

16-
local async = require("gitlinker.commons.async")
16+
local async = require("gitlinker.async")
1717
local github_actions = os.getenv("GITHUB_ACTIONS") == "true"
1818
local linker = require("gitlinker.linker")
1919
describe("[make_linker]", function()

spec/gitlinker_spec.lua

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -696,9 +696,9 @@ describe("gitlinker", function()
696696
end)
697697
end)
698698

699-
describe("[_sync_link]", function()
699+
describe("[_void_link]", function()
700700
it("link browse", function()
701-
gitlinker._sync_link({
701+
gitlinker._void_link({
702702
action = require("gitlinker.actions").clipboard,
703703
router = function(lk)
704704
return require("gitlinker")._router("browse", lk)
@@ -708,7 +708,7 @@ describe("gitlinker", function()
708708
})
709709
end)
710710
it("link blame", function()
711-
gitlinker._sync_link({
711+
gitlinker._void_link({
712712
action = require("gitlinker.actions").clipboard,
713713
router = function(lk)
714714
return require("gitlinker")._router("blame", lk)

0 commit comments

Comments
 (0)