local Task = require("lazy.manage.task") local Config = require("lazy.core.config") ---@class RunnerOpts ---@field pipeline (string|{[1]:string, [string]:any})[] ---@field plugins? LazyPlugin[]|fun(plugin:LazyPlugin):any? ---@alias PipelineStep {task:string, opts?:TaskOptions} ---@alias LazyRunnerTask {co:thread, status: {task?:LazyTask, waiting?:boolean}} ---@class Runner ---@field _plugins LazyPlugin[] ---@field _running LazyRunnerTask[] ---@field _pipeline PipelineStep[] ---@field _on_done fun()[] ---@field _opts RunnerOpts local Runner = {} ---@param opts RunnerOpts function Runner.new(opts) local self = setmetatable({}, { __index = Runner }) self._opts = opts or {} local plugins = self._opts.plugins if type(plugins) == "function" then self._plugins = vim.tbl_filter(plugins, Config.plugins) else self._plugins = plugins or Config.plugins end self._running = {} self._on_done = {} ---@param step string|(TaskOptions|{[1]:string}) self._pipeline = vim.tbl_map(function(step) return type(step) == "string" and { task = step } or { task = step[1], opts = step } end, self._opts.pipeline) return self end ---@param entry LazyRunnerTask function Runner:_resume(entry) if entry.status.task and not entry.status.task:is_done() then return true end local ok, status = coroutine.resume(entry.co) entry.status = ok and status return entry.status ~= nil end function Runner:resume(waiting) local running = false for _, entry in ipairs(self._running) do if entry.status then if waiting and entry.status.waiting then entry.status.waiting = false end if not entry.status.waiting and self:_resume(entry) then running = true end end end return running or (not waiting and self:resume(true)) end function Runner:start() for _, plugin in pairs(self._plugins) do local co = coroutine.create(self.run_pipeline) local ok, status = coroutine.resume(co, self, plugin) if ok then table.insert(self._running, { co = co, status = status }) end end local check = vim.loop.new_check() check:start(function() if self:resume() then return end check:stop() self._running = {} for _, cb in ipairs(self._on_done) do vim.schedule(cb) end self._on_done = {} end) end ---@async ---@param plugin LazyPlugin function Runner:run_pipeline(plugin) for _, step in ipairs(self._pipeline) do if step.task == "wait" then coroutine.yield({ waiting = true }) else local task = self:queue(plugin, step.task, step.opts) if task then coroutine.yield({ task = task }) assert(task:is_done()) if task.error then return end end end end end ---@param plugin LazyPlugin ---@param task_type string ---@param task_opts? TaskOptions ---@return LazyTask? function Runner:queue(plugin, task_type, task_opts) assert(self._running) local def = vim.split(task_type, ".", { plain = true }) ---@type LazyTaskDef local task_def = require("lazy.manage.task." .. def[1])[def[2]] assert(task_def) if not (task_def.skip and task_def.skip(plugin, task_opts)) then local task = Task.new(plugin, def[2], task_def.run, task_opts) task:start() return task end end -- Execute the callback async when done. -- When no callback is specified, this will wait sync ---@param cb? fun() function Runner:wait(cb) if #self._running == 0 then return cb and cb() end if cb then table.insert(self._on_done, cb) else -- sync wait while #self._running > 0 do vim.wait(10) end end end return Runner