feat: handler.state module

This commit is contained in:
Marc Jakobi
2024-08-28 14:00:38 +02:00
committed by Marc Jakobi
parent 8611566831
commit efe92fa725
9 changed files with 153 additions and 120 deletions
+8 -25
View File
@@ -2,8 +2,8 @@ local loader = require("lz.n.loader")
---@class lz.n.CmdHandler: lz.n.Handler
---@type table<string, table<string, lz.n.Plugin[]>>
local pending = {}
---@type lz.n.handler.State
local state = require("lz.n.handler.state").new()
---@type lz.n.CmdHandler
local M = {
@@ -13,25 +13,14 @@ local M = {
---@param name string
---@return lz.n.Plugin?
function M.lookup(name)
return require("lz.n.handler.extra").lookup(pending, name)
return state.lookup_plugin(name)
end
---@param cmd string
---@return string[] loaded_plugin_names
local function load(cmd)
vim.api.nvim_del_user_command(cmd)
local plugins = pending[cmd]
-- Make sure trigger_load calls in before hooks can't interfere with the state,
-- but they can load a plugin before it's loaded by this handler
vim
.iter(vim.deepcopy(pending[cmd]))
---@param plugin lz.n.Plugin
:each(function(_, plugin)
if pending[cmd][plugin.name] then
loader.load(plugin)
end
end)
return vim.tbl_keys(plugins)
return state.each_pending(cmd, loader.load)
end
---@param cmd string
@@ -88,14 +77,9 @@ end
---@param name string
function M.del(name)
vim.iter(pending)
:filter(function(_, plugins)
return plugins[name] ~= nil
end)
:each(function(cmd, plugins)
pcall(vim.api.nvim_del_user_command, cmd)
plugins[name] = nil
end)
state.del(name, function(cmd)
pcall(vim.api.nvim_del_user_command, cmd)
end)
end
---@param plugin lz.n.Plugin
@@ -105,8 +89,7 @@ function M.add(plugin)
end
---@param cmd string
vim.iter(plugin.cmd):each(function(cmd)
pending[cmd] = pending[cmd] or {}
pending[cmd][plugin.name] = plugin
state.insert(cmd, plugin)
add_cmd(cmd)
end)
end
+6 -23
View File
@@ -3,8 +3,8 @@ local loader = require("lz.n.loader")
---@class lz.n.ColorschemeHandler: lz.n.Handler
---@field augroup? integer
---@type table<string, table<string, lz.n.Plugin[]>>
local pending = {}
---@type lz.n.handler.State
local state = require("lz.n.handler.state").new()
---@type lz.n.ColorschemeHandler
local M = {
@@ -15,33 +15,17 @@ local M = {
---@param name string
---@return lz.n.Plugin?
function M.lookup(name)
return require("lz.n.handler.extra").lookup(pending, name)
return state.lookup_plugin(name)
end
---@param name string
function M.del(name)
vim.iter(pending):each(function(_, plugins)
plugins[name] = nil
end)
state.del(name)
end
---@param name string
local function on_colorscheme(name)
local plugins = pending[name] or {}
if vim.tbl_isempty(plugins) then
-- already loaded
return
end
-- Make sure trigger_load calls in before hooks can't interfere with the state,
-- but they can load a plugin before it's loaded by this handler
vim
.iter(vim.deepcopy(pending[name]))
---@param plugin lz.n.Plugin
:each(function(_, plugin)
if pending[name][plugin.name] then
loader.load(plugin)
end
end)
state.each_pending(name, loader.load)
end
local function init()
@@ -65,8 +49,7 @@ function M.add(plugin)
init()
---@param colorscheme string
vim.iter(plugin.colorscheme):each(function(colorscheme)
pending[colorscheme] = pending[colorscheme] or {}
pending[colorscheme][plugin.name] = plugin
state.insert(colorscheme, plugin)
end)
end
+12 -24
View File
@@ -18,8 +18,8 @@ local lz_n_events = {
lz_n_events["User DeferredUIEnter"] = lz_n_events.DeferredUIEnter
---@type table<string, table<string, lz.n.Plugin[]>>
local pending = {}
---@type lz.n.handler.State
local state = require("lz.n.handler.state").new()
---@type lz.n.EventHandler
local M = {
@@ -61,7 +61,7 @@ local M = {
---@param name string
---@return lz.n.Plugin?
function M.lookup(name)
return require("lz.n.handler.extra").lookup(pending, name)
return state.lookup_plugin(name)
end
-- Get all augroups for an event
@@ -89,7 +89,7 @@ local event_triggers = {
---@return lz.n.EventOpts[]
local function get_state(event, buf, data)
---@type lz.n.EventOpts[]
local state = {}
local st = {}
while event do
---@type lz.n.EventOpts
local event_opts = {
@@ -98,11 +98,11 @@ local function get_state(event, buf, data)
buffer = buf,
data = data,
}
table.insert(state, 1, event_opts)
table.insert(st, 1, event_opts)
data = nil -- only pass the data to the first event
event = event_triggers[event]
end
return state
return st
end
-- Trigger an event
@@ -152,24 +152,15 @@ local function add_event(event)
once = true,
pattern = event.pattern,
callback = function(ev)
if done or not pending[event.id] then
if done or not state.has_pending_plugins(event.id) then
return
end
-- HACK: work-around for https://github.com/neovim/neovim/issues/25526
done = true
local state = get_state(ev.event, ev.buf, ev.data)
-- Make sure trigger_load calls in before hooks can't interfere with the state,
-- but they can load a plugin before it's loaded by this handler
vim
.iter(vim.deepcopy(pending[event.id]))
---@param plugin lz.n.Plugin
:each(function(_, plugin)
if pending[event.id][plugin.name] then
loader.load(plugin)
end
end)
local st = get_state(ev.event, ev.buf, ev.data)
state.each_pending(event.id, loader.load)
---@param s lz.n.EventOpts
vim.iter(state):each(function(s)
vim.iter(st):each(function(s)
trigger(s)
end)
end,
@@ -180,17 +171,14 @@ end
function M.add(plugin)
---@param event lz.n.Event
vim.iter(plugin.event or {}):each(function(event)
pending[event.id] = pending[event.id] or {}
pending[event.id][plugin.name] = plugin
state.insert(event.id, plugin)
add_event(event)
end)
end
---@param name string
function M.del(name)
vim.iter(pending):each(function(_, plugins)
plugins[name] = nil
end)
state.del(name)
end
return M
-23
View File
@@ -1,23 +0,0 @@
---@mod lz.n.handler.extra Helper functions for use by handlers
local M = {}
---Look up a plugin in a plugin table, commonly used by handlers to
---keep track of plugins they manage
---@param plugin_tbl table<unknown, table<string, lz.n.Plugin>>
---@param name string
---@return lz.n.Plugin?
function M.lookup(plugin_tbl, name)
return vim
.iter(plugin_tbl)
---@param plugins table<string, lz.n.Plugin>
:map(function(_, plugins)
return plugins[name]
end)
---@param plugin lz.n.Plugin?
:find(function(plugin)
return plugin ~= nil
end)
end
return M
+7 -19
View File
@@ -23,8 +23,8 @@ local function parse(value, mode)
return ret
end
---@type table<string, table<string, lz.n.Plugin[]>>
local pending = {}
---@type lz.n.handler.State
local state = require("lz.n.handler.state").new()
---@type lz.n.KeysHandler
local M = {
@@ -48,7 +48,7 @@ local M = {
---@param name string
---@return lz.n.Plugin?
function M.lookup(name)
return require("lz.n.handler.extra").lookup(pending, name)
return state.lookup_plugin(name)
end
local skip = { mode = true, id = true, ft = true, rhs = true, lhs = true }
@@ -103,16 +103,7 @@ local function add_keys(keys)
vim.keymap.set(keys.mode, lhs, function()
-- always delete the mapping immediately to prevent recursive mappings
del(keys)
-- Make sure trigger_load calls in before hooks can't interfere with the state,
-- but they can load a plugin before it's loaded by this handler
vim
.iter(vim.deepcopy(pending[keys.id]))
---@param plugin lz.n.Plugin
:each(function(_, plugin)
if pending[keys.id][plugin.name] then
loader.load(plugin)
end
end)
state.each_pending(keys.id, loader.load)
-- Create the real buffer-local mapping
if keys.ft then
set(keys, buf)
@@ -136,7 +127,7 @@ local function add_keys(keys)
vim.api.nvim_create_autocmd("FileType", {
pattern = keys.ft,
callback = function(event)
if pending[keys.id] then
if state.has_pending_plugins[keys.id] then
add(event.buf)
else
-- Only create the mapping if its managed by lz.n
@@ -154,17 +145,14 @@ end
function M.add(plugin)
---@param key lz.n.Keys
vim.iter(plugin.keys or {}):each(function(key)
pending[key.id] = pending[key.id] or {}
pending[key.id][plugin.name] = plugin
state.insert(key.id, plugin)
add_keys(key)
end)
end
---@param name string
function M.del(name)
vim.iter(pending):each(function(_, plugins)
plugins[name] = nil
end)
state.del(name)
end
return M
+89
View File
@@ -0,0 +1,89 @@
---@mod lz.n.handler.state Safe state management for handlers
---
---@brief [[
---This module is to be used by |lz.n.Handler| implementations.
---It provides an API for safely managing handler state,
---ensuring that `trigger_load` can be called in plugin hooks.
---@brief ]]
local state = {}
---@return lz.n.handler.State
function state.new()
---@type table<string, table<string, lz.n.Plugin>>
local pending = {}
---@type lz.n.handler.State
return {
insert = function(key, plugin)
pending[key] = pending[key] or {}
pending[key][plugin.name] = plugin
end,
del = function(plugin_name, callback)
vim.iter(pending)
:filter(function(_, plugins)
return plugins[plugin_name] ~= nil
end)
:each(
---@param key string
---@param plugins lz.n.Plugin[]
function(key, plugins)
if callback then
callback(key)
end
plugins[plugin_name] = nil
end
)
end,
has_pending_plugins = function(key)
return pending[key] ~= nil and not vim.tbl_isempty(pending[key])
end,
lookup_plugin = function(plugin_name)
return vim
.iter(pending)
---@param plugins table<string, lz.n.Plugin>
:map(function(_, plugins)
return plugins[plugin_name]
end)
---@param plugin lz.n.Plugin?
:find(function(plugin)
return plugin ~= nil
end)
end,
each_pending = function(key, callback)
local plugins = pending[key] or {}
vim
.iter(vim.deepcopy(plugins))
---@param plugin lz.n.Plugin
:each(function(_, plugin)
if pending[key][plugin.name] then
callback(plugin)
end
end)
return vim.tbl_keys(plugins)
end,
}
end
---@class lz.n.handler.State
---
---Insert a plugin by key.
---@field insert fun(key: string, plugin: lz.n.Plugin)
---
---Remove a plugin by its name.
---@field del fun(plugin_name: string, callback?: fun(key: string))
---
---Check if there are pending plugins for a key
---@field has_pending_plugins fun(key: string):boolean
---
---Lookup a plugin by its name.
---@field lookup_plugin fun(plugin_name: string):lz.n.Plugin?
---
---Safely apply a callback to all pending plugins by key.
---@field each_pending fun(key: string, callback: fun(plugin: lz.n.Plugin)): string[]
return state