-
-
Notifications
You must be signed in to change notification settings - Fork 309
async await using libuv #83
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
a096073
b363ee0
621bfb9
b226549
eea0b24
3fe213a
db2f958
02e938e
7b9c2ac
2ccda6b
4fe5f84
23553f3
7062489
dab2d92
de8ac26
430a2d0
1bc875a
68af03b
8915094
e1e44b1
9234048
6906da0
60368ff
53ab866
b682e78
7fc1214
02fe675
1c4559b
0fd0f1a
dc68752
c5efd4d
83378a0
2e3ef66
bab7509
578cd63
0c96921
8384d3d
fbd402c
1666282
e24a703
f74b094
522a7b8
ffcf332
2c9f3a9
58ac59d
73c4c81
e3e3e8f
14c734d
79640e9
92afbb9
fc4557f
4767627
60ea7d9
1752ae5
df921db
374d7cd
981a291
2765ee5
6dfa6fc
5f2d6e5
5233604
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
local a = require('plenary.async_lib.async') | ||
local async, await = a.async, a.await | ||
|
||
return setmetatable({}, {__index = function(t, k) | ||
return async(function(...) | ||
-- if we are in a fast event await the scheduler | ||
if vim.in_fast_event() then | ||
await(a.scheduler()) | ||
end | ||
|
||
vim.api[k](...) | ||
end) | ||
end}) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,201 @@ | ||
local co = coroutine | ||
local errors = require('plenary.errors') | ||
local traceback_error = errors.traceback_error | ||
|
||
local M = {} | ||
|
||
---@class Future | ||
---Something that will give a value when run | ||
|
||
---Executes a future with a callback when it is done | ||
---@param future Future: the future to execute | ||
---@param callback function: the callback to call when done | ||
local execute = function(future, callback) | ||
assert(type(future) == "function", "type error :: expected func") | ||
local thread = co.create(future) | ||
|
||
local step | ||
step = function(...) | ||
local res = {co.resume(thread, ...)} | ||
local stat = res[1] | ||
local ret = {select(2, unpack(res))} | ||
|
||
if not stat then | ||
error(string.format("The coroutine failed with this message: %s", ret[1])) | ||
end | ||
|
||
if co.status(thread) == "dead" then | ||
(callback or function() end)(unpack(ret)) | ||
else | ||
assert(#ret == 1, "expected a single return value") | ||
local returned_future = ret[1] | ||
assert(type(returned_future) == "function", "type error :: expected func") | ||
returned_future(step) | ||
end | ||
end | ||
|
||
step() | ||
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 | ||
M.wrap = function(func, argc) | ||
if type(func) ~= "function" then | ||
traceback_error("type error :: expected func, got " .. type(func)) | ||
end | ||
|
||
if type(argc) ~= "number" and argc ~= "vararg" then | ||
traceback_error("expected argc to be a number or string literal 'vararg'") | ||
end | ||
|
||
return function(...) | ||
local params = {...} | ||
|
||
local function future(step) | ||
if step then | ||
if type(argc) == "number" then | ||
params[argc] = step | ||
else | ||
table.insert(params, step) -- change once not optional | ||
end | ||
oberblastmeister marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return func(unpack(params)) | ||
else | ||
return co.yield(future) | ||
end | ||
end | ||
return future | ||
end | ||
end | ||
|
||
---Return a new future that when run will run all futures concurrently. | ||
---@param futures table: the futures that you want to join | ||
---@return Future: returns a future | ||
M.join = M.wrap(function(futures, step) | ||
local len = #futures | ||
local results = {} | ||
local done = 0 | ||
|
||
if len == 0 then | ||
return step(results) | ||
end | ||
|
||
for i, future in ipairs(futures) do | ||
assert(type(future) == "function", "type error :: future must be function") | ||
|
||
local callback = function(...) | ||
results[i] = {...} | ||
done = done + 1 | ||
if done == len then | ||
step(results) | ||
end | ||
end | ||
|
||
future(callback) | ||
end | ||
end, 2) | ||
|
||
---Returns a future that when run will select the first future that finishes | ||
---@param futures table: The future that you want to select | ||
---@return Future | ||
M.select = M.wrap(function(futures, step) | ||
local selected = false | ||
|
||
for _, future in ipairs(futures) do | ||
assert(type(future) == "function", "type error :: future must be function") | ||
|
||
local callback = function(...) | ||
if not selected then | ||
selected = true | ||
step(...) | ||
end | ||
end | ||
|
||
future(callback) | ||
end | ||
end, 2) | ||
|
||
---Use this to either run a future concurrently and then do something else | ||
---or use it to run a future with a callback in a non async context | ||
---@param future Future | ||
---@param callback function | ||
M.run = function(future, callback) | ||
future(callback or function() end) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. what about making |
||
end | ||
|
||
---Same as run but runs multiple futures | ||
---@param futures table | ||
---@param callback function | ||
M.run_all = function(futures, callback) | ||
M.run(M.join(futures), callback) | ||
end | ||
|
||
---Await a future, yielding the current function | ||
---@param future Future | ||
---@return any: returns the result of the future when it is done | ||
M.await = function(future) | ||
assert(type(future) == "function", "type error :: expected function to await") | ||
return future(nil) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It seems a bit weird that here we pass There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. They are supposed to be completely opposite functions which is why they are not consistent. |
||
end | ||
|
||
---Same as await but can await multiple futures. | ||
---If the futures have libuv leaf futures they will be run concurrently | ||
---@param futures table | ||
---@return table: returns a table of results that each future returned. Note that if the future returns multiple values they will be packed into a table. | ||
M.await_all = function(futures) | ||
assert(type(futures) == "table", "type error :: expected table") | ||
return M.await(M.join(futures)) | ||
end | ||
|
||
---suspend a coroutine | ||
M.suspend = co.yield | ||
|
||
---create a async scope | ||
M.scope = function(func) | ||
M.run(M.future(func)) | ||
end | ||
|
||
--- Future a :: a -> (a -> ()) | ||
--- turns this signature | ||
--- ... -> Future a | ||
--- into this signature | ||
--- ... -> () | ||
M.void = function(async_func) | ||
return function(...) | ||
async_func(...)(function() end) | ||
end | ||
end | ||
|
||
---creates an async function | ||
---@param func function | ||
---@return function: returns an async function | ||
M.async = function(func) | ||
if type(func) ~= "function" then | ||
traceback_error("type error :: expected func, got " .. type(func)) | ||
end | ||
|
||
return function(...) | ||
local args = {...} | ||
local function future(step) | ||
if step == nil then | ||
return func(unpack(args)) | ||
else | ||
execute(future, step) | ||
end | ||
end | ||
return future | ||
end | ||
end | ||
|
||
---creates a future | ||
---@param func function | ||
---@return Future | ||
M.future = function(func) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. does this create a future? I'd like to rename this maybe to |
||
return M.async(func)() | ||
end | ||
|
||
---An async function that when awaited will await the scheduler to be able to call the api. | ||
M.scheduler = M.wrap(vim.schedule, 1) | ||
|
||
return M |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
local exports = require('plenary.async_lib.async') | ||
exports.uv = require('plenary.async_lib.uv_async') | ||
exports.util = require('plenary.async_lib.util') | ||
exports.lsp = require('plenary.async_lib.lsp') | ||
exports.api = require('plenary.async_lib.api') | ||
exports.tests = require('plenary.async_lib.tests') | ||
|
||
exports.tests.add_globals = function() | ||
a = exports | ||
async = exports.async | ||
await = exports.await | ||
await_all = exports.await_all | ||
|
||
-- must prefix with a or stack overflow, plenary.test harness already added it | ||
a.describe = exports.tests.describe | ||
-- must prefix with a or stack overflow | ||
a.it = exports.tests.it | ||
end | ||
|
||
exports.tests.add_to_env = function() | ||
local env = getfenv(2) | ||
|
||
env.a = exports | ||
env.async = exports.async | ||
env.await = exports.await | ||
env.await_all = exports.await_all | ||
|
||
-- must prefix with a or stack overflow, plenary.test harness already added it | ||
env.a.describe = exports.tests.describe | ||
-- must prefix with a or stack overflow | ||
env.a.it = exports.tests.it | ||
|
||
setfenv(2, env) | ||
end | ||
|
||
return exports |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
local a = require('plenary.async_lib.async') | ||
|
||
local M = {} | ||
|
||
---Same as vim.lsp.buf_request but works with async await | ||
M.buf_request = a.wrap(vim.lsp.buf_request, 4) | ||
|
||
return M |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
local M = {} | ||
|
||
Deque = {} | ||
Deque.__index = Deque | ||
|
||
---@class Deque | ||
---A double ended queue | ||
--- | ||
---@return Deque | ||
function Deque.new() | ||
-- the indexes are created with an offset so that the indices are consequtive | ||
-- otherwise, when both pushleft and pushright are used, the indices will have a 1 length hole in the middle | ||
return setmetatable({first = 0, last = -1}, Deque) | ||
end | ||
|
||
---push to the left of the deque | ||
---@param value any | ||
function Deque:pushleft(value) | ||
local first = self.first - 1 | ||
self.first = first | ||
self[first] = value | ||
end | ||
|
||
---push to the right of the deque | ||
---@param value any | ||
function Deque:pushright(value) | ||
local last = self.last + 1 | ||
self.last = last | ||
self[last] = value | ||
end | ||
|
||
---pop from the left of the deque | ||
---@return any | ||
function Deque:popleft() | ||
local first = self.first | ||
if first > self.last then return nil end | ||
local value = self[first] | ||
self[first] = nil -- to allow garbage collection | ||
self.first = first + 1 | ||
return value | ||
end | ||
|
||
---pops from the right of the deque | ||
---@return any | ||
function Deque:popright() | ||
local last = self.last | ||
if self.first > last then return nil end | ||
local value = self[last] | ||
self[last] = nil -- to allow garbage collection | ||
self.last = last - 1 | ||
return value | ||
end | ||
|
||
---checks if the deque is empty | ||
---@return boolean | ||
function Deque:is_empty() | ||
return self:len() == 0 | ||
end | ||
|
||
---returns the number of elements of the deque | ||
---@return number | ||
function Deque:len() | ||
return self.last - self.first + 1 | ||
end | ||
|
||
---returns and iterator of the indices and values starting from the left | ||
---@return function | ||
function Deque:ipairs_left() | ||
local i = self.first | ||
|
||
return function() | ||
local res = self[i] | ||
local idx = i | ||
|
||
if res then | ||
i = i + 1 | ||
|
||
return idx, res | ||
end | ||
end | ||
end | ||
|
||
---returns and iterator of the indices and values starting from the right | ||
---@return function | ||
function Deque:ipairs_right() | ||
local i = self.last | ||
|
||
return function() | ||
local res = self[i] | ||
local idx = i | ||
|
||
if res then | ||
i = i - 1 -- advance the iterator before we return | ||
|
||
return idx, res | ||
end | ||
end | ||
end | ||
|
||
---removes all values from the deque | ||
---@return nil | ||
function Deque:clear() | ||
for i, _ in self:ipairs_left() do | ||
self[i] = nil | ||
end | ||
self.first = 0 | ||
self.last = -1 | ||
end | ||
|
||
M.Deque = Deque | ||
|
||
return M |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
local a = require('plenary.async_lib.async') | ||
local util = require('plenary.async_lib.util') | ||
|
||
local M = {} | ||
|
||
M.describe = function(s, func) | ||
describe(s, util.will_block(a.future(func))) | ||
end | ||
|
||
M.it = function(s, func) | ||
it(s, util.will_block(a.future(func))) | ||
end | ||
|
||
return M |
Uh oh!
There was an error while loading. Please reload this page.