Skip to content

Commit

Permalink
fix(async): revert back to previous version 'async' module (#258)
Browse files Browse the repository at this point in the history
  • Loading branch information
linrongbin16 authored Jan 18, 2025
1 parent df0c4e3 commit bc27701
Show file tree
Hide file tree
Showing 13 changed files with 389 additions and 59 deletions.
5 changes: 5 additions & 0 deletions .github/ISSUE_TEMPLATE/bug.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,8 @@ assignees: ""
3. how do you configure this plugin?

It's ...

4. What's your OS and Neovim version?

The OS is: Windows 10 x86_64 (Intel/AMD chip), MacOS M1 chip, Ubuntu/Fedora/Arch x86_64 (Intel/AMD chip), ...
The Neovim version (`nvim --version`) is: ...
8 changes: 3 additions & 5 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
@@ -1,20 +1,18 @@
# Regression Test

## Platforms
## Test Platforms

- [ ] windows
- [ ] macOS
- [ ] linux

## Hosts
## Test Hosts

- [ ] Test on [github.com](https://github.com).
- [ ] Test on [gitlab.com](https://gitlab.com).
- [ ] Test on [bitbucket.org](https://bitbucket.org).
- [ ] Test on [codeberg.org](https://codeberg.org).
- [ ] Test on [git.samba.org](https://git.samba.org).

## Functions
## Test Functions

- [ ] Use `GitLink(!)` to copy git link (or open in browser).
- [ ] Use `GitLink(!) blame` to copy the `/blame` link (or open in browser).
Expand Down
12 changes: 6 additions & 6 deletions lua/gitlinker.lua
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ local str = require("gitlinker.commons.str")
local num = require("gitlinker.commons.num")
local LogLevels = require("gitlinker.commons.logging").LogLevels
local logging = require("gitlinker.commons.logging")
local async = require("gitlinker.commons.async")

local async = require("gitlinker.async")
local configs = require("gitlinker.configs")
local range = require("gitlinker.range")
local linker = require("gitlinker.linker")
Expand Down Expand Up @@ -221,7 +221,7 @@ local _link = function(opts)
lk.rev = opts.rev
end

async.schedule()
async.scheduler()
local ok, url = pcall(opts.router, lk, true)
-- logger:debug(
-- "|link| ok:%s, url:%s, router:%s",
Expand Down Expand Up @@ -273,7 +273,7 @@ local _link = function(opts)
end

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

--- @param args string?
--- @return {router_type:string,remote:string?,file:string?,rev:string?}
Expand Down Expand Up @@ -332,7 +332,7 @@ local function setup(opts)
local lstart = math.min(r.lstart, r.lend, command_opts.line1, command_opts.line2)
local lend = math.max(r.lstart, r.lend, command_opts.line1, command_opts.line2)
local parsed = _parse_args(args)
_sync_link({
_void_link({
action = command_opts.bang and require("gitlinker.actions").system
or require("gitlinker.actions").clipboard,
router = function(lk)
Expand Down Expand Up @@ -392,7 +392,7 @@ local function link_api(opts)
opts.lend = math.max(r.lstart, r.lend)
end

_sync_link({
_void_link({
action = opts.action,
router = opts.router,
lstart = opts.lstart,
Expand All @@ -408,7 +408,7 @@ end
local M = {
_url_template_engine = _url_template_engine,
_worker = _worker,
_sync_link = _sync_link,
_void_link = _void_link,
_router = _router,
_browse = _browse,
_blame = _blame,
Expand Down
239 changes: 239 additions & 0 deletions lua/gitlinker/async.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
---@diagnostic disable: luadoc-miss-module-name, undefined-doc-name
--- Small async library for Neovim plugins
--- @module async
-- Store all the async threads in a weak table so we don't prevent them from
-- being garbage collected
local handles = setmetatable({}, { __mode = "k" })
local M = {}
-- Note: coroutine.running() was changed between Lua 5.1 and 5.2:
-- - 5.1: Returns the running coroutine, or nil when called by the main thread.
-- - 5.2: Returns the running coroutine plus a boolean, true when the running
-- coroutine is the main one.
--
-- For LuaJIT, 5.2 behaviour is enabled with LUAJIT_ENABLE_LUA52COMPAT
--
-- We need to handle both.
--- Returns whether the current execution context is async.
---
--- @treturn boolean?
function M.running()
local current = coroutine.running()
if current and handles[current] then
return true
end
end
local function is_Async_T(handle)
if
handle
and type(handle) == "table"
and vim.is_callable(handle.cancel)
and vim.is_callable(handle.is_cancelled)
then
return true
end
end
local Async_T = {}
-- Analogous to uv.close
function Async_T:cancel(cb)
-- Cancel anything running on the event loop
if self._current and not self._current:is_cancelled() then
self._current:cancel(cb)
end
end
function Async_T.new(co)
local handle = setmetatable({}, { __index = Async_T })
handles[co] = handle
return handle
end
-- Analogous to uv.is_closing
function Async_T:is_cancelled()
return self._current and self._current:is_cancelled()
end
--- Run a function in an async context.
--- @tparam function func
--- @tparam function callback
--- @tparam any ... Arguments for func
--- @treturn async_t Handle
function M.run(func, callback, ...)
vim.validate({
func = { func, "function" },
callback = { callback, "function", true },
})
local co = coroutine.create(func)
local handle = Async_T.new(co)
local function step(...)
local ret = { coroutine.resume(co, ...) }
local ok = ret[1]
if not ok then
local err = ret[2]
error(
string.format("The coroutine failed with this message:\n%s\n%s", err, debug.traceback(co))
)
end
if coroutine.status(co) == "dead" then
if callback then
callback(unpack(ret, 4, table.maxn(ret)))
end
return
end
local nargs, fn = ret[2], ret[3]
local args = { select(4, unpack(ret)) }
assert(type(fn) == "function", "type error :: expected func")
args[nargs] = step
local r = fn(unpack(args, 1, nargs))
if is_Async_T(r) then
handle._current = r
end
end
step(...)
return handle
end
local function wait(argc, func, ...)
vim.validate({
argc = { argc, "number" },
func = { func, "function" },
})
-- Always run the wrapped functions in xpcall and re-raise the error in the
-- coroutine. This makes pcall work as normal.
local function pfunc(...)
local args = { ... }
local cb = args[argc]
args[argc] = function(...)
cb(true, ...)
end
xpcall(func, function(err)
cb(false, err, debug.traceback())
end, unpack(args, 1, argc))
end
local ret = { coroutine.yield(argc, pfunc, ...) }
local ok = ret[1]
if not ok then
local _, err, traceback = unpack(ret)
error(string.format("Wrapped function failed: %s\n%s", err, traceback))
end
return unpack(ret, 2, table.maxn(ret))
end
--- Wait on a callback style function
---
--- @tparam integer? argc The number of arguments of func.
--- @tparam function func callback style function to execute
--- @tparam any ... Arguments for func
function M.wait(...)
if type(select(1, ...)) == "number" then
return wait(...)
end
-- Assume argc is equal to the number of passed arguments.
return wait(select("#", ...) - 1, ...)
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
--- @tparam function func
--- @tparam number argc The number of arguments of func. Defaults to 0
--- @tparam boolean strict Error when called in non-async context
--- @treturn function(...):async_t
function M.create(func, argc, strict)
vim.validate({
func = { func, "function" },
argc = { argc, "number", true },
})
argc = argc or 0
return function(...)
if M.running() then
if strict then
error("This function must run in a non-async context")
end
return func(...)
end
local callback = select(argc + 1, ...)
return M.run(func, callback, unpack({ ... }, 1, argc))
end
end
--- Create a function which executes in an async context but
--- called from a non-async context.
--- @tparam function func
--- @tparam boolean strict Error when called in non-async context
function M.void(func, strict)
vim.validate({ func = { func, "function" } })
return function(...)
if M.running() then
if strict then
error("This function must run in a non-async context")
end
return func(...)
end
return M.run(func, nil, ...)
end
end
--- Creates an async function with a callback style function.
---
--- @tparam function func A callback style function to be converted. The last argument must be the callback.
--- @tparam integer argc The number of arguments of func. Must be included.
--- @tparam boolean strict Error when called in non-async context
--- @treturn function Returns an async function
function M.wrap(func, argc, strict)
vim.validate({
argc = { argc, "number" },
})
return function(...)
if not M.running() then
if strict then
error("This function must run in an async context")
end
return func(...)
end
return M.wait(argc, func, ...)
end
end
--- Run a collection of async functions (`thunks`) concurrently and return when
--- all have finished.
--- @tparam function[] thunks
--- @tparam integer n Max number of thunks to run concurrently
--- @tparam function interrupt_check Function to abort thunks between calls
function M.join(thunks, n, interrupt_check)
local function run(finish)
if #thunks == 0 then
return finish()
end
local remaining = { select(n + 1, unpack(thunks)) }
local to_go = #thunks
local ret = {}
local function cb(...)
ret[#ret + 1] = { ... }
to_go = to_go - 1
if to_go == 0 then
finish(ret)
elseif not interrupt_check or not interrupt_check() then
if #remaining > 0 then
local next_task = table.remove(remaining)
next_task(cb)
end
end
end
for i = 1, math.min(n, #thunks) do
thunks[i](cb)
end
end
if not M.running() then
return run
end
return M.wait(1, false, run)
end
--- Partially applying arguments to an async function
--- @tparam function fn
--- @param ... arguments to apply to `fn`
function M.curry(fn, ...)
local args = { ... }
local nargs = select("#", ...)
return function(...)
local other = { ... }
for i = 1, select("#", ...) do
args[nargs + i] = other[i]
end
fn(unpack(args))
end
end
--- An async function that when called will yield to the Neovim scheduler to be
--- able to call the neovim API.
M.scheduler = M.wrap(vim.schedule, 1, false)
return M
21 changes: 10 additions & 11 deletions lua/gitlinker/commons/fileio.lua → lua/gitlinker/commons/fio.lua
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ function FileLineReader:open(filename, batchsize)
if type(handler) ~= "number" then
error(
string.format(
"|commons.fileio - FileLineReader:open| failed to fs_open file: %s",
"|commons.fio - FileLineReader:open| failed to fs_open file: %s",
vim.inspect(filename)
)
)
Expand All @@ -31,7 +31,7 @@ function FileLineReader:open(filename, batchsize)
if type(fstat) ~= "table" then
error(
string.format(
"|commons.fileio - FileLineReader:open| failed to fs_fstat file: %s",
"|commons.fio - FileLineReader:open| failed to fs_fstat file: %s",
vim.inspect(filename)
)
)
Expand Down Expand Up @@ -67,7 +67,7 @@ function FileLineReader:_read_chunk()
if read_err then
error(
string.format(
"|commons.fileio - FileLineReader:_read_chunk| failed to fs_read file: %s, read_error:%s, read_name:%s",
"|commons.fio - FileLineReader:_read_chunk| failed to fs_read file: %s, read_error:%s, read_name:%s",
vim.inspect(self.filename),
vim.inspect(read_err),
vim.inspect(read_name)
Expand Down Expand Up @@ -199,12 +199,12 @@ end
--- @alias commons.AsyncReadFileOnComplete fun(data:string?):any
--- @alias commons.AsyncReadFileOnError fun(step:string?,err:string?):any
--- @param filename string
--- @param on_complete commons.AsyncReadFileOnComplete
--- @param opts {trim:boolean?,on_error:commons.AsyncReadFileOnError?}?
M.asyncreadfile = function(filename, on_complete, opts)
opts = opts or { trim = false }
opts.trim = type(opts.trim) == "boolean" and opts.trim or false
--- @param opts {on_complete:commons.AsyncReadFileOnComplete,on_error:commons.AsyncReadFileOnError?,trim:boolean?}
M.asyncreadfile = function(filename, opts)
assert(type(opts) == "table")
assert(type(opts.on_complete) == "function")

opts.trim = type(opts.trim) == "boolean" and opts.trim or false
if type(opts.on_error) ~= "function" then
opts.on_error = function(step1, err1)
error(
Expand Down Expand Up @@ -240,11 +240,10 @@ M.asyncreadfile = function(filename, on_complete, opts)
uv.fs_close(fd --[[@as integer]], function(close_complete_err)
if opts.trim and type(data) == "string" then
local trimmed_data = vim.trim(data)
on_complete(trimmed_data)
opts.on_complete(trimmed_data)
else
on_complete(data)
opts.on_complete(data)
end

if close_complete_err then
opts.on_error("fs_close complete", close_complete_err)
end
Expand Down
2 changes: 1 addition & 1 deletion lua/gitlinker/commons/platform.lua
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ local os_name = uv.os_uname().sysname
local os_name_valid = type(os_name) == "string" and string.len(os_name) > 0

M.OS_NAME = os_name
M.IS_WINDOWS = os_name_valid and os_name:gmatch("Windows") ~= nil
M.IS_WINDOWS = os_name_valid and os_name:match("Windows") ~= nil
M.IS_MAC = os_name_valid and os_name:match("Darwin") ~= nil
M.IS_BSD = vim.fn.has("bsd") > 0
M.IS_LINUX = os_name_valid and os_name:match("Linux") ~= nil
Expand Down
Loading

0 comments on commit bc27701

Please sign in to comment.