Skip to content

Commit f3c7169

Browse files
authored
feat(plugin): dont include plugin spec fragments for disabled or optional plugins (#1058)
* feat(plugin): dont include plugin spec fragments for disabled or optional plugins * test: fixed tests * fix(plugin): calculate handlers after disabling plugins * fix(plugin): clear Plugin._.super when rebuilding * fix(ui): dont process handlers for disabled plugins * test: added tests for disabling fragments * fix(plugin): ignore any installed deps of a disabled conditional plugin. Fixes #1053
1 parent 6b55e46 commit f3c7169

File tree

4 files changed

+179
-78
lines changed

4 files changed

+179
-78
lines changed

lua/lazy/core/plugin.lua

+119-67
Original file line numberDiff line numberDiff line change
@@ -8,22 +8,30 @@ M.loading = false
88

99
---@class LazySpecLoader
1010
---@field plugins table<string, LazyPlugin>
11+
---@field fragments table<number, LazyPlugin>
1112
---@field disabled table<string, LazyPlugin>
13+
---@field dirty table<string, true>
14+
---@field ignore_installed table<string, true>
1215
---@field modules string[]
1316
---@field notifs {msg:string, level:number, file?:string}[]
1417
---@field importing? string
1518
---@field optional? boolean
1619
local Spec = {}
1720
M.Spec = Spec
21+
M.last_fid = 0
22+
M.fid_stack = {} ---@type number[]
1823

1924
---@param spec? LazySpec
2025
---@param opts? {optional?:boolean}
2126
function Spec.new(spec, opts)
2227
local self = setmetatable({}, { __index = Spec })
2328
self.plugins = {}
29+
self.fragments = {}
2430
self.disabled = {}
2531
self.modules = {}
32+
self.dirty = {}
2633
self.notifs = {}
34+
self.ignore_installed = {}
2735
self.optional = opts and opts.optional
2836
if spec then
2937
self:parse(spec)
@@ -33,6 +41,7 @@ end
3341

3442
function Spec:parse(spec)
3543
self:normalize(spec)
44+
self:fix_disabled()
3645

3746
-- calculate handlers
3847
for _, plugin in pairs(self.plugins) do
@@ -42,8 +51,6 @@ function Spec:parse(spec)
4251
end
4352
end
4453
end
45-
46-
self:fix_disabled()
4754
end
4855

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

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

127-
plugin._ = {}
128-
plugin._.dep = is_dep
133+
local fpid = M.fid_stack[#M.fid_stack]
134+
135+
M.last_fid = M.last_fid + 1
136+
plugin._ = {
137+
fid = M.last_fid,
138+
fpid = fpid,
139+
dep = fpid ~= nil,
140+
}
141+
self.fragments[plugin._.fid] = plugin
142+
143+
if fpid then
144+
local parent = self.fragments[fpid]
145+
parent._.fdeps = parent._.fdeps or {}
146+
table.insert(parent._.fdeps, plugin._.fid)
147+
end
148+
149+
if plugin.dependencies then
150+
table.insert(M.fid_stack, plugin._.fid)
151+
plugin.dependencies = self:normalize(plugin.dependencies, {})
152+
table.remove(M.fid_stack)
153+
end
129154

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

149-
---@param gathered_deps string[]
150-
---@param dep_of table<string,string[]>
151-
---@param on_disable fun(string):nil
152-
function Spec:fix_dependencies(gathered_deps, dep_of, on_disable)
153-
local function should_disable(dep_name)
154-
for _, parent in ipairs(dep_of[dep_name] or {}) do
155-
if self.plugins[parent] then
156-
return false
173+
--- Rebuilds a plugin spec excluding any removed fragments
174+
---@param name string
175+
function Spec:rebuild(name)
176+
local plugin = self.plugins[name]
177+
if not plugin then
178+
return
179+
end
180+
181+
local fragments = {} ---@type LazyPlugin[]
182+
183+
repeat
184+
local super = plugin._.super
185+
if self.fragments[plugin._.fid] then
186+
plugin._.dep = plugin._.fpid ~= nil
187+
plugin._.super = nil
188+
if plugin._.fdeps then
189+
plugin.dependencies = {}
190+
for _, cid in ipairs(plugin._.fdeps) do
191+
if self.fragments[cid] then
192+
table.insert(plugin.dependencies, self.fragments[cid].name)
193+
end
194+
end
157195
end
196+
setmetatable(plugin, nil)
197+
table.insert(fragments, 1, plugin)
158198
end
159-
return true
199+
plugin = super
200+
until not plugin
201+
202+
if #fragments == 0 then
203+
self.plugins[name] = nil
204+
return
160205
end
161206

162-
for _, dep_name in ipairs(gathered_deps) do
163-
-- only check if the plugin is still enabled and it is a dep
164-
if self.plugins[dep_name] and self.plugins[dep_name]._.dep then
165-
-- check if the dep is still used by another plugin
166-
if should_disable(dep_name) then
167-
-- disable the dep when no longer needed
168-
on_disable(dep_name)
207+
plugin = fragments[1]
208+
for i = 2, #fragments do
209+
plugin = self:merge(plugin, fragments[i])
210+
end
211+
self.plugins[name] = plugin
212+
end
213+
214+
--- Recursively removes all fragments from a plugin spec or a given fragment
215+
---@param id string|number Plugin name or fragment id
216+
---@param opts {self: boolean}
217+
function Spec:remove_fragments(id, opts)
218+
local fids = {} ---@type number[]
219+
220+
if type(id) == "number" then
221+
fids[1] = id
222+
else
223+
local plugin = self.plugins[id]
224+
repeat
225+
fids[#fids + 1] = plugin._.fid
226+
plugin = plugin._.super
227+
until not plugin
228+
end
229+
230+
for _, fid in ipairs(fids) do
231+
local fragment = self.fragments[fid]
232+
if fragment then
233+
for _, cid in ipairs(fragment._.fdeps or {}) do
234+
self:remove_fragments(cid, { self = true })
169235
end
236+
if opts.self then
237+
self.fragments[fid] = nil
238+
end
239+
self.dirty[fragment.name] = true
170240
end
171241
end
172242
end
@@ -179,14 +249,20 @@ function Spec:fix_cond()
179249
end
180250
if cond == false or (type(cond) == "function" and not cond(plugin)) then
181251
plugin._.cond = false
252+
local stack = { plugin }
253+
while #stack > 0 do
254+
local p = table.remove(stack)
255+
for _, dep in ipairs(p.dependencies or {}) do
256+
table.insert(stack, self.plugins[dep])
257+
end
258+
self.ignore_installed[p.name] = true
259+
end
182260
plugin.enabled = false
183261
end
184262
end
185263
end
186264

187-
---@return string[]
188265
function Spec:fix_optional()
189-
local all_optional_deps = {}
190266
if not self.optional then
191267
---@param plugin LazyPlugin
192268
local function all_optional(plugin)
@@ -196,14 +272,12 @@ function Spec:fix_optional()
196272
-- handle optional plugins
197273
for _, plugin in pairs(self.plugins) do
198274
if plugin.optional and all_optional(plugin) then
275+
-- remove all optional fragments
276+
self:remove_fragments(plugin.name, { self = true })
199277
self.plugins[plugin.name] = nil
200-
if plugin.dependencies then
201-
vim.list_extend(all_optional_deps, plugin.dependencies)
202-
end
203278
end
204279
end
205280
end
206-
return all_optional_deps
207281
end
208282

209283
function Spec:fix_disabled()
@@ -214,44 +288,24 @@ function Spec:fix_disabled()
214288
end
215289
end
216290

217-
---@type table<string,string[]> plugin to parent plugin
218-
local dep_of = {}
219-
220-
---@type string[] dependencies of disabled plugins
221-
local disabled_deps = {}
222-
223-
---@type string[] dependencies of plugins that are completely optional
224-
local all_optional_deps = self:fix_optional()
291+
self:fix_optional()
225292
self:fix_cond()
226293

227294
for _, plugin in pairs(self.plugins) do
228-
local enabled = not (plugin.enabled == false or (type(plugin.enabled) == "function" and not plugin.enabled()))
229-
if enabled then
230-
for _, dep in ipairs(plugin.dependencies or {}) do
231-
dep_of[dep] = dep_of[dep] or {}
232-
table.insert(dep_of[dep], plugin.name)
233-
end
234-
else
295+
local disabled = plugin.enabled == false or (type(plugin.enabled) == "function" and not plugin.enabled())
296+
if disabled then
235297
plugin._.kind = "disabled"
298+
-- remove all child fragments
299+
self:remove_fragments(plugin.name, { self = false })
236300
self.plugins[plugin.name] = nil
237301
self.disabled[plugin.name] = plugin
238-
if plugin.dependencies then
239-
vim.list_extend(disabled_deps, plugin.dependencies)
240-
end
241302
end
242303
end
243304

244-
-- fix deps of plugins that are completely optional
245-
self:fix_dependencies(all_optional_deps, dep_of, function(dep_name)
246-
self.plugins[dep_name] = nil
247-
end)
248-
-- fix deps of disabled plugins
249-
self:fix_dependencies(disabled_deps, dep_of, function(dep_name)
250-
local plugin = self.plugins[dep_name]
251-
plugin._.kind = "disabled"
252-
self.plugins[plugin.name] = nil
253-
self.disabled[plugin.name] = plugin
254-
end)
305+
-- rebuild any plugin specs that were modified
306+
for name, _ in pairs(self.dirty) do
307+
self:rebuild(name)
308+
end
255309
end
256310

257311
---@param msg string
@@ -272,24 +326,24 @@ end
272326
---@param spec LazySpec|LazySpecImport
273327
---@param results? string[]
274328
---@param is_dep? boolean
275-
function Spec:normalize(spec, results, is_dep)
329+
function Spec:normalize(spec, results)
276330
if type(spec) == "string" then
277-
if is_dep and not spec:find("/", 1, true) then
331+
if not spec:find("/", 1, true) then
278332
-- spec is a plugin name
279333
if results then
280334
table.insert(results, spec)
281335
end
282336
else
283-
self:add({ spec }, results, is_dep)
337+
self:add({ spec }, results)
284338
end
285339
elseif #spec > 1 or Util.is_list(spec) then
286340
---@cast spec LazySpec[]
287341
for _, s in ipairs(spec) do
288-
self:normalize(s, results, is_dep)
342+
self:normalize(s, results)
289343
end
290344
elseif spec[1] or spec.dir or spec.url then
291345
---@cast spec LazyPlugin
292-
local plugin = self:add(spec, results, is_dep)
346+
local plugin = self:add(spec, results)
293347
---@diagnostic disable-next-line: cast-type-mismatch
294348
---@cast plugin LazySpecImport
295349
if plugin and plugin.import then
@@ -425,10 +479,8 @@ function M.update_state()
425479
end
426480
end
427481

428-
for _, plugin in pairs(Config.spec.disabled) do
429-
if plugin._.cond == false then
430-
installed[plugin.name] = nil
431-
end
482+
for name in pairs(Config.spec.ignore_installed) do
483+
installed[name] = nil
432484
end
433485

434486
Config.to_clean = {}

lua/lazy/types.lua

+5-2
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,15 @@
22
---@alias LazyPluginKind "normal"|"clean"|"disabled"
33

44
---@class LazyPluginState
5+
---@field fid number id of the plugin spec fragment
6+
---@field fpid? number parent id of the plugin spec fragment
7+
---@field fdeps? number[] children ids of the fragment
58
---@field loaded? {[string]:string}|{time:number}
6-
---@field installed boolean
9+
---@field installed? boolean
710
---@field tasks? LazyTask[]
811
---@field dirty? boolean
912
---@field updated? {from:string, to:string}
10-
---@field is_local boolean
13+
---@field is_local? boolean
1114
---@field updates? {from:GitInfo, to:GitInfo}
1215
---@field cloned? boolean
1316
---@field kind? LazyPluginKind

lua/lazy/view/render.lua

+8-6
Original file line numberDiff line numberDiff line change
@@ -411,13 +411,15 @@ function M:plugin(plugin)
411411
else
412412
self:append(" ")
413413
local reason = {}
414-
for handler in pairs(Handler.types) do
415-
if plugin[handler] then
416-
local trigger = {}
417-
for _, value in ipairs(plugin[handler]) do
418-
table.insert(trigger, type(value) == "table" and value[1] or value)
414+
if plugin._.kind ~= "disabled" then
415+
for handler in pairs(Handler.types) do
416+
if plugin[handler] then
417+
local trigger = {}
418+
for _, value in ipairs(plugin[handler]) do
419+
table.insert(trigger, type(value) == "table" and value[1] or value)
420+
end
421+
reason[handler] = table.concat(trigger, " ")
419422
end
420-
reason[handler] = table.concat(trigger, " ")
421423
end
422424
end
423425
for _, other in pairs(Config.plugins) do

0 commit comments

Comments
 (0)