Skip to content

Commit

Permalink
draft: initial implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
mrcjkb committed Apr 11, 2024
1 parent ac72bb5 commit c5d2be3
Show file tree
Hide file tree
Showing 5 changed files with 277 additions and 0 deletions.
28 changes: 28 additions & 0 deletions lua/lz/n/init.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
---@mod lz.n

local M = {}

-- TODO: Is this necessary?
if not vim.loader or vim.fn.has('nvim-0.9.1') ~= 1 then
error('lz.n requires Neovim >= 0.9.1')
end

---@param spec string | LzSpec
function M.load(spec)
if vim.g.lzn_did_load then
return vim.notify('lz.n has already loaded your plugins.', vim.log.levels.WARN, { title = 'lz.n' })
end
vim.g.lzn_did_load = true

if type(spec) == 'string' then
spec = { import = spec }
end
---@cast spec LzSpec
local plugins = require('lz.n.spec').parse(spec)
require('lz.n.loader').load_startup_plugins(plugins)
-- DONE: Plugin.load()
-- TODO: Handler.init()
-- TODO: Handler.setup()
end

return M
86 changes: 86 additions & 0 deletions lua/lz/n/loader.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
---@mod lz.n.loader

local state = require('lz.n.state')

local M = {}

local DEFAULT_PRIORITY = 50

---@package
---@param plugin LzPlugin
function M._load(plugin)
if plugin.enable == false or (type(plugin.enable) == 'function' and not plugin.enable()) then
return
end
-- TODO: Load plugin
end

---@param plugins table<string, LzPlugin>
local function run_before_all(plugins)
for _, plugin in pairs(plugins) do
if plugin.beforeAll then
local ok, err = pcall(plugin.beforeAll, plugin)
if not ok then
vim.schedule(function()
vim.notify(
"Failed to run 'beforeAll' for " .. plugin.name .. ': ' .. tostring(err or ''),
vim.log.levels.ERROR
)
end)
end
end
end
end

---@param plugins table<string, LzPlugin>
local function get_eager_plugins(plugins)
local result = {}
for _, plugin in pairs(plugins) do
if plugin.lazy == false then
table.insert(result, plugin)
end
end
table.sort(result, function(a, b)
---@cast a LzPlugin
---@cast b LzPlugin
return (a.priority or DEFAULT_PRIORITY) > (b.priority or DEFAULT_PRIORITY)
end)
return result
end

---@param plugins table<string, LzPlugin>
function M.load_startup_plugins(plugins)
run_before_all(plugins)
for _, plugin in pairs(get_eager_plugins(plugins)) do
if not plugin._.loaded then
M.load(plugin)
end
end
end

---@param plugins string | LzPlugin | string[] | LzPlugin[]
function M.load(plugins)
plugins = (type(plugins) == 'string' or plugins.name) and { plugins } or plugins
---@cast plugins (string|LzPlugin)[]
for _, plugin in pairs(plugins) do
local loadable = true
if type(plugin) == 'string' then
if state.plugins[plugin] then
plugin = state.plugins[plugin]
-- TODO:?
-- elseif state.spec.disabled[plugin] then
-- plugin = nil
-- loadable = false
else
vim.notify('Plugin ' .. plugin .. ' not found', vim.log.levels.ERROR, { title = 'lz.n' })
loadable = false
end
---@cast plugin LzPlugin
end
if loadable then
M._load(plugin)
end
end
end

return M
78 changes: 78 additions & 0 deletions lua/lz/n/spec.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
local M = {}

---@param spec LzSpecImport
---@param result table<string, LzPlugin>
local function import_spec(spec, result)
if spec.import == 'lz.n' then
vim.schedule(function()
vim.notify("Plugins modules cannot be called 'lz.n'", vim.log.levels.ERROR)
end)
return
end
if type(spec.import) ~= 'string' then
vim.schedule(function()
vim.notify(
"Invalid import spec. The 'import' field should be a module name: " .. vim.inspect(spec),
vim.log.levels.ERROR
)
end)
return
end
if spec.cond == false or (type(spec.cond) == 'function' and not spec.cond()) then
return
end
if spec.enabled == false or (type(spec.enabled) == 'function' and not spec.enabled()) then
return
end
local modname = 'plugin.' .. spec.import
local ok, mod = pcall(require, modname)
if not ok then
vim.schedule(function()
local err = type(mod) == 'string' and ': ' .. mod or ''
vim.notify("Failed to load module '" .. modname .. err, vim.log.levels.ERROR)
end)
return
end
if type(mod) ~= table then
vim.schedule(function()
vim.notify("Invalid plugin spec module '" .. modname .. "' of type '" .. type(mod) .. "'", vim.log.levels.ERROR)
end)
return
end
M._normalize(mod, result)
end

---@private
---@param spec LzSpec
---@param result table<string, LzPlugin>
function M._normalize(spec, result)
if #spec > 1 or vim.tbl_islist(spec) then
for _, sp in ipairs(spec) do
M._normalize(sp, result)
end
elseif spec.import then
---@cast spec LzSpecImport
import_spec(spec, result)
end
end

---@param result table<string, LzPlugin>
local function remove_disabled_plugins(result)
for _, plugin in ipairs(result) do
local disabled = plugin.enabled == false or (type(plugin.enabled) == 'function' and not plugin.enabled())
if disabled then
result[plugin.name] = nil
end
end
end

---@param spec LzSpec
---@return table<string, LzPlugin>
function M.parse(spec)
local result = {}
M._normalize(spec, result)
remove_disabled_plugins(result)
return result
end

return M
8 changes: 8 additions & 0 deletions lua/lz/n/state.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---@mod lz.n.state

local M = {}

---@type table<string, LzPlugin>
M.plugins = {}

return M
77 changes: 77 additions & 0 deletions lua/lz/n/types.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
---@meta
error('Cannot import a meta module')

---@class VimGTable vim.g config table
---@field name? string Name of the vim.g config table, e.g. "rustaceanvim" for "vim.g.rustaceanvim". Defaults to the plugin name.
---@field type 'vim.g'

---@class ConfigFunction Lua function
---@field module? string Module name containing the function. Defaults to the plugin name.
---@field name? string Name of the config function. Defaults to 'setup', the most common in the Neovim plugin community.
---@field type 'func'

---@alias LzPluginOptsSpec VimGTable | ConfigFunction How a plugin accepts its options

---@class LzPluginBase
---@field name string Display name and name used for plugin config files, e.g. "neorg"
---@field optsSpec? LzPluginOptsSpec
---@field enabled? boolean|(fun():boolean)
---@field enable? boolean|(fun():boolean) Whether to enable this plugin. Useful to disable plugins under certain conditions.
---@field lazy? boolean
---@field priority? number Only useful for lazy=false plugins to force loading certain plugins first. Default priority is 50

---@alias LzEvent {id:string, event:string[]|string, pattern?:string[]|string}
---@alias LzEventSpec string|{event?:string|string[], pattern?:string|string[]}|string[]

---@alias PluginOpts table|fun(self:LzPlugin, opts:table):table?

---@class LzPluginHooks
---@field beforeAll? fun(self:LzPlugin) Will be run before loading any plugins
---@field deactivate? fun(self:LzPlugin) Unload/Stop a plugin
---@field after? fun(self:LzPlugin, opts:table)|true Will be executed when loading the plugin
---@field opts? PluginOpts

---@class LzPluginHandlers
---@field event? table<string,LzEvent>
---@field ft? table<string,LzEvent>
---@field keys? table<string,LzKeys>
---@field cmd? table<string,string>

---@class LzPluginSpecHandlers
---@field event? string[]|string|LzEventSpec[]|fun(self:LzPlugin, event:string[]):string[]
---@field cmd? string[]|string|fun(self:LzPlugin, cmd:string[]):string[]
---@field ft? string[]|string|fun(self:LzPlugin, ft:string[]):string[]
---@field keys? string|string[]|LzKeysSpec[]|fun(self:LzPlugin, keys:string[]):(string|LzKeys)[]
---@field module? false

---@class LzKeysBase
---@field desc? string
---@field noremap? boolean
---@field remap? boolean
---@field expr? boolean
---@field nowait? boolean
---@field ft? string|string[]

---@class LzKeysSpec: LzKeysBase
---@field [1] string lhs
---@field [2]? string|fun()|false rhs
---@field mode? string|string[]

---@class LzKeys: LzKeysBase
---@field lhs string lhs
---@field rhs? string|fun() rhs
---@field mode? string
---@field id string
---@field name string

---@package
---@class LzPlugin: LzPluginBase,LzPluginHandlers,LzPluginHooks

---@class LzPluginSpec: LzPluginBase,LzPluginSpecHandlers,LzPluginHooks

---@alias LzSpec LzPluginSpec|LzSpecImport|LzSpec[]

---@class LzSpecImport
---@field import string spec module to import
---@field enabled? boolean|(fun():boolean)
---@field cond? boolean|(fun():boolean)

0 comments on commit c5d2be3

Please sign in to comment.