@@ -8,22 +8,30 @@ M.loading = false
8
8
9
9
--- @class LazySpecLoader
10
10
--- @field plugins table<string , LazyPlugin>
11
+ --- @field fragments table<number , LazyPlugin>
11
12
--- @field disabled table<string , LazyPlugin>
13
+ --- @field dirty table<string , true>
14
+ --- @field ignore_installed table<string , true>
12
15
--- @field modules string[]
13
16
--- @field notifs { msg : string , level : number , file ?: string } []
14
17
--- @field importing ? string
15
18
--- @field optional ? boolean
16
19
local Spec = {}
17
20
M .Spec = Spec
21
+ M .last_fid = 0
22
+ M .fid_stack = {} --- @type number[]
18
23
19
24
--- @param spec ? LazySpec
20
25
--- @param opts ? { optional ?: boolean }
21
26
function Spec .new (spec , opts )
22
27
local self = setmetatable ({}, { __index = Spec })
23
28
self .plugins = {}
29
+ self .fragments = {}
24
30
self .disabled = {}
25
31
self .modules = {}
32
+ self .dirty = {}
26
33
self .notifs = {}
34
+ self .ignore_installed = {}
27
35
self .optional = opts and opts .optional
28
36
if spec then
29
37
self :parse (spec )
33
41
34
42
function Spec :parse (spec )
35
43
self :normalize (spec )
44
+ self :fix_disabled ()
36
45
37
46
-- calculate handlers
38
47
for _ , plugin in pairs (self .plugins ) do
@@ -42,8 +51,6 @@ function Spec:parse(spec)
42
51
end
43
52
end
44
53
end
45
-
46
- self :fix_disabled ()
47
54
end
48
55
49
56
-- PERF: optimized code to get package name without using lua patterns
56
63
57
64
--- @param plugin LazyPlugin
58
65
--- @param results ? string[]
59
- --- @param is_dep ? boolean
60
- function Spec :add (plugin , results , is_dep )
66
+ function Spec :add (plugin , results )
61
67
-- check if we already processed this spec. Can happen when a user uses the same instance of a spec in multiple specs
62
68
-- see https://github.com/folke/lazy.nvim/issues/45
63
69
if rawget (plugin , " _" ) then
@@ -124,10 +130,28 @@ function Spec:add(plugin, results, is_dep)
124
130
plugin .config = nil
125
131
end
126
132
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
129
154
130
- plugin .dependencies = plugin .dependencies and self :normalize (plugin .dependencies , {}, true ) or nil
131
155
if self .plugins [plugin .name ] then
132
156
plugin = self :merge (self .plugins [plugin .name ], plugin )
133
157
end
@@ -146,27 +170,73 @@ function Spec:warn(msg)
146
170
self :log (msg , vim .log .levels .WARN )
147
171
end
148
172
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
157
195
end
196
+ setmetatable (plugin , nil )
197
+ table.insert (fragments , 1 , plugin )
158
198
end
159
- return true
199
+ plugin = super
200
+ until not plugin
201
+
202
+ if # fragments == 0 then
203
+ self .plugins [name ] = nil
204
+ return
160
205
end
161
206
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 })
169
235
end
236
+ if opts .self then
237
+ self .fragments [fid ] = nil
238
+ end
239
+ self .dirty [fragment .name ] = true
170
240
end
171
241
end
172
242
end
@@ -179,14 +249,20 @@ function Spec:fix_cond()
179
249
end
180
250
if cond == false or (type (cond ) == " function" and not cond (plugin )) then
181
251
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
182
260
plugin .enabled = false
183
261
end
184
262
end
185
263
end
186
264
187
- --- @return string[]
188
265
function Spec :fix_optional ()
189
- local all_optional_deps = {}
190
266
if not self .optional then
191
267
--- @param plugin LazyPlugin
192
268
local function all_optional (plugin )
@@ -196,14 +272,12 @@ function Spec:fix_optional()
196
272
-- handle optional plugins
197
273
for _ , plugin in pairs (self .plugins ) do
198
274
if plugin .optional and all_optional (plugin ) then
275
+ -- remove all optional fragments
276
+ self :remove_fragments (plugin .name , { self = true })
199
277
self .plugins [plugin .name ] = nil
200
- if plugin .dependencies then
201
- vim .list_extend (all_optional_deps , plugin .dependencies )
202
- end
203
278
end
204
279
end
205
280
end
206
- return all_optional_deps
207
281
end
208
282
209
283
function Spec :fix_disabled ()
@@ -214,44 +288,24 @@ function Spec:fix_disabled()
214
288
end
215
289
end
216
290
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 ()
225
292
self :fix_cond ()
226
293
227
294
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
235
297
plugin ._ .kind = " disabled"
298
+ -- remove all child fragments
299
+ self :remove_fragments (plugin .name , { self = false })
236
300
self .plugins [plugin .name ] = nil
237
301
self .disabled [plugin .name ] = plugin
238
- if plugin .dependencies then
239
- vim .list_extend (disabled_deps , plugin .dependencies )
240
- end
241
302
end
242
303
end
243
304
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
255
309
end
256
310
257
311
--- @param msg string
@@ -272,24 +326,24 @@ end
272
326
--- @param spec LazySpec | LazySpecImport
273
327
--- @param results ? string[]
274
328
--- @param is_dep ? boolean
275
- function Spec :normalize (spec , results , is_dep )
329
+ function Spec :normalize (spec , results )
276
330
if type (spec ) == " string" then
277
- if is_dep and not spec :find (" /" , 1 , true ) then
331
+ if not spec :find (" /" , 1 , true ) then
278
332
-- spec is a plugin name
279
333
if results then
280
334
table.insert (results , spec )
281
335
end
282
336
else
283
- self :add ({ spec }, results , is_dep )
337
+ self :add ({ spec }, results )
284
338
end
285
339
elseif # spec > 1 or Util .is_list (spec ) then
286
340
--- @cast spec LazySpec[]
287
341
for _ , s in ipairs (spec ) do
288
- self :normalize (s , results , is_dep )
342
+ self :normalize (s , results )
289
343
end
290
344
elseif spec [1 ] or spec .dir or spec .url then
291
345
--- @cast spec LazyPlugin
292
- local plugin = self :add (spec , results , is_dep )
346
+ local plugin = self :add (spec , results )
293
347
--- @diagnostic disable-next-line : cast-type-mismatch
294
348
--- @cast plugin LazySpecImport
295
349
if plugin and plugin .import then
@@ -425,10 +479,8 @@ function M.update_state()
425
479
end
426
480
end
427
481
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
432
484
end
433
485
434
486
Config .to_clean = {}
0 commit comments