lazy.nvim
lazy.nvim copied to clipboard
feature: load handlers lazily
Did you check the docs?
- [X] I have read all the lazy.nvim docs
Is your feature request related to a problem? Please describe.
When too many lazy handlers are added, lazy.nvim will take too long to enable all of them. I had hundreds of keys handlers and that slow startup time a lot. But these handlers are not possible to trigger before some specific events, and it's a waste of time for starting these handlers in the startup.
e.g:
keys
handler is always after "SafeState", because user can't type any command before that.
cmd
handler is always after "CmdlineChange" since user needs to type one character for run a command or do a completion (or "CmdlineEnter" if user uses other way to do that).
ft
handler is always after "BufEnter" ("BufNewFile" or "BufReadPost" when starting Neovim with a file parameter).
And most of event handlers
are after "SafeState" too.
https://github.com/folke/lazy.nvim/blob/d3974346b6cef2116c8e7b08423256a834cb7cbc/lua/lazy/core/handler/init.lua#L100-L109 https://github.com/folke/lazy.nvim/blob/d3974346b6cef2116c8e7b08423256a834cb7cbc/lua/lazy/core/handler/init.lua#L47-L57
Describe the solution you'd like
I write a demo outside the lazy.nvim in my configuration. You can try this code before lazy.setup(), it will override the original handler.
local Handler=require("lazy.core.handler")
local Plugin=require("lazy.core.plugin")
local delay_handlers={
keys="SafeState",
cmd="CmdlineEnter",
ft={"BufEnter","BufReadPost","BufNewFile"},
}
local delay={}
function Handler.resolve(plugin)
plugin._.handlers={}
local handlers=plugin._.handlers
for type in pairs(Handler.handlers) do
if plugin[type] then
handlers[type]=delay
end
end
end
local startup_events={
["user"]=true,
["sourcecmd"]=true,
["sourcepost"]=true,
["sourcepre"]=true,
["bufenter"]=true,
["bufreadpost"]=true,
["bufnewfile"]=true,
["vimenter"]=true,
["uienter"]=true,
["safestate"]=true,
}
local function search_fn(v)
return startup_events[v.event:lower()]
end
---@param plugin LazyPlugin
function Handler.enable(plugin)
if plugin._.loaded then
return
end
if not plugin._.handlers then
Handler.resolve(plugin)
end
for type in pairs(plugin._.handlers or {}) do
local handlers=plugin._.handlers
if type=="event" then
local handler=Handler.handlers[type]
local events=handler:_values(Plugin.values(plugin,type,true),plugin)
handlers[type]=events
if vim.tbl_contains(events,search_fn,{predicate=true}) then
handler:add(plugin)
else
vim.api.nvim_create_autocmd("SafeState",{
once=true,
callback=function()
handler:add(plugin)
end,
})
end
goto next
end
vim.api.nvim_create_autocmd(delay_handlers[type],{
once=true,
callback=function()
local handler=Handler.handlers[type]
handlers[type]=handler:_values(Plugin.values(plugin,type,true),plugin)
handler:add(plugin)
end,
})
::next::
end
end
Describe alternatives you've considered
My solution is a bit extreme, ft and event part may not very stable, but the keys and cmd handler is very stable for SafeState
and CmdlineEnter
event. Simply loading these two events lazily can reduce most of the load time.
Additional context
I installed 109 plugins in total, here's how this optimization improves startup time:
before
after
Note the handlers is taking 18.53ms before, and now it only takes 1.44ms, it's about 10 time faster (the improvement base on how many handlers you had in total)
I think cmd
should not be loaded on CmdlineEnter
or CmdlineChanged
.
Because the commands may be called from cmd-mappings. It does not call command line events.
You are right. User command can be called before CmdlineEnter
, but I think most User command will be called after CmdlineEnter
, There could be a way to balance all this.
The keys
is always called after SafeState
, I think that should be fine.
This is honestly not worth the effort. If users would be setting up keymaps etc before loading lazy, or right after setting up lazy, then the order of defining keymaps would change and potentially break stuff.