-
Notifications
You must be signed in to change notification settings - Fork 348
/
meta.lua
343 lines (310 loc) · 9.19 KB
/
meta.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
local Config = require("lazy.core.config")
local Pkg = require("lazy.pkg")
local Util = require("lazy.core.util")
--- This class is used to manage the plugins.
--- A plugin is a collection of fragments that are related to each other.
---@class LazyMeta
---@field plugins table<string, LazyPlugin>
---@field str_to_meta table<string, LazyPlugin>
---@field frag_to_meta table<number, LazyPlugin>
---@field dirty table<string, boolean>
---@field spec LazySpecLoader
---@field fragments LazyFragments
---@field pkgs table<string, number>
local M = {}
---@param spec LazySpecLoader
---@return LazyMeta
function M.new(spec)
local self = setmetatable({}, { __index = M })
self.spec = spec
self.fragments = require("lazy.core.fragments").new(spec)
self.plugins = {}
self.frag_to_meta = {}
self.str_to_meta = {}
self.dirty = {}
self.pkgs = {}
return self
end
-- import package specs
function M:load_pkgs()
if not Config.options.pkg.enabled then
return
end
for _, pkg in ipairs(Pkg.get()) do
local last_id = self.fragments._fid
local meta, fragment = self:add(pkg.spec)
if meta and fragment then
meta._.pkg = pkg
-- tag all top-level package fragments that were added as optional
for _, fid in ipairs(meta._.frags) do
if fid > last_id then
local frag = self.fragments:get(fid)
frag.spec.optional = true
end
end
-- keep track of the top-level package fragment
self.pkgs[pkg.dir] = fragment.id
end
end
end
--- Remove a plugin and all its fragments.
---@param name string
function M:del(name)
local meta = self.plugins[name]
if not meta then
return
end
for _, fid in ipairs(meta._.frags or {}) do
self.fragments:del(fid)
end
self.plugins[name] = nil
end
--- Add a fragment to a plugin.
--- This will create a new plugin if it does not exist.
--- It also keeps track of renames.
---@param plugin LazyPluginSpec
function M:add(plugin)
local fragment = self.fragments:add(plugin)
if not fragment then
return
end
local meta = self.plugins[fragment.name]
or fragment.url and self.str_to_meta[fragment.url]
or fragment.dir and self.str_to_meta[fragment.dir]
if not meta then
meta = { name = fragment.name, _ = { frags = {} } }
local url, dir = fragment.url, fragment.dir
-- add to index
if url then
self.str_to_meta[url] = meta
end
if dir then
self.str_to_meta[dir] = meta
end
end
table.insert(meta._.frags, fragment.id)
if meta._ and meta._.rtp_loaded and meta.dir then
local old_dir = meta.dir
self:_rebuild(meta.name)
local new_dir = meta.dir
if old_dir ~= new_dir then
local msg = "Plugin `" .. meta.name .. "` changed `dir`:\n- from: `" .. old_dir .. "`\n- to: `" .. new_dir .. "`"
msg = msg .. "\n\nThis plugin was already partially loaded, so things may break.\nPlease fix your config."
self.spec:error(msg)
end
end
if plugin.name then
-- handle renames
if meta.name ~= plugin.name then
self.plugins[meta.name] = nil
meta.name = plugin.name
end
end
self.plugins[meta.name] = meta
self.frag_to_meta[fragment.id] = meta
self.dirty[meta.name] = true
return meta, fragment
end
--- Rebuild all plugins based on dirty fragments,
--- or dirty plugins. Will remove plugins that no longer have fragments.
function M:rebuild()
local frag_count = vim.tbl_count(self.fragments.dirty)
local plugin_count = vim.tbl_count(self.dirty)
if frag_count == 0 and plugin_count == 0 then
return
end
if Config.options.debug then
Util.track("rebuild plugins frags=" .. frag_count .. " plugins=" .. plugin_count)
end
for fid in pairs(self.fragments.dirty) do
local meta = self.frag_to_meta[fid]
if meta then
if self.fragments:get(fid) then
-- fragment still exists, so mark plugin as dirty
self.dirty[meta.name] = true
else
-- fragment was deleted, so remove it from plugin
self.frag_to_meta[fid] = nil
---@param f number
meta._.frags = Util.filter(function(f)
return f ~= fid
end, meta._.frags)
-- if no fragments left, delete plugin
if #meta._.frags == 0 then
self:del(meta.name)
else
self.dirty[meta.name] = true
end
end
end
end
self.fragments.dirty = {}
for n, _ in pairs(self.dirty) do
self:_rebuild(n)
end
if Config.options.debug then
Util.track()
end
end
--- Rebuild a single plugin.
--- This will resolve the plugin based on its fragments using metatables.
--- This also resolves dependencies, dep, optional, dir, dev, and url.
---@param name string
function M:_rebuild(name)
if not self.dirty[name] then
return
end
self.dirty[name] = nil
local plugin = self.plugins[name]
if not plugin or #plugin._.frags == 0 then
self.plugins[name] = nil
return
end
setmetatable(plugin, nil)
plugin.dependencies = {}
local super = nil
plugin.url = nil
plugin._.dep = true
plugin.optional = true
assert(#plugin._.frags > 0, "no fragments found for plugin " .. name)
---@type table<number, boolean>
local added = {}
for _, fid in ipairs(plugin._.frags) do
if not added[fid] then
added[fid] = true
local fragment = self.fragments:get(fid)
assert(fragment, "fragment " .. fid .. " not found, for plugin " .. name)
---@diagnostic disable-next-line: no-unknown
super = setmetatable(fragment.spec, super and { __index = super } or nil)
plugin._.dep = plugin._.dep and fragment.dep
plugin.optional = plugin.optional and (rawget(fragment.spec, "optional") == true)
plugin.url = fragment.url or plugin.url
-- dependencies
for _, dep in ipairs(fragment.deps or {}) do
local dep_meta = self.frag_to_meta[dep]
if dep_meta then
table.insert(plugin.dependencies, dep_meta.name)
end
end
end
end
super = super or {}
-- dir / dev
plugin.dev = super.dev
plugin.dir = super.dir
if plugin.dir then
plugin.dir = Util.norm(plugin.dir)
else
if plugin.dev == nil and plugin.url then
for _, pattern in ipairs(Config.options.dev.patterns) do
if plugin.url:find(pattern, 1, true) then
plugin.dev = true
break
end
end
end
if plugin.dev == true then
local dev_dir = type(Config.options.dev.path) == "string" and Config.options.dev.path .. "/" .. plugin.name
or Util.norm(Config.options.dev.path(plugin))
if not Config.options.dev.fallback or vim.fn.isdirectory(dev_dir) == 1 then
plugin.dir = dev_dir
else
plugin.dev = false
end
end
plugin.dir = plugin.dir or Config.options.root .. "/" .. plugin.name
end
-- dependencies
if #plugin.dependencies == 0 and not super.dependencies then
plugin.dependencies = nil
end
-- optional
if not plugin.optional and not super.optional then
plugin.optional = nil
end
setmetatable(plugin, { __index = super })
return plugin
end
--- Disable a plugin.
---@param plugin LazyPlugin
function M:disable(plugin)
plugin._.kind = "disabled"
self:del(plugin.name)
self.spec.disabled[plugin.name] = plugin
end
--- Check if a plugin should be disabled, but ignore uninstalling it.
function M:fix_cond()
for _, plugin in pairs(self.plugins) do
local cond = plugin.cond
if cond == nil then
cond = Config.options.defaults.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) --[[@as LazyPlugin]]
if not self.spec.ignore_installed[p.name] then
for _, dep in ipairs(p.dependencies or {}) do
table.insert(stack, self.plugins[dep])
end
self.spec.ignore_installed[p.name] = true
end
end
plugin.enabled = false
end
end
end
--- Removes plugins for which all its fragments are optional.
function M:fix_optional()
if self.spec.optional then
return 0
end
local changes = 0
for _, plugin in pairs(self.plugins) do
if plugin.optional then
changes = changes + 1
self:del(plugin.name)
end
end
self:rebuild()
return changes
end
--- Removes plugins that are disabled.
function M:fix_disabled()
local changes = 0
for _, plugin in pairs(self.plugins) do
if plugin.enabled == false or (type(plugin.enabled) == "function" and not plugin.enabled()) then
changes = changes + 1
self:disable(plugin)
end
end
self:rebuild()
return changes
end
--- Removes package fragments for plugins that no longer use the same directory.
function M:fix_pkgs()
for dir, fid in pairs(self.pkgs) do
local plugin = self.frag_to_meta[fid]
plugin = plugin and self.plugins[plugin.name]
if plugin then
-- check if plugin is still in the same directory
if plugin.dir ~= dir then
self.fragments:del(fid)
end
end
end
self:rebuild()
end
--- Resolve all plugins, based on cond, enabled and optional.
function M:resolve()
Util.track("resolve plugins")
self:rebuild()
self:fix_pkgs()
self:fix_cond()
-- selene: allow(empty_loop)
while self:fix_disabled() + self:fix_optional() > 0 do
end
Util.track()
end
return M