-
Notifications
You must be signed in to change notification settings - Fork 9
Custom handler examples
Writing a custom lz.n.Handler
boils down to the following recipe:
- Declare a
lz.n.PluginSpec
field that will be used to configure how the handler lazy-loads a plugin. - Keep track of plugins that have such a field, with the
add
function. - Provide a way for
lz.n
to check if a plugin is pending to be lazy-loaded by this handler, with thelookup
function. - Implement the lazy-loading logic.
- Provide a way for
lz.n
to remove a plugin from the handler if it has been loaded.
Tip
To minimise a handler's impact on startup time, the add
function should do as little as possible, i.e.
- Check if a plugin has a matching field.
- If so, update the handler's state.
Defer any other logic to when the plugin is loaded.
The following examples are out of scope for lz.n
, but are listed here for documentation purposes.
Feel free to use them in your configs.
This is adapted from #25.
Warning
If you're using Neovim's built-in loading mechanism, lz-n-auto-require
is probably better suited for this use case.
This example is brittle, as a plugin may add, remove or rename top-level modules after an update.
---@class lz.n.ReqPluginSpec: lz.n.PluginSpec
---@field on_require string[]
---@class lz.n.ReqPlugin: lz.n.Plugin
---@field on_require string[]
---@type lz.n.handler.State
local state = require("lz.n.handler.state").new()
---@type lz.n.Handler
local M = {
spec_field = "on_require",
---@param plugin lz.n.ReqPlugin
add = function(plugin)
if not plugin.on_require then
return
end
state.insert(plugin)
end,
del = state.del,
lookup = state.lookup_plugin,
}
local trigger_load = require("lz.n").trigger_load
-- How we search for and load our plugins.
---@param mod_path string
---@return boolean
local function call(mod_path)
local triggered_load = false
---@param plugin lz.n.ReqPlugin
state.each_pending(function(plugin)
local on_req = plugin.on_require
---@type string[]
local mod_paths = {}
if type(on_req) == "table" then
---@cast on_req string[]
mod_paths = on_req
elseif type(on_req) == "string" then
mod_paths = { on_req }
end
local has_mod = vim.iter(mod_paths):any(function(path)
return vim.startswith(mod_path, path)
end)
if has_mod then
trigger_load(plugin)
triggered_load = true
end
end)
return triggered_load
end
--- Override `require` to search for plugins to lazy-load.
local oldrequire = require
require("_G").require = function(mod_path)
local ok, value = pcall(oldrequire, mod_path)
if ok then
return value
end
package.loaded[mod_path] = nil
if call(mod_path) == true then
return oldrequire(mod_path)
end
error(value)
end
return M
After registering the handler, you can then use it like this:
require("lz.n").load({
"plenary.nvim",
-- top-level module
on_require = {"plenary"}
})
This custom handler will allow you to mark a spec to "load_before" another one.
It will load the spec after the before hook and before the load hook of any of the named plugins.
If any of the plugins listed to "load_before" on this spec have already been loaded, it will instead be loaded immediately.
For the reasons here this is RARELY necessary.
But when it is, this is very nice!
Usage:
If you put the below handler in lua/handlers/load_before.lua in your config.
At the start of your config run:
require('lz.n').register_handler(require("handlers.load_before"))
In a spec:
{
-- If using nix it would be called "luasnip" instead of "LuaSnip"
"LuaSnip",
-- marking luasnip to "load_before" nvim-cmp, so that it is always available when nvim-cmp is loaded.
load_before = { "nvim-cmp" },
after = function (plugin)
local luasnip = require 'luasnip'
require('luasnip.loaders.from_vscode').lazy_load()
luasnip.config.setup {}
end,
},
The handler:
local trigger_load = require("lz.n").trigger_load
local states = require("lz.n.handler.state").new()
---@type table<string, true>
local called = {}
---@type lz.n.Handler
---@diagnostic disable-next-line: missing-fields
local M = {
spec_field = "load_before",
lookup = states.lookup_plugin
}
---@param plugin lz.n.Plugin
function M.add(plugin)
local dep_of = plugin.load_before
---@type string[]
local needed_by = {}
if type(dep_of) == "table" then
---@cast dep_of string[]
needed_by = dep_of
elseif type(dep_of) == "string" then
needed_by = { dep_of }
else
return
end
for _, name in ipairs(needed_by) do
if called[name] == true then
trigger_load(plugin)
return
end
end
for _, name in ipairs(needed_by) do
states.insert(name, plugin)
end
end
---@param pname string
function M.del(pname)
states.del(pname)
called[pname] = true
if states.has_pending_plugins(pname) then
states.each_pending(pname,
function (p)
states.del(p.name)
trigger_load(p)
end
)
end
end
return M