2022-11-27 10:02:28 +00:00
|
|
|
local Process = require("lazy.manage.process")
|
2022-11-28 10:04:32 +00:00
|
|
|
|
|
|
|
---@class LazyTaskDef
|
2022-11-28 21:03:44 +00:00
|
|
|
---@field skip? fun(plugin:LazyPlugin, opts?:TaskOptions):any?
|
|
|
|
---@field run fun(task:LazyTask, opts:TaskOptions)
|
2022-11-28 10:04:32 +00:00
|
|
|
|
2024-06-26 13:28:53 +01:00
|
|
|
---@alias LazyTaskState {task:LazyTask, thread:thread}
|
|
|
|
|
|
|
|
local Scheduler = {}
|
|
|
|
---@type LazyTaskState[]
|
|
|
|
Scheduler._queue = {}
|
|
|
|
Scheduler._executor = assert(vim.loop.new_check())
|
|
|
|
Scheduler._running = false
|
|
|
|
|
|
|
|
function Scheduler.step()
|
|
|
|
Scheduler._running = true
|
|
|
|
local budget = 1 * 1e6
|
|
|
|
local start = vim.loop.hrtime()
|
|
|
|
local count = #Scheduler._queue
|
|
|
|
local i = 0
|
|
|
|
while #Scheduler._queue > 0 and vim.loop.hrtime() - start < budget do
|
|
|
|
---@type LazyTaskState
|
|
|
|
local state = table.remove(Scheduler._queue, 1)
|
|
|
|
state.task:_step(state.thread)
|
|
|
|
if coroutine.status(state.thread) ~= "dead" then
|
|
|
|
table.insert(Scheduler._queue, state)
|
|
|
|
end
|
|
|
|
i = i + 1
|
|
|
|
if i >= count then
|
|
|
|
break
|
|
|
|
end
|
|
|
|
end
|
|
|
|
Scheduler._running = false
|
|
|
|
if #Scheduler._queue == 0 then
|
|
|
|
return Scheduler._executor:stop()
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
---@param state LazyTaskState
|
|
|
|
function Scheduler.add(state)
|
|
|
|
table.insert(Scheduler._queue, state)
|
|
|
|
if not Scheduler._executor:is_active() then
|
|
|
|
Scheduler._executor:start(vim.schedule_wrap(Scheduler.step))
|
|
|
|
end
|
|
|
|
end
|
2022-11-20 22:04:56 +00:00
|
|
|
|
|
|
|
---@class LazyTask
|
|
|
|
---@field plugin LazyPlugin
|
2022-11-28 21:03:44 +00:00
|
|
|
---@field name string
|
2022-11-28 10:04:32 +00:00
|
|
|
---@field output string
|
|
|
|
---@field status string
|
|
|
|
---@field error? string
|
2024-06-25 12:23:25 +01:00
|
|
|
---@field warn? string
|
2024-06-26 13:28:53 +01:00
|
|
|
---@field private _task fun(task:LazyTask, opts:TaskOptions)
|
2022-11-28 21:03:44 +00:00
|
|
|
---@field private _started? number
|
|
|
|
---@field private _ended? number
|
2022-11-28 10:04:32 +00:00
|
|
|
---@field private _opts TaskOptions
|
2024-06-26 13:28:53 +01:00
|
|
|
---@field private _threads thread[]
|
2022-11-20 22:04:56 +00:00
|
|
|
local Task = {}
|
|
|
|
|
2022-11-28 21:03:44 +00:00
|
|
|
---@class TaskOptions: {[string]:any}
|
2022-11-28 06:35:58 +00:00
|
|
|
---@field on_done? fun(task:LazyTask)
|
2022-11-20 22:04:56 +00:00
|
|
|
|
|
|
|
---@param plugin LazyPlugin
|
2022-11-28 21:03:44 +00:00
|
|
|
---@param name string
|
2022-11-23 15:12:43 +00:00
|
|
|
---@param opts? TaskOptions
|
2022-11-28 10:04:32 +00:00
|
|
|
---@param task fun(task:LazyTask)
|
2022-11-28 21:03:44 +00:00
|
|
|
function Task.new(plugin, name, task, opts)
|
2022-11-20 22:04:56 +00:00
|
|
|
local self = setmetatable({}, {
|
|
|
|
__index = Task,
|
|
|
|
})
|
2022-11-28 10:04:32 +00:00
|
|
|
self._opts = opts or {}
|
2024-06-26 13:28:53 +01:00
|
|
|
self._threads = {}
|
2022-11-28 10:04:32 +00:00
|
|
|
self._task = task
|
2022-11-28 21:03:44 +00:00
|
|
|
self._started = nil
|
2022-11-20 22:04:56 +00:00
|
|
|
self.plugin = plugin
|
2022-11-28 21:03:44 +00:00
|
|
|
self.name = name
|
2022-11-20 22:04:56 +00:00
|
|
|
self.output = ""
|
|
|
|
self.status = ""
|
2022-12-17 13:28:41 +00:00
|
|
|
---@param other LazyTask
|
|
|
|
plugin._.tasks = vim.tbl_filter(function(other)
|
|
|
|
return other.name ~= name or other:is_running()
|
2024-06-26 13:28:53 +01:00
|
|
|
end, plugin._.tasks or {})
|
2022-11-28 10:19:50 +00:00
|
|
|
table.insert(plugin._.tasks, self)
|
2022-11-20 22:04:56 +00:00
|
|
|
return self
|
|
|
|
end
|
|
|
|
|
2022-11-28 10:04:32 +00:00
|
|
|
function Task:has_started()
|
2022-11-28 21:03:44 +00:00
|
|
|
return self._started ~= nil
|
2022-11-20 22:04:56 +00:00
|
|
|
end
|
|
|
|
|
2024-06-26 13:28:53 +01:00
|
|
|
function Task:has_ended()
|
|
|
|
return self._ended ~= nil
|
|
|
|
end
|
|
|
|
|
2022-11-28 10:04:32 +00:00
|
|
|
function Task:is_done()
|
2024-06-26 13:28:53 +01:00
|
|
|
return self:has_started() and self:has_ended()
|
2022-11-20 22:04:56 +00:00
|
|
|
end
|
|
|
|
|
2022-11-28 10:04:32 +00:00
|
|
|
function Task:is_running()
|
2024-06-26 13:28:53 +01:00
|
|
|
return self:has_started() and not self:has_ended()
|
2022-11-20 22:04:56 +00:00
|
|
|
end
|
|
|
|
|
2022-11-28 10:04:32 +00:00
|
|
|
function Task:start()
|
2024-06-26 13:28:53 +01:00
|
|
|
assert(not self:has_started(), "task already started")
|
|
|
|
assert(not self:has_ended(), "task already done")
|
|
|
|
|
2024-03-22 07:58:36 +00:00
|
|
|
self._started = vim.uv.hrtime()
|
2024-06-26 13:28:53 +01:00
|
|
|
self:async(function()
|
|
|
|
self._task(self, self._opts)
|
|
|
|
end)
|
2022-11-20 22:04:56 +00:00
|
|
|
end
|
|
|
|
|
2024-06-25 12:23:25 +01:00
|
|
|
---@param msg string|string[]
|
|
|
|
---@param severity? vim.diagnostic.Severity
|
|
|
|
function Task:notify(msg, severity)
|
|
|
|
local var = severity == vim.diagnostic.severity.ERROR and "error"
|
|
|
|
or severity == vim.diagnostic.severity.WARN and "warn"
|
|
|
|
or "output"
|
|
|
|
msg = type(msg) == "table" and table.concat(msg, "\n") or msg
|
|
|
|
---@cast msg string
|
|
|
|
---@diagnostic disable-next-line: no-unknown
|
|
|
|
self[var] = self[var] and (self[var] .. "\n" .. msg) or msg
|
|
|
|
self.status = msg
|
|
|
|
vim.api.nvim_exec_autocmds("User", { pattern = "LazyRender", modeline = false })
|
|
|
|
end
|
|
|
|
|
|
|
|
---@param msg string|string[]
|
|
|
|
function Task:notify_error(msg)
|
|
|
|
self:notify(msg, vim.diagnostic.severity.ERROR)
|
|
|
|
end
|
|
|
|
|
|
|
|
---@param msg string|string[]
|
|
|
|
function Task:notify_warn(msg)
|
|
|
|
self:notify(msg, vim.diagnostic.severity.WARN)
|
|
|
|
end
|
|
|
|
|
2024-06-24 13:16:00 +01:00
|
|
|
---@param fn async fun()
|
|
|
|
function Task:async(fn)
|
|
|
|
local co = coroutine.create(fn)
|
2024-06-26 13:28:53 +01:00
|
|
|
table.insert(self._threads, co)
|
|
|
|
Scheduler.add({ task = self, thread = co })
|
2024-06-24 13:16:00 +01:00
|
|
|
end
|
|
|
|
|
2024-06-26 13:28:53 +01:00
|
|
|
---@param co thread
|
|
|
|
function Task:_step(co)
|
|
|
|
local status = coroutine.status(co)
|
|
|
|
if status == "suspended" then
|
|
|
|
local ok, res = coroutine.resume(co)
|
|
|
|
if not ok then
|
|
|
|
self:notify_error(tostring(res))
|
|
|
|
elseif res then
|
|
|
|
self:notify(tostring(res))
|
|
|
|
end
|
|
|
|
end
|
|
|
|
for _, t in ipairs(self._threads) do
|
|
|
|
if coroutine.status(t) ~= "dead" then
|
2022-11-29 06:56:59 +00:00
|
|
|
return
|
|
|
|
end
|
2022-11-28 10:04:32 +00:00
|
|
|
end
|
2024-06-26 13:28:53 +01:00
|
|
|
self:_done()
|
|
|
|
end
|
|
|
|
|
|
|
|
---@private
|
|
|
|
function Task:_done()
|
|
|
|
assert(self:has_started(), "task not started")
|
|
|
|
assert(not self:has_ended(), "task already done")
|
2024-03-22 07:58:36 +00:00
|
|
|
self._ended = vim.uv.hrtime()
|
2022-11-28 10:04:32 +00:00
|
|
|
if self._opts.on_done then
|
|
|
|
self._opts.on_done(self)
|
2022-11-23 15:12:43 +00:00
|
|
|
end
|
2023-01-13 08:00:15 +00:00
|
|
|
vim.api.nvim_exec_autocmds("User", { pattern = "LazyRender", modeline = false })
|
2022-11-28 10:04:32 +00:00
|
|
|
vim.api.nvim_exec_autocmds("User", {
|
2022-11-28 21:03:44 +00:00
|
|
|
pattern = "LazyPlugin" .. self.name:sub(1, 1):upper() .. self.name:sub(2),
|
2022-11-28 10:04:32 +00:00
|
|
|
data = { plugin = self.plugin.name },
|
|
|
|
})
|
|
|
|
end
|
|
|
|
|
2022-11-28 21:03:44 +00:00
|
|
|
function Task:time()
|
|
|
|
if not self:has_started() then
|
|
|
|
return 0
|
|
|
|
end
|
2024-06-26 13:28:53 +01:00
|
|
|
if not self:has_ended() then
|
2024-03-22 07:58:36 +00:00
|
|
|
return (vim.uv.hrtime() - self._started) / 1e6
|
2022-11-28 21:03:44 +00:00
|
|
|
end
|
|
|
|
return (self._ended - self._started) / 1e6
|
|
|
|
end
|
|
|
|
|
2024-06-26 13:28:53 +01:00
|
|
|
---@async
|
2022-11-20 22:04:56 +00:00
|
|
|
---@param cmd string
|
2022-11-28 10:04:32 +00:00
|
|
|
---@param opts? ProcessOpts
|
2022-11-20 22:04:56 +00:00
|
|
|
function Task:spawn(cmd, opts)
|
|
|
|
opts = opts or {}
|
|
|
|
local on_line = opts.on_line
|
|
|
|
local on_exit = opts.on_exit
|
|
|
|
|
|
|
|
function opts.on_line(line)
|
|
|
|
self.status = line
|
|
|
|
if on_line then
|
|
|
|
pcall(on_line, line)
|
|
|
|
end
|
2023-01-13 08:00:15 +00:00
|
|
|
vim.api.nvim_exec_autocmds("User", { pattern = "LazyRender", modeline = false })
|
2022-11-20 22:04:56 +00:00
|
|
|
end
|
|
|
|
|
2024-06-26 13:28:53 +01:00
|
|
|
local running = true
|
2022-11-28 10:04:32 +00:00
|
|
|
---@param output string
|
2022-11-20 22:04:56 +00:00
|
|
|
function opts.on_exit(ok, output)
|
2022-11-28 10:04:32 +00:00
|
|
|
self.output = self.output .. output
|
2022-11-20 22:04:56 +00:00
|
|
|
if not ok then
|
2022-11-28 10:04:32 +00:00
|
|
|
self.error = self.error and (self.error .. "\n" .. output) or output
|
2022-11-20 22:04:56 +00:00
|
|
|
end
|
|
|
|
if on_exit then
|
|
|
|
pcall(on_exit, ok, output)
|
|
|
|
end
|
2024-06-26 13:28:53 +01:00
|
|
|
running = false
|
|
|
|
end
|
|
|
|
Process.spawn(cmd, opts)
|
|
|
|
while running do
|
|
|
|
coroutine.yield()
|
2022-11-20 22:04:56 +00:00
|
|
|
end
|
2022-11-22 20:12:50 +00:00
|
|
|
end
|
2022-11-20 22:04:56 +00:00
|
|
|
|
2022-11-28 10:04:32 +00:00
|
|
|
---@param tasks (LazyTask?)[]
|
|
|
|
function Task.all_done(tasks)
|
|
|
|
for _, task in ipairs(tasks) do
|
|
|
|
if task and not task:is_done() then
|
|
|
|
return false
|
|
|
|
end
|
2022-11-23 15:12:43 +00:00
|
|
|
end
|
2022-11-28 10:04:32 +00:00
|
|
|
return true
|
2022-11-20 22:04:56 +00:00
|
|
|
end
|
|
|
|
|
2022-11-28 10:04:32 +00:00
|
|
|
function Task:wait()
|
|
|
|
while self:is_running() do
|
|
|
|
vim.wait(10)
|
2022-11-20 22:04:56 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
return Task
|