diff --git a/README.md b/README.md index 6010c4b..a4fd428 100755 --- a/README.md +++ b/README.md @@ -315,6 +315,42 @@ Or ├── init.lua ``` +### Custom Handlers + +You may register your own handlers to lazy-load plugins via +other triggers not already covered by the plugin spec. + +You should register all handlers before calling `require('lz.n').load`, +because they will not be retroactively applied to +the `load` calls that occur before they are registered. + +The `register_handler` function returns a boolean that indicates success. + +`require("lz.n").register_handler(handler: lz.n.Handler): boolean` + +#### lz.n.Handler + + +| Property | Type | Description | +|----------|------|-------------| +| spec_field | `string` | the `lz.n.PluginSpec` field defined by the handler | +| add | `fun(plugin: lz.n.Plugin)` | adds a plugin to the handler | +| del | `fun(plugin: lz.n.Plugin)?` | removes a plugin from the handler | + + +When writing custom handlers, +you can load the plugin and run the hooks from +the spec with the following function: + +```lua + ---@type fun(plugins: string | lz.n.Plugin | string[] | lz.n.Plugin[]) + require('lz.n').trigger_load +``` + +The function accepts plugin names or parsed plugin specs. +It will call the handler's `del` function (if it exists) after the `before` hooks, +and before `load` of the plugin's spec. + ## :green_heart: Contributing All contributions are welcome! diff --git a/lua/lz/n/handler/cmd.lua b/lua/lz/n/handler/cmd.lua index d2ea6e0..4805d77 100644 --- a/lua/lz/n/handler/cmd.lua +++ b/lua/lz/n/handler/cmd.lua @@ -5,7 +5,7 @@ local loader = require("lz.n.loader") ---@type lz.n.CmdHandler local M = { pending = {}, - type = "cmd", + spec_field = "cmd", } ---@param cmd string diff --git a/lua/lz/n/handler/colorscheme.lua b/lua/lz/n/handler/colorscheme.lua index 3e01d0e..c1bdde6 100644 --- a/lua/lz/n/handler/colorscheme.lua +++ b/lua/lz/n/handler/colorscheme.lua @@ -6,8 +6,8 @@ local loader = require("lz.n.loader") ---@type lz.n.ColorschemeHandler local M = { pending = {}, - type = "colorscheme", augroup = nil, + spec_field = "colorscheme", } ---@param plugin lz.n.Plugin diff --git a/lua/lz/n/handler/event.lua b/lua/lz/n/handler/event.lua index 8d429a8..802bcb1 100644 --- a/lua/lz/n/handler/event.lua +++ b/lua/lz/n/handler/event.lua @@ -23,7 +23,7 @@ local M = { pending = {}, events = {}, group = vim.api.nvim_create_augroup("lz_n_handler_event", { clear = true }), - type = "event", + spec_field = "event", ---@param spec lz.n.EventSpec parse = function(spec) local ret = lz_n_events[spec] diff --git a/lua/lz/n/handler/ft.lua b/lua/lz/n/handler/ft.lua index 81901b7..362d2bc 100644 --- a/lua/lz/n/handler/ft.lua +++ b/lua/lz/n/handler/ft.lua @@ -6,7 +6,7 @@ local event = require("lz.n.handler.event") ---@type lz.n.FtHandler local M = { pending = {}, - type = "ft", + spec_field = "ft", ---@param value string ---@return lz.n.Event parse = function(value) diff --git a/lua/lz/n/handler/init.lua b/lua/lz/n/handler/init.lua index 13429f5..2d22250 100644 --- a/lua/lz/n/handler/init.lua +++ b/lua/lz/n/handler/init.lua @@ -1,20 +1,5 @@ ----@class lz.n.Handler ----@field type lz.n.HandlerTypes ----@field pending table> -- key: plugin_name: plugin_name ----@field add fun(plugin: lz.n.Plugin) ----@field del? fun(plugin: lz.n.Plugin) - local M = {} ----@enum lz.n.HandlerTypes -M.types = { - cmd = "cmd", - event = "event", - ft = "ft", - keys = "keys", - colorscheme = "colorscheme", -} - local handlers = { cmd = require("lz.n.handler.cmd"), event = require("lz.n.handler.event"), @@ -23,6 +8,31 @@ local handlers = { colorscheme = require("lz.n.handler.colorscheme"), } +---@param spec lz.n.PluginSpec +---@return boolean +function M.is_lazy(spec) + ---@diagnostic disable-next-line: undefined-field + return spec.lazy or vim.iter(handlers):any(function(spec_field, _) + return spec[spec_field] ~= nil + end) +end + +---@param handler lz.n.Handler +---@return boolean success +function M.register_handler(handler) + if handlers[handler.spec_field] == nil then + handlers[handler.spec_field] = handler + return true + else + vim.notify( + "Handler already exists for " .. handler.spec_field .. ". Refusing to register new handler.", + vim.log.levels.ERROR, + { title = "lz.n" } + ) + return false + end +end + ---@param plugin lz.n.Plugin local function enable(plugin) for _, handler in pairs(handlers) do @@ -32,7 +42,7 @@ end function M.disable(plugin) for _, handler in pairs(handlers) do - if type(handler.del) == "function" then + if handler.del then handler.del(plugin) end end diff --git a/lua/lz/n/handler/keys.lua b/lua/lz/n/handler/keys.lua index 271403b..c69ff8a 100644 --- a/lua/lz/n/handler/keys.lua +++ b/lua/lz/n/handler/keys.lua @@ -5,7 +5,7 @@ local loader = require("lz.n.loader") ---@type lz.n.KeysHandler local M = { pending = {}, - type = "keys", + spec_field = "keys", ---@param value string|lz.n.KeysSpec ---@param mode? string ---@return lz.n.Keys diff --git a/lua/lz/n/init.lua b/lua/lz/n/init.lua index 10548ad..12d1984 100644 --- a/lua/lz/n/init.lua +++ b/lua/lz/n/init.lua @@ -14,6 +14,12 @@ local deferred_ui_enter = vim.schedule_wrap(function() vim.api.nvim_exec_autocmds("User", { pattern = "DeferredUIEnter", modeline = false }) end) +---@type fun(handler: lz.n.Handler): boolean +M.register_handler = require("lz.n.handler").register_handler + +---@type fun(plugins: string | lz.n.Plugin | string[] | lz.n.Plugin[]) +M.trigger_load = require("lz.n.loader").load + ---@overload fun(spec: lz.n.Spec) ---@overload fun(import: string) function M.load(spec) diff --git a/lua/lz/n/meta.lua b/lua/lz/n/meta.lua index 0c4f1f6..1295e87 100644 --- a/lua/lz/n/meta.lua +++ b/lua/lz/n/meta.lua @@ -88,5 +88,10 @@ error("Cannot import a meta module") --- Takes the plugin name (not the module name). Defaults to |packadd| if not set. --- @field load? fun(name: string) +--- @class lz.n.Handler +--- @field spec_field string +--- @field add fun(plugin: lz.n.Plugin) +--- @field del? fun(plugin: lz.n.Plugin) + --- @type lz.n.Config vim.g.lz_n = vim.g.lz_n diff --git a/lua/lz/n/spec.lua b/lua/lz/n/spec.lua index c1f72b1..75ac512 100644 --- a/lua/lz/n/spec.lua +++ b/lua/lz/n/spec.lua @@ -144,11 +144,7 @@ local function parse(spec) table.insert(result.colorscheme, _colorscheme_spec) end end - result.lazy = result.lazy - or result.event ~= nil - or result.keys ~= nil - or result.cmd ~= nil - or result.colorscheme ~= nil + result.lazy = require("lz.n.handler").is_lazy(spec) return result end diff --git a/spec/register_handler_spec.lua b/spec/register_handler_spec.lua new file mode 100644 index 0000000..f56686e --- /dev/null +++ b/spec/register_handler_spec.lua @@ -0,0 +1,47 @@ +---@diagnostic disable: invisible +vim.g.lz_n = { + load = function() end, +} +local lz_n = require("lz.n") +local spy = require("luassert.spy") + +describe("handlers.custom", function() + ---@class TestHandler: lz.n.Handler + ---@type TestHandler + local hndl = { + spec_field = "testfield", + add = function(_) end, + del = function(_) end, + } + local addspy = spy.on(hndl, "add") + local delspy = spy.on(hndl, "del") + it("Duplicate handlers fail to register", function() + local notispy = spy.new(function() end) + -- NOTE: teardown fails if you don't temporarily replace vim.notify + local og_notify = vim.notify + vim.notify = notispy + assert.False(lz_n.register_handler(require("lz.n.handler.ft"))) + assert.spy(notispy).called(1) + vim.notify = og_notify + end) + it("can add plugins to the handler", function() + assert.True(lz_n.register_handler(hndl)) + lz_n.load({ + "testplugin", + testfield = { "a", "b" }, + }) + assert.spy(addspy).called_with({ + name = "testplugin", + testfield = { "a", "b" }, + lazy = true, + }) + end) + it("loading a plugin removes it from the handler", function() + lz_n.trigger_load("testplugin") + assert.spy(delspy).called_with({ + name = "testplugin", + testfield = { "a", "b" }, + lazy = true, + }) + end) +end)