-
-
Notifications
You must be signed in to change notification settings - Fork 14
Custom handler examples
Writing a custom lz.n.Handler boils down to the following recipe:
- Declare a
lz.n.PluginSpecfield that will be used to configure how the handler lazy-loads a plugin. - Keep track of plugins that have such a field, with the
addfunction. - Provide a way for
lz.nto check if a plugin is pending to be lazy-loaded by this handler, with thelookupfunction. - Implement the lazy-loading logic.
- Provide a way for
lz.nto 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 MAfter 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