discard_deps: Repair active plugins

Plugins that are unused currently can still contribute to
active plugins by the specs added to their "dependencies" field.

This commit adds functionality to the "parse" method,
repairing only the active plugins that could be affected by the
aforementioned specs.

This can be achieved by maintaining an extra administration table
containing a copy of each added plugin instance.
This commit is contained in:
abeldekat 2023-07-31 20:24:59 +02:00
commit f95c7a8be1

View file

@ -7,7 +7,9 @@ local M = {}
M.loading = false M.loading = false
---@class LazySpecLoader ---@class LazySpecLoader
---@field repair_info table<string,LazyPlugin[]>
---@field plugins table<string, LazyPlugin> ---@field plugins table<string, LazyPlugin>
---@field optional_only table<string, LazyPlugin>
---@field disabled table<string, LazyPlugin> ---@field disabled table<string, LazyPlugin>
---@field modules string[] ---@field modules string[]
---@field notifs {msg:string, level:number, file?:string}[] ---@field notifs {msg:string, level:number, file?:string}[]
@ -20,7 +22,9 @@ M.Spec = Spec
---@param opts? {optional?:boolean} ---@param opts? {optional?:boolean}
function Spec.new(spec, opts) function Spec.new(spec, opts)
local self = setmetatable({}, { __index = Spec }) local self = setmetatable({}, { __index = Spec })
self.repair_info = {}
self.plugins = {} self.plugins = {}
self.optional_only = {}
self.disabled = {} self.disabled = {}
self.modules = {} self.modules = {}
self.notifs = {} self.notifs = {}
@ -32,18 +36,75 @@ function Spec.new(spec, opts)
end end
function Spec:parse(spec) function Spec:parse(spec)
-- spec -> self.plugins
self:normalize(spec) self:normalize(spec)
-- self.plugins -> self.optional_only, self.disabled
self:fix_disabled()
-- calculate handlers if self:has_unused_plugins() then
for _, plugin in pairs(self.plugins) do self:fix_active()
end
self:calculate_handlers(self.plugins)
self:calculate_handlers(self.disabled)
end
function Spec:has_unused_plugins()
return not (vim.tbl_isempty(self.optional_only) and vim.tbl_isempty(self.disabled))
end
function Spec:fix_active()
---@type table<string,LazyPlugin>
local unused_plugins = vim.tbl_extend("keep", self.optional_only, self.disabled)
---@type string[]
local candidates = {}
for _, plugin in pairs(unused_plugins) do
if plugin.dependencies then
vim.list_extend(candidates, plugin.dependencies)
end
end
-- merge candidate again without instances supplied by unused plugins
for _, candidate in pairs(candidates) do
local fix = false
local instances = vim.tbl_filter(function(instance)
if instance._.parent_name and unused_plugins[instance._.parent_name] then
fix = true
return false
end
return instance
end, self.repair_info[candidate])
if fix then
self.plugins[candidate] = self:fix_merge(instances)
end
end
end
---@param instances_of_plugin LazyPlugin[]
---@return LazyPlugin
function Spec:fix_merge(instances_of_plugin)
local last
for index, inst in ipairs(instances_of_plugin) do
if index == 1 then
last = inst
else
self:merge(last, inst)
last = inst
end
end
return last
end
---@param plugins table<string, LazyPlugin>
function Spec:calculate_handlers(plugins)
for _, plugin in pairs(plugins) do
for _, handler in pairs(Handler.types) do for _, handler in pairs(Handler.types) do
if plugin[handler] then if plugin[handler] then
plugin[handler] = M.values(plugin, handler, true) plugin[handler] = M.values(plugin, handler, true)
end end
end end
end end
self:fix_disabled()
end end
-- PERF: optimized code to get package name without using lua patterns -- PERF: optimized code to get package name without using lua patterns
@ -56,8 +117,8 @@ end
---@param plugin LazyPlugin ---@param plugin LazyPlugin
---@param results? string[] ---@param results? string[]
---@param is_dep? boolean ---@param parent_name? string
function Spec:add(plugin, results, is_dep) function Spec:add(plugin, results, parent_name)
-- check if we already processed this spec. Can happen when a user uses the same instance of a spec in multiple specs -- 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 -- see https://github.com/folke/lazy.nvim/issues/45
if rawget(plugin, "_") then if rawget(plugin, "_") then
@ -125,9 +186,10 @@ function Spec:add(plugin, results, is_dep)
end end
plugin._ = {} plugin._ = {}
plugin._.dep = is_dep plugin._.dep = (parent_name ~= nil) or nil
plugin.dependencies = plugin.dependencies and self:normalize(plugin.dependencies, {}, true) or nil plugin.dependencies = plugin.dependencies and self:normalize(plugin.dependencies, {}, plugin.name) or nil
self:add_to_repair_info(plugin, parent_name)
if self.plugins[plugin.name] then if self.plugins[plugin.name] then
plugin = self:merge(self.plugins[plugin.name], plugin) plugin = self:merge(self.plugins[plugin.name], plugin)
end end
@ -138,6 +200,15 @@ function Spec:add(plugin, results, is_dep)
return plugin return plugin
end end
---@param plugin LazyPlugin
---@param parent_name? string
function Spec:add_to_repair_info(plugin, parent_name)
local copy = vim.deepcopy(plugin) -- copy the instance of the plugin
copy._.parent_name = parent_name
self.repair_info[copy.name] = self.repair_info[copy.name] or {}
table.insert(self.repair_info[copy.name], copy)
end
function Spec:error(msg) function Spec:error(msg)
self:log(msg, vim.log.levels.ERROR) self:log(msg, vim.log.levels.ERROR)
end end
@ -197,6 +268,7 @@ function Spec:fix_optional()
for _, plugin in pairs(self.plugins) do for _, plugin in pairs(self.plugins) do
if plugin.optional and all_optional(plugin) then if plugin.optional and all_optional(plugin) then
self.plugins[plugin.name] = nil self.plugins[plugin.name] = nil
self.optional_only[plugin.name] = plugin
if plugin.dependencies then if plugin.dependencies then
vim.list_extend(all_optional_deps, plugin.dependencies) vim.list_extend(all_optional_deps, plugin.dependencies)
end end
@ -243,7 +315,9 @@ function Spec:fix_disabled()
-- fix deps of plugins that are completely optional -- fix deps of plugins that are completely optional
self:fix_dependencies(all_optional_deps, dep_of, function(dep_name) self:fix_dependencies(all_optional_deps, dep_of, function(dep_name)
local plugin = self.plugins[dep_name]
self.plugins[dep_name] = nil self.plugins[dep_name] = nil
self.optional_only[dep_name] = plugin
end) end)
-- fix deps of disabled plugins -- fix deps of disabled plugins
self:fix_dependencies(disabled_deps, dep_of, function(dep_name) self:fix_dependencies(disabled_deps, dep_of, function(dep_name)
@ -271,8 +345,9 @@ end
---@param spec LazySpec|LazySpecImport ---@param spec LazySpec|LazySpecImport
---@param results? string[] ---@param results? string[]
---@param is_dep? boolean ---@param parent_name? string
function Spec:normalize(spec, results, is_dep) function Spec:normalize(spec, results, parent_name)
local is_dep = parent_name ~= nil
if type(spec) == "string" then if type(spec) == "string" then
if is_dep and not spec:find("/", 1, true) then if is_dep and not spec:find("/", 1, true) then
-- spec is a plugin name -- spec is a plugin name
@ -280,16 +355,16 @@ function Spec:normalize(spec, results, is_dep)
table.insert(results, spec) table.insert(results, spec)
end end
else else
self:add({ spec }, results, is_dep) self:add({ spec }, results, parent_name)
end end
elseif #spec > 1 or Util.is_list(spec) then elseif #spec > 1 or Util.is_list(spec) then
---@cast spec LazySpec[] ---@cast spec LazySpec[]
for _, s in ipairs(spec) do for _, s in ipairs(spec) do
self:normalize(s, results, is_dep) self:normalize(s, results, parent_name)
end end
elseif spec[1] or spec.dir or spec.url then elseif spec[1] or spec.dir or spec.url then
---@cast spec LazyPlugin ---@cast spec LazyPlugin
local plugin = self:add(spec, results, is_dep) local plugin = self:add(spec, results, parent_name)
---@diagnostic disable-next-line: cast-type-mismatch ---@diagnostic disable-next-line: cast-type-mismatch
---@cast plugin LazySpecImport ---@cast plugin LazySpecImport
if plugin and plugin.import then if plugin and plugin.import then