diff options
| author | Lewis Russell <lewis6991@gmail.com> | 2026-08-25 17:37:27 +0100 |
|---|---|---|
| committer | Lewis Russell <me@lewisr.dev> | 2026-09-03 19:17:25 +0100 |
| commit | ce8a897f985b2cfd1194f6c03fe5388f9bc6d6bf (patch) | |
| tree | 81a85d8e22dc906aea69e0265f2e035f800a6c9c /runtime/lua/vim/async/_future.lua | |
| parent | da4355ab8f68c4e0290134e691ab7b9dedc2a5df (diff) | |
| download | rneovim-ce8a897f985b2cfd1194f6c03fe5388f9bc6d6bf.tar.gz rneovim-ce8a897f985b2cfd1194f6c03fe5388f9bc6d6bf.tar.bz2 rneovim-ce8a897f985b2cfd1194f6c03fe5388f9bc6d6bf.zip | |
feat(lua): add vim.async
Problem: Nvim has many Lua APIs that start callback-driven work: timers,
jobs, libuv handles, and other event-loop tasks. Callers that need to
sequence or cancel that work have to build their own coroutine wrappers,
task bookkeeping, and cleanup rules. This makes async control flow hard
to share, test, and document.
Solution: Add `vim.async`, a structured-concurrency module vendored from
async.nvim. It provides task handles, await/pawait helpers,
sleep/timeout helpers, completion-order iteration, and semaphores on top
of Nvim's event loop.
The API follows the same broad model as Trio: async work has an owner,
tasks are awaited explicitly, and cancellation is cooperative. Include
generated vimdoc with an introductory overview and examples, a news
entry, and functional tests for the new module.
AI-assisted
Diffstat (limited to 'runtime/lua/vim/async/_future.lua')
| -rw-r--r-- | runtime/lua/vim/async/_future.lua | 77 |
1 files changed, 77 insertions, 0 deletions
diff --git a/runtime/lua/vim/async/_future.lua b/runtime/lua/vim/async/_future.lua new file mode 100644 index 0000000000..46b036b88c --- /dev/null +++ b/runtime/lua/vim/async/_future.lua @@ -0,0 +1,77 @@ +-- LuaLS cannot model the generic annotations used by this vendored implementation. +---@diagnostic disable: no-unknown, undefined-doc-name, luadoc-miss-symbol, missing-return, missing-return-value, param-type-mismatch, return-type-mismatch, redundant-return-value, undefined-field, need-check-nil, await-in-sync + +local F = vim.F +local util = require('vim._core.util') + +local Future = {} +Future.__index = Future + +function Future:completed() + return self._err ~= nil or self._result ~= nil +end + +function Future:result() + if not self:completed() then + error('Future has not completed', 2) + end + if self._err ~= nil then + return false, self._err + else + return true, F.unpack_len(self._result) + end +end + +function Future:on_complete(callback) + if self:completed() then + -- Already completed or closed + if self._err ~= nil then + callback(self._err) + else + callback(nil, F.unpack_len(self._result)) + end + return function() end + end + + local id = self._callback_pos + self._callback_pos = id + 1 + self._callbacks[id] = callback + + return function() + self._callbacks[id] = nil + end +end + +function Future:complete(err, ...) + if self:completed() then + error('Future is already completed', 2) + end + + if err ~= nil then + self._err = err + else + self._result = F.pack_len(...) + end + + local callbacks = self._callbacks + self._callbacks = {} + + local errs = {} -- Need to use pairs to avoid gaps caused by removed callbacks + for _, cb in pairs(callbacks) do + local ok, cb_err = pcall(cb, err, ...) + if not ok then + errs[#errs + 1] = util._stringify_error(util._normalize_error(cb_err)) + end + end + + if #errs > 0 then + error(table.concat(errs, '\n'), 0) + end +end + +return function() + return setmetatable({ + _callbacks = {}, + _callback_pos = 1, + }, Future) +end |