Skip to content
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

feat(plugin): dont include plugin spec fragments for disabled or optional plugins #1058

Merged
merged 7 commits into from
Sep 29, 2023
Merged
186 changes: 119 additions & 67 deletions lua/lazy/core/plugin.lua
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,30 @@ M.loading = false

---@class LazySpecLoader
---@field plugins table<string, LazyPlugin>
---@field fragments table<number, LazyPlugin>
---@field disabled table<string, LazyPlugin>
---@field dirty table<string, true>
---@field ignore_installed table<string, true>
---@field modules string[]
---@field notifs {msg:string, level:number, file?:string}[]
---@field importing? string
---@field optional? boolean
local Spec = {}
M.Spec = Spec
M.last_fid = 0
M.fid_stack = {} ---@type number[]

---@param spec? LazySpec
---@param opts? {optional?:boolean}
function Spec.new(spec, opts)
local self = setmetatable({}, { __index = Spec })
self.plugins = {}
self.fragments = {}
self.disabled = {}
self.modules = {}
self.dirty = {}
self.notifs = {}
self.ignore_installed = {}
self.optional = opts and opts.optional
if spec then
self:parse(spec)
Expand All @@ -33,6 +41,7 @@ end

function Spec:parse(spec)
self:normalize(spec)
self:fix_disabled()

-- calculate handlers
for _, plugin in pairs(self.plugins) do
Expand All @@ -42,8 +51,6 @@ function Spec:parse(spec)
end
end
end

self:fix_disabled()
end

-- PERF: optimized code to get package name without using lua patterns
Expand All @@ -56,8 +63,7 @@ end

---@param plugin LazyPlugin
---@param results? string[]
---@param is_dep? boolean
function Spec:add(plugin, results, is_dep)
function Spec:add(plugin, results)
-- check if we already processed this spec. Can happen when a user uses the same instance of a spec in multiple specs
-- see https://github.com/folke/lazy.nvim/issues/45
if rawget(plugin, "_") then
Expand Down Expand Up @@ -124,10 +130,28 @@ function Spec:add(plugin, results, is_dep)
plugin.config = nil
end

plugin._ = {}
plugin._.dep = is_dep
local fpid = M.fid_stack[#M.fid_stack]

M.last_fid = M.last_fid + 1
plugin._ = {
fid = M.last_fid,
fpid = fpid,
dep = fpid ~= nil,
}
self.fragments[plugin._.fid] = plugin

if fpid then
local parent = self.fragments[fpid]
parent._.fdeps = parent._.fdeps or {}
table.insert(parent._.fdeps, plugin._.fid)
end

if plugin.dependencies then
table.insert(M.fid_stack, plugin._.fid)
plugin.dependencies = self:normalize(plugin.dependencies, {})
table.remove(M.fid_stack)
end

plugin.dependencies = plugin.dependencies and self:normalize(plugin.dependencies, {}, true) or nil
if self.plugins[plugin.name] then
plugin = self:merge(self.plugins[plugin.name], plugin)
end
Expand All @@ -146,27 +170,73 @@ function Spec:warn(msg)
self:log(msg, vim.log.levels.WARN)
end

---@param gathered_deps string[]
---@param dep_of table<string,string[]>
---@param on_disable fun(string):nil
function Spec:fix_dependencies(gathered_deps, dep_of, on_disable)
local function should_disable(dep_name)
for _, parent in ipairs(dep_of[dep_name] or {}) do
if self.plugins[parent] then
return false
--- Rebuilds a plugin spec excluding any removed fragments
---@param name string
function Spec:rebuild(name)
local plugin = self.plugins[name]
if not plugin then
return
end

local fragments = {} ---@type LazyPlugin[]

repeat
local super = plugin._.super
if self.fragments[plugin._.fid] then
plugin._.dep = plugin._.fpid ~= nil
plugin._.super = nil
if plugin._.fdeps then
plugin.dependencies = {}
for _, cid in ipairs(plugin._.fdeps) do
if self.fragments[cid] then
table.insert(plugin.dependencies, self.fragments[cid].name)
end
end
end
setmetatable(plugin, nil)
table.insert(fragments, 1, plugin)
end
return true
plugin = super
until not plugin

if #fragments == 0 then
self.plugins[name] = nil
return
end

for _, dep_name in ipairs(gathered_deps) do
-- only check if the plugin is still enabled and it is a dep
if self.plugins[dep_name] and self.plugins[dep_name]._.dep then
-- check if the dep is still used by another plugin
if should_disable(dep_name) then
-- disable the dep when no longer needed
on_disable(dep_name)
plugin = fragments[1]
for i = 2, #fragments do
plugin = self:merge(plugin, fragments[i])
end
self.plugins[name] = plugin
end

--- Recursively removes all fragments from a plugin spec or a given fragment
---@param id string|number Plugin name or fragment id
---@param opts {self: boolean}
function Spec:remove_fragments(id, opts)
local fids = {} ---@type number[]

if type(id) == "number" then
fids[1] = id
else
local plugin = self.plugins[id]
repeat
fids[#fids + 1] = plugin._.fid
plugin = plugin._.super
until not plugin
end

for _, fid in ipairs(fids) do
local fragment = self.fragments[fid]
if fragment then
for _, cid in ipairs(fragment._.fdeps or {}) do
self:remove_fragments(cid, { self = true })
end
if opts.self then
self.fragments[fid] = nil
end
self.dirty[fragment.name] = true
end
end
end
Expand All @@ -179,14 +249,20 @@ function Spec:fix_cond()
end
if cond == false or (type(cond) == "function" and not cond(plugin)) then
plugin._.cond = false
local stack = { plugin }
while #stack > 0 do
local p = table.remove(stack)
for _, dep in ipairs(p.dependencies or {}) do
table.insert(stack, self.plugins[dep])
end
self.ignore_installed[p.name] = true
end
plugin.enabled = false
end
end
end

---@return string[]
function Spec:fix_optional()
local all_optional_deps = {}
if not self.optional then
---@param plugin LazyPlugin
local function all_optional(plugin)
Expand All @@ -196,14 +272,12 @@ function Spec:fix_optional()
-- handle optional plugins
for _, plugin in pairs(self.plugins) do
if plugin.optional and all_optional(plugin) then
-- remove all optional fragments
self:remove_fragments(plugin.name, { self = true })
self.plugins[plugin.name] = nil
if plugin.dependencies then
vim.list_extend(all_optional_deps, plugin.dependencies)
end
end
end
end
return all_optional_deps
end

function Spec:fix_disabled()
Expand All @@ -214,44 +288,24 @@ function Spec:fix_disabled()
end
end

---@type table<string,string[]> plugin to parent plugin
local dep_of = {}

---@type string[] dependencies of disabled plugins
local disabled_deps = {}

---@type string[] dependencies of plugins that are completely optional
local all_optional_deps = self:fix_optional()
self:fix_optional()
self:fix_cond()

for _, plugin in pairs(self.plugins) do
local enabled = not (plugin.enabled == false or (type(plugin.enabled) == "function" and not plugin.enabled()))
if enabled then
for _, dep in ipairs(plugin.dependencies or {}) do
dep_of[dep] = dep_of[dep] or {}
table.insert(dep_of[dep], plugin.name)
end
else
local disabled = plugin.enabled == false or (type(plugin.enabled) == "function" and not plugin.enabled())
if disabled then
plugin._.kind = "disabled"
-- remove all child fragments
self:remove_fragments(plugin.name, { self = false })
self.plugins[plugin.name] = nil
self.disabled[plugin.name] = plugin
if plugin.dependencies then
vim.list_extend(disabled_deps, plugin.dependencies)
end
end
end

-- fix deps of plugins that are completely optional
self:fix_dependencies(all_optional_deps, dep_of, function(dep_name)
self.plugins[dep_name] = nil
end)
-- fix deps of disabled plugins
self:fix_dependencies(disabled_deps, dep_of, function(dep_name)
local plugin = self.plugins[dep_name]
plugin._.kind = "disabled"
self.plugins[plugin.name] = nil
self.disabled[plugin.name] = plugin
end)
-- rebuild any plugin specs that were modified
for name, _ in pairs(self.dirty) do
self:rebuild(name)
end
end

---@param msg string
Expand All @@ -272,24 +326,24 @@ end
---@param spec LazySpec|LazySpecImport
---@param results? string[]
---@param is_dep? boolean
function Spec:normalize(spec, results, is_dep)
function Spec:normalize(spec, results)
if type(spec) == "string" then
if is_dep and not spec:find("/", 1, true) then
if not spec:find("/", 1, true) then
-- spec is a plugin name
if results then
table.insert(results, spec)
end
else
self:add({ spec }, results, is_dep)
self:add({ spec }, results)
end
elseif #spec > 1 or Util.is_list(spec) then
---@cast spec LazySpec[]
for _, s in ipairs(spec) do
self:normalize(s, results, is_dep)
self:normalize(s, results)
end
elseif spec[1] or spec.dir or spec.url then
---@cast spec LazyPlugin
local plugin = self:add(spec, results, is_dep)
local plugin = self:add(spec, results)
---@diagnostic disable-next-line: cast-type-mismatch
---@cast plugin LazySpecImport
if plugin and plugin.import then
Expand Down Expand Up @@ -425,10 +479,8 @@ function M.update_state()
end
end

for _, plugin in pairs(Config.spec.disabled) do
if plugin._.cond == false then
installed[plugin.name] = nil
end
for name in pairs(Config.spec.ignore_installed) do
installed[name] = nil
end

Config.to_clean = {}
Expand Down
7 changes: 5 additions & 2 deletions lua/lazy/types.lua
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@
---@alias LazyPluginKind "normal"|"clean"|"disabled"

---@class LazyPluginState
---@field fid number id of the plugin spec fragment
---@field fpid? number parent id of the plugin spec fragment
---@field fdeps? number[] children ids of the fragment
---@field loaded? {[string]:string}|{time:number}
---@field installed boolean
---@field installed? boolean
---@field tasks? LazyTask[]
---@field dirty? boolean
---@field updated? {from:string, to:string}
---@field is_local boolean
---@field is_local? boolean
---@field updates? {from:GitInfo, to:GitInfo}
---@field cloned? boolean
---@field kind? LazyPluginKind
Expand Down
14 changes: 8 additions & 6 deletions lua/lazy/view/render.lua
Original file line number Diff line number Diff line change
Expand Up @@ -411,13 +411,15 @@ function M:plugin(plugin)
else
self:append(" ")
local reason = {}
for handler in pairs(Handler.types) do
if plugin[handler] then
local trigger = {}
for _, value in ipairs(plugin[handler]) do
table.insert(trigger, type(value) == "table" and value[1] or value)
if plugin._.kind ~= "disabled" then
for handler in pairs(Handler.types) do
if plugin[handler] then
local trigger = {}
for _, value in ipairs(plugin[handler]) do
table.insert(trigger, type(value) == "table" and value[1] or value)
end
reason[handler] = table.concat(trigger, " ")
end
reason[handler] = table.concat(trigger, " ")
end
end
for _, other in pairs(Config.plugins) do
Expand Down
Loading