refactor(handlers): lazy resolving of plugin handlers (#1126)

* refactor(handlers): lazy resolving of plugin handlers

* test: fixed tests
This commit is contained in:
Folke Lemaitre 2023-10-16 22:34:44 +02:00 committed by GitHub
parent b9c604e839
commit 2f169e74d4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 126 additions and 120 deletions

View File

@ -55,7 +55,7 @@ M.defaults = {
icons = { icons = {
cmd = "", cmd = "",
config = "", config = "",
event = "", event = " ",
ft = "", ft = "",
init = "", init = "",
import = "", import = "",
@ -67,7 +67,7 @@ M.defaults = {
runtime = "", runtime = "",
require = "󰢱 ", require = "󰢱 ",
source = "", source = "",
start = "", start = " ",
task = "", task = "",
list = { list = {
"", "",

View File

@ -37,7 +37,7 @@ M.group = vim.api.nvim_create_augroup("lazy_handler_event", { clear = true })
---@param spec LazyEventSpec ---@param spec LazyEventSpec
---@return LazyEvent ---@return LazyEvent
function M:parse(spec) function M:_parse(spec)
local ret = M.mappings[spec] --[[@as LazyEvent?]] local ret = M.mappings[spec] --[[@as LazyEvent?]]
if ret then if ret then
return ret return ret
@ -62,19 +62,6 @@ function M:parse(spec)
return ret return ret
end end
---@param plugin LazyPlugin
function M:values(plugin)
local Plugin = require("lazy.core.plugin")
---@type table<string,any>
local values = {}
---@diagnostic disable-next-line: no-unknown
for _, value in ipairs(Plugin.values(plugin, self.type, true)) do
local event = self:parse(value)
values[event.id] = event
end
return values
end
---@param event LazyEvent ---@param event LazyEvent
function M:_add(event) function M:_add(event)
local done = false local done = false

View File

@ -13,7 +13,8 @@ function M:add(plugin)
end end
end end
function M:parse(value) ---@return LazyEvent
function M:_parse(value)
return { return {
id = value, id = value,
event = "FileType", event = "FileType",

View File

@ -39,29 +39,20 @@ end
---@param plugin LazyPlugin ---@param plugin LazyPlugin
function M.disable(plugin) function M.disable(plugin)
if not plugin._.handlers_enabled then for type in pairs(plugin._.handlers or {}) do
return M.handlers[type]:del(plugin)
end
plugin._.handlers_enabled = false
for type, handler in pairs(M.handlers) do
if plugin[type] then
handler:del(plugin)
end
end end
end end
---@param plugin LazyPlugin ---@param plugin LazyPlugin
function M.enable(plugin) function M.enable(plugin)
if not plugin._.loaded then if not plugin._.loaded then
if plugin._.handlers_enabled then if not plugin._.handlers then
return M.load(plugin)
end end
for type, handler in pairs(M.handlers) do for type in pairs(plugin._.handlers or {}) do
if plugin[type] then M.handlers[type]:add(plugin)
handler:add(plugin)
end
end end
plugin._.handlers_enabled = true
end end
end end
@ -86,21 +77,40 @@ function M:_add(_value) end
---@protected ---@protected
function M:_del(_value) end function M:_del(_value) end
---@param value any
---@param _plugin LazyPlugin
---@return string|{id:string}
function M:_parse(value, _plugin)
assert(type(value) == "string", "Expected string, got " .. vim.inspect(value))
return value
end
---@param values any[]
---@param plugin LazyPlugin ---@param plugin LazyPlugin
function M:values(plugin) function M:_values(values, plugin)
local Plugin = require("lazy.core.plugin")
---@type table<string,any> ---@type table<string,any>
local values = {} local ret = {}
---@diagnostic disable-next-line: no-unknown for _, value in ipairs(values) do
for _, value in ipairs(Plugin.values(plugin, self.type, true)) do local parsed = self:_parse(value, plugin)
values[value] = value ret[type(parsed) == "string" and parsed or parsed.id] = parsed
end
return ret
end
---@param plugin LazyPlugin
function M.load(plugin)
local Plugin = require("lazy.core.plugin")
plugin._.handlers = {}
for type, handler in pairs(M.handlers) do
if plugin[type] then
plugin._.handlers[type] = handler:_values(Plugin.values(plugin, type, true), plugin)
end
end end
return values
end end
---@param plugin LazyPlugin ---@param plugin LazyPlugin
function M:add(plugin) function M:add(plugin)
for key, value in pairs(self:values(plugin)) do for key, value in pairs(plugin._.handlers[self.type] or {}) do
if not self.active[key] then if not self.active[key] then
self.active[key] = {} self.active[key] = {}
self:_add(value) self:_add(value)
@ -112,7 +122,10 @@ end
---@param plugin LazyPlugin ---@param plugin LazyPlugin
function M:del(plugin) function M:del(plugin)
for key, value in pairs(self:values(plugin)) do if not plugin._.handlers then
return
end
for key, value in pairs(plugin._.handlers[self.type] or {}) do
if self.active[key] and self.active[key][plugin.name] then if self.active[key] and self.active[key][plugin.name] then
self.active[key][plugin.name] = nil self.active[key][plugin.name] = nil
if vim.tbl_isempty(self.active[key]) then if vim.tbl_isempty(self.active[key]) then

View File

@ -19,10 +19,13 @@ local Util = require("lazy.core.util")
---@field rhs? string|fun() rhs ---@field rhs? string|fun() rhs
---@field mode? string ---@field mode? string
---@field id string ---@field id string
---@field name string
---@class LazyKeysHandler:LazyHandler ---@class LazyKeysHandler:LazyHandler
local M = {} local M = {}
local skip = { mode = true, id = true, ft = true, rhs = true, lhs = true }
---@param value string|LazyKeysSpec ---@param value string|LazyKeysSpec
---@param mode? string ---@param mode? string
---@return LazyKeys ---@return LazyKeys
@ -37,12 +40,18 @@ function M.parse(value, mode)
ret[2] = nil ret[2] = nil
ret.mode = mode or "n" ret.mode = mode or "n"
ret.id = vim.api.nvim_replace_termcodes(ret.lhs, true, true, true) ret.id = vim.api.nvim_replace_termcodes(ret.lhs, true, true, true)
if ret.mode ~= "n" then if ret.mode ~= "n" then
ret.id = ret.id .. " (" .. ret.mode .. ")" ret.id = ret.id .. " (" .. ret.mode .. ")"
end end
return ret return ret
end end
---@param keys LazyKeys
function M.to_string(keys)
return keys.lhs .. (keys.mode == "n" and "" or " (" .. keys.mode .. ")")
end
---@param lhs string ---@param lhs string
---@param mode? string ---@param mode? string
function M:have(lhs, mode) function M:have(lhs, mode)
@ -50,10 +59,8 @@ function M:have(lhs, mode)
return self.managed[keys.id] ~= nil return self.managed[keys.id] ~= nil
end end
---@param plugin LazyPlugin function M:_values(values)
function M:values(plugin) return M.resolve(values)
local Plugin = require("lazy.core.plugin")
return M.resolve(Plugin.values(plugin, "keys", true))
end end
---@param spec? (string|LazyKeysSpec)[] ---@param spec? (string|LazyKeysSpec)[]
@ -79,7 +86,6 @@ end
---@param keys LazyKeys ---@param keys LazyKeys
function M.opts(keys) function M.opts(keys)
local skip = { mode = true, id = true, ft = true, rhs = true, lhs = true }
local opts = {} ---@type LazyKeysBase local opts = {} ---@type LazyKeysBase
---@diagnostic disable-next-line: no-unknown ---@diagnostic disable-next-line: no-unknown
for k, v in pairs(keys) do for k, v in pairs(keys) do
@ -106,8 +112,9 @@ function M:_add(keys)
self.active[keys.id] = nil self.active[keys.id] = nil
if plugins then if plugins then
Util.track({ keys = lhs }) local name = M.to_string(keys)
Loader.load(plugins, { keys = lhs }) Util.track({ keys = name })
Loader.load(plugins, { keys = name })
Util.track() Util.track()
end end

View File

@ -556,10 +556,6 @@ function M.load()
Config.plugins[name]._ = plugin._ Config.plugins[name]._ = plugin._
Config.plugins[name]._.dep = dep Config.plugins[name]._.dep = dep
Config.plugins[name]._.super = super Config.plugins[name]._.super = super
-- FIXME: work-around for changes related to Plugin.values
for handler in pairs(Handler) do
Config.plugins[name][handler] = plugin[handler]
end
end end
end end
Util.track() Util.track()
@ -607,29 +603,32 @@ function M.values(plugin, prop, is_list)
if not plugin[prop] then if not plugin[prop] then
return {} return {}
end end
plugin._.values = plugin._.values or {} plugin._.cache = plugin._.cache or {}
local key = prop .. (is_list and "_list" or "") local key = prop .. (is_list and "_list" or "")
if plugin._.values[key] == nil then if plugin._.cache[key] == nil then
plugin[prop] = M._values(plugin, prop, is_list) plugin._.cache[key] = M._values(plugin, plugin, prop, is_list)
plugin._.values[key] = true
end end
return plugin[prop] or {} return plugin._.cache[key]
end end
-- Merges super values or runs the values function to override values or return new ones -- Merges super values or runs the values function to override values or return new ones
-- Used for opts, cmd, event, ft and keys -- Used for opts, cmd, event, ft and keys
---@param root LazyPlugin
---@param plugin LazyPlugin ---@param plugin LazyPlugin
---@param prop string ---@param prop string
---@param is_list? boolean ---@param is_list? boolean
function M._values(plugin, prop, is_list) function M._values(root, plugin, prop, is_list)
if not plugin[prop] then
return {}
end
---@type table ---@type table
local ret = plugin._.super and M._values(plugin._.super, prop, is_list) or {} local ret = plugin._.super and M._values(root, plugin._.super, prop, is_list) or {}
local values = rawget(plugin, prop) local values = rawget(plugin, prop)
if not values then if not values then
return ret return ret
elseif type(values) == "function" then elseif type(values) == "function" then
ret = values(plugin, ret) or ret ret = values(root, ret) or ret
return type(ret) == "table" and ret or { ret } return type(ret) == "table" and ret or { ret }
end end

View File

@ -20,8 +20,8 @@
---@field module? string ---@field module? string
---@field dir? string Explicit dir or dev set for this plugin ---@field dir? string Explicit dir or dev set for this plugin
---@field rtp_loaded? boolean ---@field rtp_loaded? boolean
---@field values? table<string,boolean> ---@field handlers? LazyPluginHandlers
---@field handlers_enabled? boolean ---@field cache? table<string,any>
---@alias PluginOpts table|fun(self:LazyPlugin, opts:table):table? ---@alias PluginOpts table|fun(self:LazyPlugin, opts:table):table?
@ -32,12 +32,11 @@
---@field build? string|fun(self:LazyPlugin)|(string|fun(self:LazyPlugin))[] ---@field build? string|fun(self:LazyPlugin)|(string|fun(self:LazyPlugin))[]
---@field opts? PluginOpts ---@field opts? PluginOpts
---@class LazyPluginHandlers ---@class LazyPluginHandlers: {[string]: any}
---@field event? LazyEventSpec[] ---@field event? table<string,LazyEvent>
---@field cmd? string[] ---@field ft? table<string,LazyEvent>
---@field ft? string[] ---@field keys? table<string,LazyKeys>
---@field keys? (string|LazyKeysSpec)[] ---@field cmd? table<string,string>
---@field module? false
---@class LazyPluginRef ---@class LazyPluginRef
---@field branch? string ---@field branch? string

View File

@ -1,6 +1,7 @@
local Config = require("lazy.core.config") local Config = require("lazy.core.config")
local Git = require("lazy.manage.git") local Git = require("lazy.manage.git")
local Handler = require("lazy.core.handler") local Handler = require("lazy.core.handler")
local Keys = require("lazy.core.handler.keys")
local Plugin = require("lazy.core.plugin") local Plugin = require("lazy.core.plugin")
local Sections = require("lazy.view.sections") local Sections = require("lazy.view.sections")
local Util = require("lazy.util") local Util = require("lazy.util")
@ -334,12 +335,10 @@ function M:reason(reason, opts)
else else
self:append(" ") self:append(" ")
end end
if key == "keys" then
value = type(value) == "string" and value or value.lhs or value[1]
end
local hl = "LazyReason" .. key:sub(1, 1):upper() .. key:sub(2) local hl = "LazyReason" .. key:sub(1, 1):upper() .. key:sub(2)
local icon = Config.options.ui.icons[key] local icon = Config.options.ui.icons[key]
if icon then if icon then
icon = icon:gsub("%s*$", "")
self:append(icon .. " ", hl) self:append(icon .. " ", hl)
self:append(value, hl) self:append(value, hl)
else else
@ -411,35 +410,18 @@ function M:plugin(plugin)
end end
local plugin_start = self:row() local plugin_start = self:row()
if plugin._.loaded then if plugin._.loaded then
-- When the plugin is loaded, only show the loading reason
self:reason(plugin._.loaded) self:reason(plugin._.loaded)
else else
-- otherwise show all lazy handlers
self:append(" ") self:append(" ")
local reason = {} self:handlers(plugin)
if plugin._.kind ~= "disabled" and plugin._.handlers_enabled then
for handler in pairs(Handler.types) do
if plugin[handler] then
local values = Handler.handlers[handler]:values(plugin)
values = vim.tbl_map(function(value)
if handler == "ft" or handler == "event" then
---@cast value LazyEvent
return value.id
elseif handler == "keys" then
---@cast value LazyKeys
return value.lhs .. (value.mode == "n" and "" or " (" .. value.mode .. ")")
end
return value
end, vim.tbl_values(values))
table.sort(values)
reason[handler] = table.concat(values, " ")
end
end
end
for _, other in pairs(Config.plugins) do for _, other in pairs(Config.plugins) do
if vim.tbl_contains(other.dependencies or {}, plugin.name) then if vim.tbl_contains(other.dependencies or {}, plugin.name) then
reason.plugin = other.name self:reason({ plugin = other.name })
self:append(" ")
end end
end end
self:reason(reason)
end end
self:diagnostics(plugin) self:diagnostics(plugin)
self:nl() self:nl()
@ -542,26 +524,36 @@ function M:details(plugin)
end end
end) end)
if plugin._.handlers_enabled then for handler in pairs(plugin._.handlers or {}) do
for handler in pairs(Handler.types) do table.insert(props, {
if plugin[handler] then handler,
table.insert(props, { function()
handler, self:handlers(plugin, handler)
function() end,
for _, value in ipairs(Plugin.values(plugin, handler, true)) do })
self:reason({ [handler] = value })
self:append(" ")
end
end,
})
end
end
end end
self:props(props, { indent = 6 }) self:props(props, { indent = 6 })
self:nl() self:nl()
end end
---@param plugin LazyPlugin
---@param types? LazyHandlerTypes[]|LazyHandlerTypes
function M:handlers(plugin, types)
if not plugin._.handlers then
return
end
types = type(types) == "string" and { types } or types
types = types and types or vim.tbl_keys(Handler.types)
for _, t in ipairs(types) do
for id, value in pairs(plugin._.handlers[t] or {}) do
value = t == "keys" and Keys.to_string(value) or id
self:reason({ [t] = value })
self:append(" ")
end
end
end
---@alias LazyProps {[1]:string, [2]:string|fun(), [3]?:string}[] ---@alias LazyProps {[1]:string, [2]:string|fun(), [3]?:string}[]
---@param props LazyProps ---@param props LazyProps
---@param opts? {indent: number} ---@param opts? {indent: number}
@ -690,16 +682,16 @@ function M:debug()
Util.foreach(require("lazy.core.handler").handlers, function(handler_type, handler) Util.foreach(require("lazy.core.handler").handlers, function(handler_type, handler)
Util.foreach(handler.active, function(value, plugins) Util.foreach(handler.active, function(value, plugins)
value = type(value) == "table" and value[1] or value assert(type(value) == "string")
if not vim.tbl_isempty(plugins) then if not vim.tbl_isempty(plugins) then
---@type string[] ---@type string[]
plugins = vim.tbl_values(plugins) plugins = vim.tbl_values(plugins)
table.sort(plugins) table.sort(plugins)
self:append("", "LazySpecial", { indent = 2 }) self:append("", "LazySpecial", { indent = 2 })
if handler_type == "keys" then if handler_type == "keys" then
for k, v in pairs(Handler.handlers.keys:values(Config.plugins[plugins[1]])) do for k, v in pairs(Config.plugins[plugins[1]]._.handlers.keys) do
if k == value then if k == value then
value = v value = v.name
break break
end end
end end

View File

@ -1,4 +1,5 @@
local Config = require("lazy.core.config") local Config = require("lazy.core.config")
local Handler = require("lazy.core.handler")
local Plugin = require("lazy.core.plugin") local Plugin = require("lazy.core.plugin")
local assert = require("luassert") local assert = require("luassert")
@ -142,6 +143,9 @@ describe("plugin spec opt", function()
end) end)
describe("deps", function() describe("deps", function()
before_each(function()
Handler.init()
end)
it("handles dep names", function() it("handles dep names", function()
Config.options.defaults.lazy = false Config.options.defaults.lazy = false
local tests = { local tests = {
@ -237,11 +241,13 @@ describe("plugin spec opt", function()
local spec = Plugin.Spec.new(test) local spec = Plugin.Spec.new(test)
assert(#spec.notifs == 0) assert(#spec.notifs == 0)
assert(vim.tbl_count(spec.plugins) == 1) assert(vim.tbl_count(spec.plugins) == 1)
Plugin.values(spec.plugins.bar, "event", true) Handler.load(spec.plugins.bar)
assert(type(spec.plugins.bar.event) == "table") vim.print(spec.plugins.bar._.handlers)
assert(#spec.plugins.bar.event == 2) local events = vim.tbl_keys(spec.plugins.bar._.handlers.event or {})
assert(vim.tbl_contains(spec.plugins.bar.event, "mod1")) assert(type(events) == "table")
assert(vim.tbl_contains(spec.plugins.bar.event, "mod2")) assert(#events == 2)
assert(vim.tbl_contains(events, "mod1"))
assert(vim.tbl_contains(events, "mod2"))
end end
end) end)
end) end)
@ -297,14 +303,16 @@ describe("plugin spec opt", function()
{ { "foo/bar", event = "mod1" }, { "foo/bar", event = { "mod2" } } }, { { "foo/bar", event = "mod1" }, { "foo/bar", event = { "mod2" } } },
} }
for _, test in ipairs(tests) do for _, test in ipairs(tests) do
Handler.init()
local spec = Plugin.Spec.new(test) local spec = Plugin.Spec.new(test)
assert(#spec.notifs == 0) assert(#spec.notifs == 0)
assert(vim.tbl_count(spec.plugins) == 1) assert(vim.tbl_count(spec.plugins) == 1)
Plugin.values(spec.plugins.bar, "event", true) Handler.load(spec.plugins.bar)
assert(type(spec.plugins.bar.event) == "table") local events = spec.plugins.bar._.handlers.event
assert(#spec.plugins.bar.event == 2) assert(type(events) == "table")
assert(vim.tbl_contains(spec.plugins.bar.event, "mod1")) assert(vim.tbl_count(events) == 2)
assert(vim.tbl_contains(spec.plugins.bar.event, "mod2")) assert(events["mod1"])
assert(events["mod2"])
end end
end) end)