refactor: moved manage functionality to its own module

This commit is contained in:
Folke Lemaitre 2022-11-27 11:02:28 +01:00
parent 42c2fb42c8
commit 6dc45ada55
No known key found for this signature in database
GPG key ID: 41F8B1FBACAE2040
6 changed files with 22 additions and 24 deletions

124
lua/lazy/manage/init.lua Normal file
View file

@ -0,0 +1,124 @@
local Config = require("lazy.core.config")
local Task = require("lazy.manage.task")
local Runner = require("lazy.manage.runner")
local Plugin = require("lazy.core.plugin")
local M = {}
---@alias ManagerOpts {wait?: boolean, plugins?: LazyPlugin[], clear?: boolean, show?: boolean}
---@param operation TaskType
---@param opts? ManagerOpts
---@param filter? fun(plugin:LazyPlugin):boolean?
function M.run(operation, opts, filter)
opts = opts or {}
local plugins = opts.plugins or Config.plugins
if opts.clear then
M.clear()
end
if opts.show then
require("lazy.view").show()
end
---@type Runner
local runner = Runner.new()
-- install missing plugins
for _, plugin in pairs(plugins) do
if filter == nil or filter(plugin) then
runner:add(Task.new(plugin, operation))
end
end
vim.cmd([[do User LazyRender]])
-- wait for install to finish
runner:wait(function()
-- check if we need to do any post-install hooks
for _, plugin in ipairs(runner:plugins()) do
if plugin.dirty then
runner:add(Task.new(plugin, "docs"))
if plugin.opt == false or plugin.run then
runner:add(Task.new(plugin, "run"))
end
end
plugin.dirty = false
if opts.show and operation == "update" and plugin.updated and plugin.updated.from ~= plugin.updated.to then
runner:add(Task.new(plugin, "log", {
log = {
from = plugin.updated.from,
to = plugin.updated.to,
},
}))
end
end
-- wait for post-install to finish
runner:wait(function()
vim.cmd([[do User LazyRender]])
end)
end)
if opts.wait then
runner:wait()
end
return runner
end
---@param opts? ManagerOpts
function M.install(opts)
---@param plugin LazyPlugin
M.run("install", opts, function(plugin)
return plugin.uri and not plugin.installed
end)
end
---@param opts? ManagerOpts
function M.update(opts)
---@param plugin LazyPlugin
M.run("update", opts, function(plugin)
return plugin.uri and plugin.installed
end)
end
---@param opts? ManagerOpts
function M.log(opts)
---@param plugin LazyPlugin
M.run("log", opts, function(plugin)
return plugin.uri and plugin.installed
end)
end
---@param opts? ManagerOpts
function M.docs(opts)
---@param plugin LazyPlugin
M.run("docs", opts, function(plugin)
return plugin.installed
end)
end
---@param opts? ManagerOpts
function M.clean(opts)
opts = opts or {}
Plugin.update_state(true)
opts.plugins = vim.tbl_values(Config.to_clean)
M.run("clean", opts)
end
function M.clear()
for _, plugin in pairs(Config.plugins) do
-- clear updated status
plugin.updated = nil
-- clear finished tasks
if plugin.tasks then
---@param task LazyTask
plugin.tasks = vim.tbl_filter(function(task)
return task.running
end, plugin.tasks)
end
end
vim.cmd([[do User LazyRender]])
end
return M

View file

@ -0,0 +1,90 @@
local M = {}
---@alias ProcessOpts {args: string[], cwd?: string, on_line?:fun(string), on_exit?: fun(ok:boolean, output:string)}
function M.spawn(cmd, opts)
opts = opts or {}
local env = {
"GIT_TERMINAL_PROMPT=0",
"GIT_SSH_COMMAND=ssh -oBatchMode=yes",
}
for key, value in
pairs(vim.loop.os_environ() --[[@as string[] ]])
do
table.insert(env, key .. "=" .. value)
end
local stdout = vim.loop.new_pipe()
local stderr = vim.loop.new_pipe()
local output = ""
---@type vim.loop.Process
local handle = nil
handle = vim.loop.spawn(cmd, {
stdio = { nil, stdout, stderr },
args = opts.args,
cwd = opts.cwd,
env = env,
}, function(exit_code)
handle:close()
stdout:close()
stderr:close()
local check = vim.loop.new_check()
check:start(function()
if not stdout:is_closing() or not stderr:is_closing() then
return
end
check:stop()
if opts.on_exit then
output = output:gsub("[^\r\n]+\r", "")
vim.schedule(function()
opts.on_exit(exit_code == 0, output)
end)
end
end)
end)
if not handle then
if opts.on_exit then
opts.on_exit(false, "Failed to spawn process " .. cmd .. " " .. vim.inspect(opts))
end
return
end
local function on_output(err, data)
assert(not err, err)
if data then
output = output .. data:gsub("\r\n", "\n")
local lines = vim.split(vim.trim(output:gsub("\r$", "")):gsub("[^\n\r]+\r", ""), "\n")
if opts.on_line then
vim.schedule(function()
opts.on_line(lines[#lines])
end)
end
end
end
vim.loop.read_start(stdout, on_output)
vim.loop.read_start(stderr, on_output)
return handle
end
-- FIXME: can be removed?
function M.all_done(slot0)
for slot4, slot5 in ipairs(slot0) do
if slot5 and not slot5:is_closing() then
return false
end
end
return true
end
return M

View file

@ -0,0 +1,68 @@
---@class Runner
---@field _tasks LazyTask[]
local Runner = {}
function Runner.new()
local self = setmetatable({}, {
__index = Runner,
})
self._tasks = {}
return self
end
---@param task LazyTask
function Runner:add(task)
table.insert(self._tasks, task)
task:start()
end
function Runner:is_empty()
return #self._tasks == 0
end
---@return LazyPlugin[]
function Runner:plugins()
---@param task LazyTask
return vim.tbl_map(function(task)
return task.plugin
end, self._tasks)
end
function Runner:tasks()
return self._tasks
end
---@param cb? fun()
function Runner:wait(cb)
if #self._tasks == 0 then
return cb and cb()
end
local done = false
local check = vim.loop.new_check()
check:start(function()
for _, task in ipairs(self._tasks) do
if task.running then
return
end
end
check:stop()
done = true
if cb then
vim.schedule(cb)
end
end)
if not cb then
while not done do
vim.wait(100)
end
end
end
return Runner

264
lua/lazy/manage/task.lua Normal file
View file

@ -0,0 +1,264 @@
local Process = require("lazy.manage.process")
local Loader = require("lazy.core.loader")
local Util = require("lazy.util")
---@class LazyTask
---@field plugin LazyPlugin
---@field type TaskType
---@field running boolean
---@field opts TaskOptions
local Task = {}
---@alias TaskType "update"|"install"|"run"|"clean"|"log"|"docs"
---@class TaskOptions
local options = {
log = {
since = "7 days ago",
---@type string
from = nil,
---@type string
to = nil,
},
}
---@param plugin LazyPlugin
---@param type TaskType
---@param opts? TaskOptions
function Task.new(plugin, type, opts)
local self = setmetatable({}, {
__index = Task,
})
self.opts = vim.tbl_deep_extend("force", {}, options, opts or {})
self.plugin = plugin
self.type = type
self.output = ""
self.status = ""
plugin.tasks = plugin.tasks or {}
table.insert(plugin.tasks, self)
return self
end
function Task:_done()
self.running = false
vim.cmd("do User LazyRender")
vim.api.nvim_exec_autocmds("User", {
pattern = "LazyPlugin" .. self.type:sub(1, 1):upper() .. self.type:sub(2),
data = { plugin = self.plugin.name },
})
end
function Task:clean()
local dir = self.plugin.dir:gsub("/+$", "")
local stat = vim.loop.fs_lstat(dir)
if stat.type == "directory" then
Util.walk(dir, function(path, _, type)
if type == "directory" then
vim.loop.fs_rmdir(path)
else
vim.loop.fs_unlink(path)
end
end)
vim.loop.fs_rmdir(dir)
else
vim.loop.fs_unlink(dir)
end
self.plugin.installed = false
self:_done()
end
function Task:install()
if Util.file_exists(self.plugin.uri) then
vim.loop.fs_symlink(self.plugin.uri, self.plugin.dir, {
dir = true,
})
vim.opt.runtimepath:append(self.plugin.uri)
self:_done()
else
local args = {
"clone",
self.plugin.uri,
-- "--depth=1",
-- "--filter=blob:none",
"--filter=tree:0",
"--recurse-submodules",
"--single-branch",
"--shallow-submodules",
"--progress",
}
if self.plugin.branch then
vim.list_extend(args, {
"-b",
self.plugin.branch,
})
end
table.insert(args, self.plugin.dir)
self:spawn("git", {
args = args,
on_exit = function(ok)
if ok then
self.plugin.installed = true
self.plugin.dirty = true
end
end,
})
end
end
function Task:run()
Loader.load(self.plugin, { task = "run" }, { load_start = true })
local run = self.plugin.run
if run then
if type(run) == "string" and run:sub(1, 1) == ":" then
local cmd = vim.api.nvim_parse_cmd(run:sub(2), {})
self.output = vim.api.nvim_cmd(cmd, { output = true })
elseif type(run) == "function" then
run()
else
local args = vim.split(run, "%s+")
return self:spawn(table.remove(args, 1), {
args = args,
cwd = self.plugin.dir,
})
end
end
self:_done()
end
function Task:docs()
local docs = self.plugin.dir .. "/doc/"
if Util.file_exists(docs) then
self.output = vim.api.nvim_cmd({ cmd = "helptags", args = { docs } }, { output = true })
end
self:_done()
end
---@param cmd string
---@param opts ProcessOpts
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
vim.cmd("do User LazyRender")
end
function opts.on_exit(ok, output)
self.output = output
if not ok then
self.error = output
end
if on_exit then
pcall(on_exit, ok, output)
end
self:_done()
end
Process.spawn(cmd, opts)
end
function Task:start()
self.running = true
local ok, err = pcall(function()
if self.type == "update" then
self:update()
elseif self.type == "install" then
self:install()
elseif self.type == "run" then
self:run()
elseif self.type == "clean" then
self:clean()
elseif self.type == "log" then
self:log()
elseif self.type == "docs" then
self:docs()
end
end)
if not ok then
self.error = err or "failed"
self:_done()
end
end
function Task:log()
if not Util.file_exists(self.plugin.dir .. "/.git") then
self:_done()
return
end
local args = {
"log",
"--pretty=format:%h %s (%cr)",
"--abbrev-commit",
"--decorate",
"--date=short",
"--color=never",
}
if self.opts.log.from then
table.insert(args, self.opts.log.from .. ".." .. (self.opts.log.to or "HEAD"))
else
table.insert(args, "--since=" .. self.opts.log.since)
end
self:spawn("git", {
args = args,
cwd = self.plugin.dir,
})
end
function Task:update()
if Util.file_exists(self.plugin.uri) then
if vim.loop.fs_realpath(self.plugin.uri) ~= vim.loop.fs_realpath(self.plugin.dir) then
vim.loop.fs_unlink(self.plugin.dir)
vim.loop.fs_symlink(self.plugin.uri, self.plugin.dir, {
dir = true,
})
vim.opt.runtimepath:append(self.plugin.uri)
end
self:_done()
else
local args = {
"pull",
"--tags",
"--recurse-submodules",
"--update-shallow",
"--progress",
}
local git = assert(Util.git_info(self.plugin.dir))
self:spawn("git", {
args = args,
cwd = self.plugin.dir,
on_exit = function(ok)
if ok then
local git_new = assert(Util.git_info(self.plugin.dir))
self.plugin.updated = {
from = git.hash,
to = git_new.hash,
}
self.plugin.dirty = not vim.deep_equal(git, git_new)
end
end,
})
end
end
return Task