From 3732144c33511d87f778fe238159933c526c65a6 Mon Sep 17 00:00:00 2001 From: LeMarsu Date: Mon, 3 Jun 2024 02:55:50 +0200 Subject: [PATCH] refactor: deps are now loaded via a FSM --- lua/sloth-flake/dep.lua | 215 ++++++++++++++++-------------- lua/sloth-flake/state_machine.lua | 104 +++++++++++++++ 2 files changed, 219 insertions(+), 100 deletions(-) create mode 100644 lua/sloth-flake/state_machine.lua diff --git a/lua/sloth-flake/dep.lua b/lua/sloth-flake/dep.lua index e15cd11..1550cbb 100644 --- a/lua/sloth-flake/dep.lua +++ b/lua/sloth-flake/dep.lua @@ -1,17 +1,92 @@ local raw_deps = require 'sloth-flake.dependencies' +local state_machine = require 'sloth-flake.state_machine' local M = {} +local State = state_machine.build_states { + 'NotLoaded', + 'Shimed', + 'Inited', + 'Imported', + 'Loaded', +} + function M.new(values) - return setmetatable({ - priv = { - init = false, - import = false, - config = false, - shim = false, - }, + local self = { values = values, - }, { + } + self.sm = state_machine.build(State, { + enter = { + [State.Shimed] = function() + if self.cmd then + for _, cmd in ipairs(self.cmd) do + vim.api.nvim_create_user_command(cmd, self:lazy_load_cmd(cmd), { + desc = "Sloth-flake placeholder for plugin " .. self.name, + nargs = '*', + bang = true, + }) + end + end + + if self.ft then + local group_id = vim.api.nvim_create_augroup(self.augroup_name, { + clear = true, + }) + vim.api.nvim_create_autocmd('FileType', { + group = group_id, + pattern = self.ft, + callback = self:lazy_load_ft() + }) + end + end, + [State.Inited] = function() + for _, dep in ipairs(self.dependencies) do + dep:init() + end + + local init = self.values.init or function() end + init() + end, + [State.Imported] = function() + for _, dep in ipairs(self.dependencies) do + dep:import() + end + + if self.is_lazy then + vim.cmd("packadd " .. self.name) + end + end, + [State.Loaded] = function() + for _, dep in ipairs(self.dependencies) do + dep:config() + end + + local config = self.values.config or function() end + config() + end + }, + exit = { + [State.Shimed] = function() + if self.cmd then + for _, cmd in ipairs(self.cmd) do + vim.api.nvim_del_user_command(cmd) + end + end + + if self.ft then + vim.api.nvim_del_augroup_by_name(self.augroup_name) + end + end, + }, + events = { + shim = { from = State.NotLoaded, to = State.Shimed, }, + init = { from = { State.NotLoaded, State.Shimed }, to = State.Inited, }, + import = { from = State.Inited, to = State.Imported, }, + config = { from = State.Imported, to = State.Loaded, }, + }, + }) + + return setmetatable(self, { __index = function(self, k) local fn = M[k] if fn then @@ -23,7 +98,7 @@ function M.new(values) end end, __newindex = function(self, k, v) - + -- Ignore new values end }) end @@ -63,62 +138,47 @@ function M:get_is_lazy() return self.values.lazy or false end +function M:get_state() + return self.sm.state +end + function M:get_is_imported() - return self.priv.import + return self.state >= State.Imported end function M:get_is_loaded() - -- last step is config, so a plugin is loaded if its config has run - return self.priv.config -end - -local function load_fn(type) - local function fn(self) - if self.priv[type] then - return - end - self.priv[type] = true - - for _, dep in ipairs(self.dependencies) do - fn(dep) - end - - if self.values[type] ~= nil then - self.values[type]() - end - end - return fn -end - -M.init = load_fn('init') -M.config = load_fn('config') - -function M:import() - if self.is_imported then - return - end - self.priv.import = true - - for _, dep in ipairs(self.dependencies) do - dep:import() - end - - if self.is_lazy then - vim.cmd("packadd " .. self.name) - end -end - -function M:load() - self:unshim() - self:init() - self:import() - self:config() + return self.state >= State.Loaded end function M:get_augroup_name() return "Sloth-plugin-" .. self.name end +function M:shim() + return self.sm:shim() +end + +function M:init() + return self.sm:init() +end + +function M:import() + self:init() + return self.sm:import() +end + +function M:config() + self:init() + self:import() + return self.sm:config() +end + +function M:load() + self:init() + self:import() + return self:config() +end + function M:lazy_load_cmd(cmd) return function(param) self:load() @@ -136,51 +196,6 @@ function M:lazy_load_ft() end end -function M:shim() - if self.priv.shim then - return - end - self.priv.shim = true - - if self.cmd then - for _, cmd in ipairs(self.cmd) do - vim.api.nvim_create_user_command(cmd, self:lazy_load_cmd(cmd), { - desc = "Sloth-flake placeholder for plugin " .. self.name, - nargs = '*', - bang = true, - }) - end - end - - if self.ft then - local group_id = vim.api.nvim_create_augroup(self.augroup_name, { - clear = true, - }) - vim.api.nvim_create_autocmd('FileType', { - group = group_id, - pattern = self.ft, - callback = self:lazy_load_ft() - }) - end -end - -function M:unshim() - if not self.priv.shim then - return - end - self.priv.shim = nil - - if self.cmd then - for _, cmd in ipairs(self.cmd) do - vim.api.nvim_del_user_command(cmd) - end - end - - if self.ft then - vim.api.nvim_del_augroup_by_name(self.augroup_name) - end -end - local deps = {} for k, v in pairs(raw_deps) do deps[k] = M.new(v) diff --git a/lua/sloth-flake/state_machine.lua b/lua/sloth-flake/state_machine.lua new file mode 100644 index 0000000..3c9c98b --- /dev/null +++ b/lua/sloth-flake/state_machine.lua @@ -0,0 +1,104 @@ +local M = {} + +local stateMeta + +local function are_both_states(a, b) + return a.is_state and b.is_state +end + +stateMeta = { + __tostring = function(v) + return v.name + end, + __le = function(self, other) + if not are_both_states(self, other) then + return false + end + return self.idx <= other.idx + end, + __lt = function(self, other) + if not are_both_states(self, other) then + return false + end + return self.idx < other.idx + end, + __index = function(self, name) + if name == 'is_state' then + return true + end + end +} + +function M.build_states(defs) + local states = {} + for i, name in ipairs(defs) do + local state = setmetatable({ idx = i, name = name }, stateMeta) + states[i] = state + states[name] = state + end + return states +end + +local SM = {} + +local function empty_fn() +end + +function SM:run_enter_state(state) + local enter_fn = self.defs.enter and self.defs.enter[state] or empty_fn + enter_fn(self.state) +end + +function SM:run_exit_state(state) + local enter_fn = self.defs.exit and self.defs.exit[state] or empty_fn + enter_fn(self.state) +end + +local function wrap_state_array(val) + return val.is_state and { val } or val +end + +local function canonicalize_event(event) + return { + from = wrap_state_array(event.from), + to = event.to, + transition = event.transition or function() end, + } +end + +function M.build(states, defs) + local machine = { + state = states[1], + defs = defs, + } + local prototype = {} + + for name, infos in pairs(defs.events) do + local event = canonicalize_event(infos) + prototype[name] = function(self) + if not vim.list_contains(event.from, self.state) then + return false + end + local previous = self.state + self:run_exit_state(previous) + self.state = event.to + event.transition(previous, self.state) + self:run_enter_state(self.state) + return true + end + end + + local ret = setmetatable(machine, { __index = vim.tbl_extend('error', SM, prototype)}) + ret:run_enter_state(states[1]) + return ret +end + +local State = M.build_states { + 'NotLoaded', + 'Shimed', + 'Inited', + 'Imported', + 'Loaded', +} + +return M