draft: initial implementation

This commit is contained in:
Marc Jakobi
2024-04-11 20:46:06 +02:00
parent ac72bb5c76
commit e61adde6ce
15 changed files with 904 additions and 102 deletions
+1
View File
@@ -1,3 +1,4 @@
result
.pre-commit-config.yaml
.direnv
.luarc.json
Generated
+79 -24
View File
@@ -66,6 +66,24 @@
"type": "github"
}
},
"flake-parts_2": {
"inputs": {
"nixpkgs-lib": "nixpkgs-lib_2"
},
"locked": {
"lastModified": 1706830856,
"narHash": "sha256-a0NYyp+h9hlb7ddVz4LUn1vT/PLwqfrWYcHMvFB1xYg=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "b253292d9c0a5ead9bc98c4e9a26c6312e27d69f",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "flake-parts",
"type": "github"
}
},
"flake-utils": {
"inputs": {
"systems": "systems"
@@ -138,6 +156,25 @@
"type": "github"
}
},
"gen-luarc": {
"inputs": {
"flake-parts": "flake-parts_2",
"nixpkgs": "nixpkgs"
},
"locked": {
"lastModified": 1710933866,
"narHash": "sha256-GtYTuxY6AdFxl3uwFkTkqpvOP4lQLzu2YwqnejhDs1Q=",
"owner": "mrcjkb",
"repo": "nix-gen-luarc-json",
"rev": "6e8912ea4fbfaa10797caafb1f5628fb4178b6e8",
"type": "github"
},
"original": {
"owner": "mrcjkb",
"repo": "nix-gen-luarc-json",
"type": "github"
}
},
"gitignore": {
"inputs": {
"nixpkgs": [
@@ -181,28 +218,12 @@
"type": "github"
}
},
"neodev-nvim": {
"flake": false,
"locked": {
"lastModified": 1704434754,
"narHash": "sha256-guSJ809CDvzbnPyYG8ELr7LEkuX833m5TK+U0hl0hbc=",
"owner": "folke",
"repo": "neodev.nvim",
"rev": "be8d4d4cab6c13c6a572269c9d6a63774baba9a0",
"type": "github"
},
"original": {
"owner": "folke",
"repo": "neodev.nvim",
"type": "github"
}
},
"neorocks": {
"inputs": {
"flake-compat": "flake-compat",
"flake-utils": "flake-utils",
"neovim-nightly": "neovim-nightly",
"nixpkgs": "nixpkgs",
"nixpkgs": "nixpkgs_2",
"pre-commit-hooks": "pre-commit-hooks"
},
"locked": {
@@ -245,16 +266,16 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1704161960,
"narHash": "sha256-QGua89Pmq+FBAro8NriTuoO/wNaUtugt29/qqA8zeeM=",
"lastModified": 1708475490,
"narHash": "sha256-g1v0TsWBQPX97ziznfJdWhgMyMGtoBFs102xSYO4syU=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "63143ac2c9186be6d9da6035fa22620018c85932",
"rev": "0e74ca98a74bc7270d28838369593635a5db3260",
"type": "github"
},
"original": {
"owner": "nixos",
"ref": "nixpkgs-unstable",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
@@ -277,6 +298,24 @@
"type": "github"
}
},
"nixpkgs-lib_2": {
"locked": {
"dir": "lib",
"lastModified": 1706550542,
"narHash": "sha256-UcsnCG6wx++23yeER4Hg18CXWbgNpqNXcHIo5/1Y+hc=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "97b17f32362e475016f942bbdfda4a4a72a8a652",
"type": "github"
},
"original": {
"dir": "lib",
"owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs-stable": {
"locked": {
"lastModified": 1685801374,
@@ -326,6 +365,22 @@
}
},
"nixpkgs_3": {
"locked": {
"lastModified": 1704161960,
"narHash": "sha256-QGua89Pmq+FBAro8NriTuoO/wNaUtugt29/qqA8zeeM=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "63143ac2c9186be6d9da6035fa22620018c85932",
"type": "github"
},
"original": {
"owner": "nixos",
"ref": "nixpkgs-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs_4": {
"locked": {
"lastModified": 1689261696,
"narHash": "sha256-LzfUtFs9MQRvIoQ3MfgSuipBVMXslMPH/vZ+nM40LkA=",
@@ -371,7 +426,7 @@
"flake-compat": "flake-compat_3",
"flake-utils": "flake-utils_4",
"gitignore": "gitignore_2",
"nixpkgs": "nixpkgs_3",
"nixpkgs": "nixpkgs_4",
"nixpkgs-stable": "nixpkgs-stable_2"
},
"locked": {
@@ -391,9 +446,9 @@
"root": {
"inputs": {
"flake-parts": "flake-parts",
"neodev-nvim": "neodev-nvim",
"gen-luarc": "gen-luarc",
"neorocks": "neorocks",
"nixpkgs": "nixpkgs_2",
"nixpkgs": "nixpkgs_3",
"pre-commit-hooks": "pre-commit-hooks_2"
}
},
+21 -68
View File
@@ -10,14 +10,9 @@
url = "github:cachix/pre-commit-hooks.nix";
};
neorocks = {
url = "github:nvim-neorocks/neorocks";
};
neorocks.url = "github:nvim-neorocks/neorocks";
neodev-nvim = {
url = "github:folke/neodev.nvim";
flake = false;
};
gen-luarc.url = "github:mrcjkb/nix-gen-luarc-json";
};
outputs = inputs @ {
@@ -26,7 +21,7 @@
flake-parts,
pre-commit-hooks,
neorocks,
neodev-nvim,
gen-luarc,
...
}: let
name = "lz.n";
@@ -49,77 +44,33 @@
...
}: let
ci-overlay = import ./nix/ci-overlay.nix {
inherit
self
neodev-nvim
;
inherit self;
plugin-name = name;
};
pkgs = import nixpkgs {
inherit system;
overlays = [
ci-overlay
gen-luarc.overlays.default
neorocks.overlays.default
ci-overlay
plugin-overlay
];
};
mkTypeCheck = {
nvim-api ? [],
disabled-diagnostics ? [],
}:
pre-commit-hooks.lib.${system}.run {
src = self;
hooks = {
lua-ls.enable = true;
};
settings = {
lua-ls = {
config = {
runtime.version = "LuaJIT";
Lua = {
workspace = {
library =
nvim-api
++ [
"\${3rd}/busted/library"
"\${3rd}/luassert/library"
];
ignoreDir = [
".git"
".github"
".direnv"
"result"
"nix"
"doc"
];
};
diagnostics = {
libraryFiles = "Disable";
disable = disabled-diagnostics;
};
};
};
};
};
};
type-check-stable = mkTypeCheck {
nvim-api = [
"${pkgs.neovim}/share/nvim/runtime/lua"
"${pkgs.neodev-plugin}/types/stable"
];
disabled-diagnostics = [
# For compatibility with nightly, some diagnostics may have to be disabled here.
];
luarc = pkgs.mk-luarc {
nvim = pkgs.neovim-nightly;
neodev-types = "nightly";
};
type-check-nightly = mkTypeCheck {
nvim-api = [
"${pkgs.neovim-nightly}/share/nvim/runtime/lua"
"${pkgs.neodev-plugin}/types/nightly"
];
type-check-nightly = pre-commit-hooks.lib.${system}.run {
src = self;
hooks = {
lua-ls.enable = true;
};
settings = {
lua-ls.config = luarc;
};
};
pre-commit-check = pre-commit-hooks.lib.${system}.run {
@@ -135,7 +86,10 @@
devShell = pkgs.nvim-nightly-tests.overrideAttrs (oa: {
name = "lz.n devShell";
inherit (pre-commit-check) shellHook;
shellHook = ''
${pre-commit-check.shellHook}
ln -fs ${pkgs.luarc-to-json luarc} .luarc.json
'';
buildInputs = with pre-commit-hooks.packages.${system};
[
alejandra
@@ -162,7 +116,6 @@
checks = {
inherit
pre-commit-check
type-check-stable
type-check-nightly
;
inherit
+88
View File
@@ -0,0 +1,88 @@
local loader = require('lz.n.loader')
---@class LzCmdHandler: LzHandler
---@type LzCmdHandler
local M = {
active = {},
managed = {},
type = 'cmd',
}
---@param cmd string
local function load(cmd)
vim.api.nvim_del_user_command(cmd)
loader.load(M.active[cmd])
end
---@param cmd string
local function add(cmd)
vim.api.nvim_create_user_command(cmd, function(event)
---@cast event vim.api.keyset.user_command
local command = {
cmd = cmd,
bang = event.bang or nil,
---@diagnostic disable-next-line: undefined-field
mods = event.smods,
---@diagnostic disable-next-line: undefined-field
args = event.fargs,
count = event.count >= 0 and event.range == 0 and event.count or nil,
}
if event.range == 1 then
---@diagnostic disable-next-line: undefined-field
command.range = { event.line1 }
elseif event.range == 2 then
---@diagnostic disable-next-line: undefined-field
command.range = { event.line1, event.line2 }
end
---@type string
local plugins = '`' .. table.concat(vim.tbl_values(M.active[cmd]), ', ') .. '`'
load(cmd)
local info = vim.api.nvim_get_commands({})[cmd] or vim.api.nvim_buf_get_commands(0, {})[cmd]
if not info then
vim.schedule(function()
vim.notify('Command `' .. cmd .. '` not found after loading ' .. plugins, vim.log.levels.ERROR)
end)
return
end
command.nargs = info.nargs
---@diagnostic disable-next-line: undefined-field
if event.args and event.args ~= '' and info.nargs and info.nargs:find('[1?]') then
---@diagnostic disable-next-line: undefined-field
command.args = { event.args }
end
vim.cmd(command)
end, {
bang = true,
range = true,
nargs = '*',
complete = function(_, line)
load(cmd)
return vim.fn.getcompletion(line, 'cmdline')
end,
})
end
---@param cmd string
function M.del(cmd)
pcall(vim.api.nvim_del_user_command, cmd)
end
---@param plugin LzPlugin
function M.add(plugin)
if not plugin.cmd then
return
end
for _, cmd in pairs(plugin.cmd) do
M.active[cmd] = M.active[cmd] or {}
M.active[cmd][plugin.name] = plugin.name
add(cmd)
end
end
return M
+147
View File
@@ -0,0 +1,147 @@
local loader = require('lz.n.loader')
---@class LzEventOpts
---@field event string
---@field group? string
---@field exclude? string[] augroups to exclude
---@field data? unknown
---@field buffer? number
---@class LzEventHandler: LzHandler
---@field events table<string,true>
---@field group number
---@type LzEventHandler
local M = {
active = {},
managed = {},
type = 'event',
}
---@param spec LzEventSpec
---@return LzEvent
function M.parse(spec)
local ret
if type(spec) == 'string' then
local event, pattern = spec:match('^(%w+)%s+(.*)$')
event = event or spec
return { id = spec, event = event, pattern = pattern }
elseif vim.tbl_islist(spec) then
ret = { id = table.concat(spec, '|'), event = spec }
else
ret = spec --[[@as LzEvent]]
if not ret.id then
---@diagnostic disable-next-line: assign-type-mismatch, param-type-mismatch
ret.id = type(ret.event) == 'string' and ret.event or table.concat(ret.event, '|')
if ret.pattern then
---@diagnostic disable-next-line: assign-type-mismatch, param-type-mismatch
ret.id = ret.id .. ' ' .. (type(ret.pattern) == 'string' and ret.pattern or table.concat(ret.pattern, ', '))
end
end
end
return ret
end
-- Get all augroups for an event
---@param event string
local function get_augroups(event)
---@type string[]
local groups = {}
for _, autocmd in ipairs(vim.api.nvim_get_autocmds { event = event }) do
if autocmd.group_name then
table.insert(groups, autocmd.group_name)
end
end
return groups
end
local event_triggers = {
FileType = 'BufReadPost',
BufReadPost = 'BufReadPre',
}
-- Get the current state of the event and all the events that will be fired
---@param event string
---@param buf integer
---@param data unknown
---@return LzEventOpts[]
local function get_state(event, buf, data)
---@type LzEventOpts[]
local state = {}
while event do
---@type LzEventOpts
local event_opts = {
event = event,
exclude = event ~= 'FileType' and get_augroups(event) or nil,
buffer = buf,
data = data,
}
table.insert(state, 1, event_opts)
data = nil -- only pass the data to the first event
event = event_triggers[event]
end
return state
end
-- Trigger an event
---@param opts LzEventOpts
local function _trigger(opts)
xpcall(
function()
vim.api.nvim_exec_autocmds(opts.event, {
buffer = opts.buffer,
group = opts.group,
modeline = false,
data = opts.data,
})
end,
vim.schedule_wrap(function(err)
vim.notify(err, vim.log.levels.ERROR)
end)
)
end
-- Trigger an event. When a group is given, only the events in that group will be triggered.
-- When exclude is set, the events in those groups will be skipped.
---@param opts LzEventOpts
local function trigger(opts)
if opts.group or opts.exclude == nil then
return _trigger(opts)
end
---@type table<string,true>
local done = {}
for _, autocmd in ipairs(vim.api.nvim_get_autocmds { event = opts.event }) do
local id = autocmd.event .. ':' .. (autocmd.group or '') ---@type string
local skip = done[id] or (opts.exclude and vim.tbl_contains(opts.exclude, autocmd.group_name))
done[id] = true
if autocmd.group and not skip then
opts.group = autocmd.group_name
_trigger(opts)
end
end
end
---@param event LzEvent
function M.add(event)
local done = false
vim.api.nvim_create_autocmd(event.event, {
group = M.group,
once = true,
pattern = event.pattern,
callback = function(ev)
if done or not M.active[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)
-- load the plugins
loader.load(M.active[event.id])
-- check if any plugin created an event handler for this event and fire the group
for _, s in ipairs(state) do
trigger(s)
end
end,
})
end
return M
+27
View File
@@ -0,0 +1,27 @@
local event = require('lz.n.handler.event')
---@class LzFtHandler: LzHandler
---@type LzFtHandler
local M = {
active = {},
managed = {},
type = 'ft',
}
---@param value string
---@return LzEvent
function M.parse(value)
return {
id = value,
event = 'FileType',
pattern = value,
}
end
---@param lz_event LzEvent
function M.add(lz_event)
event.add(lz_event)
end
return M
+55
View File
@@ -0,0 +1,55 @@
---@class LzHandler
---@field type LzHandlerTypes
---@field active table<string,table<string,string>>
---@field managed table<string,string>
---@field add fun(plugin: LzPlugin)
---@field del? fun(plugin: LzPlugin)
local M = {}
---@enum LzHandlerTypes
M.types = {
cmd = 'cmd',
event = 'event',
ft = 'ft',
keys = 'keys',
}
local handlers = {
cmd = require('lz.n.handler.cmd'),
event = require('lz.n.handler.event'),
ft = require('lz.n.handler.ft'),
keys = require('lz.n.handler.keys'),
}
---@param plugin LzPlugin
local function enable(plugin)
for _, handler in pairs(handlers) do
handler.add(plugin)
end
-- TODO: Change handler add implementations to take a LzPlugin
end
function M.disable(plugin)
for _, handler in pairs(handlers) do
if type(handler.del) == 'function' then
-- TODO: Change handler del implementations to take a LzPlugin?
handler.del(plugin)
end
end
end
---@param plugins table<string, LzPlugin>
function M.init(plugins)
for _, plugin in pairs(plugins) do
xpcall(
enable,
vim.schedule_wrap(function(err)
vim.notify(('Failed to enable handlers for %s: %s'):format(plugin.name, err), vim.log.levels.ERROR)
end),
plugin
)
end
end
return M
+128
View File
@@ -0,0 +1,128 @@
local loader = require('lz.n.loader')
---@class LzKeysHandler: LzHandler
---@type LzKeysHandler
local M = {
active = {},
managed = {},
type = 'keys',
}
---@param value string|LzKeysSpec
---@param mode? string
---@return LzKeys
function M.parse(value, mode)
value = type(value) == 'string' and { value } or value --[[@as LzKeysSpec]]
local ret = vim.deepcopy(value) --[[@as LzKeys]]
ret.lhs = ret[1] or ''
ret.rhs = ret[2]
ret[1] = nil
ret[2] = nil
ret.mode = mode or 'n'
ret.id = vim.api.nvim_replace_termcodes(ret.lhs, true, true, true)
if ret.ft then
local ft = type(ret.ft) == 'string' and { ret.ft } or ret.ft --[[@as string[] ]]
ret.id = ret.id .. ' (' .. table.concat(ft, ', ') .. ')'
end
if ret.mode ~= 'n' then
ret.id = ret.id .. ' (' .. ret.mode .. ')'
end
return ret
end
local skip = { mode = true, id = true, ft = true, rhs = true, lhs = true }
---@param keys LzKeys
---@return LzKeysBase
local function get_opts(keys)
---@type LzKeysBase
local opts = {}
for k, v in pairs(keys) do
if type(k) ~= 'number' and not skip[k] then
opts[k] = v
end
end
return opts
end
-- Create a mapping if it is managed by lz.n
---@param keys LzKeys
---@param buf integer?
local function set(keys, buf)
if keys.rhs then
local opts = get_opts(keys)
opts.buffer = buf
vim.keymap.set(keys.mode, keys.lhs, keys.rhs, opts)
end
end
-- Delete a mapping and create the real global
-- mapping when needed
---@param keys LzKeys
local function del(keys)
pcall(vim.keymap.del, keys.mode, keys.lhs, {
-- NOTE: for buffer-local mappings, we only delete the mapping for the current buffer
-- So the mapping could still exist in other buffers
buffer = keys.ft and true or nil,
})
-- make sure to create global mappings when needed
-- buffer-local mappings are managed by lazy
if not keys.ft then
set(keys)
end
end
---@param keys LzKeys
M.add = function(keys)
local lhs = keys.lhs
local opts = get_opts(keys)
---@param buf? number
local function add(buf)
vim.keymap.set(keys.mode, lhs, function()
local plugins = M.active[keys.id]
-- always delete the mapping immediately to prevent recursive mappings
del(keys)
M.active[keys.id] = nil
if plugins then
loader.load(plugins)
end
-- Create the real buffer-local mapping
if keys.ft then
set(keys, buf)
end
if keys.mode:sub(-1) == 'a' then
lhs = lhs .. '<C-]>'
end
local feed = vim.api.nvim_replace_termcodes('<Ignore>' .. lhs, true, true, true)
-- insert instead of append the lhs
vim.api.nvim_feedkeys(feed, 'i', false)
end, {
desc = opts.desc,
nowait = opts.nowait,
-- we do not return anything, but this is still needed to make operator pending mappings work
expr = true,
buffer = buf,
})
end
-- buffer-local mappings
if keys.ft then
vim.api.nvim_create_autocmd('FileType', {
pattern = keys.ft,
callback = function(event)
if M.active[keys.id] then
add(event.buf)
else
-- Only create the mapping if its managed by lz.n
-- otherwise the plugin is supposed to manage it
set(keys, event.buf)
end
end,
})
else
add()
end
end
return M
+27
View File
@@ -0,0 +1,27 @@
---@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)
require('lz.n.state').plugins = plugins
require('lz.n.handler').init(plugins)
end
return M
+85
View File
@@ -0,0 +1,85 @@
---@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
require('lz.n.handler').disable(plugin)
-- 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
xpcall(
plugin.beforeAll,
vim.schedule_wrap(function(err)
vim.notify(
"Failed to run 'beforeAll' for " .. plugin.name .. ': ' .. tostring(err or ''),
vim.log.levels.ERROR
)
end),
plugin
)
end
end
end
---@param plugins table<string, LzPlugin>
---@return 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
--- Loads startup plugins, removing loaded plugins from the table
---@param plugins table<string, LzPlugin>
function M.load_startup_plugins(plugins)
run_before_all(plugins)
for _, plugin in pairs(get_eager_plugins(plugins)) do
M.load(plugin)
plugins[plugin.name] = nil
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]
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
+142
View File
@@ -0,0 +1,142 @@
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
---@param spec LzPluginSpec
---@return LzPlugin
local function parse(spec)
---@type LzPlugin
---@diagnostic disable-next-line: assign-type-mismatch
local result = vim.deepcopy(spec)
local event_spec = spec.event
if event_spec then
result.event = {}
end
if type(event_spec) == 'string' then
local event = require('lz.n.handler.event').parse(event_spec)
result.event[event.id] = event
elseif type(event_spec) == 'table' then
---@cast event_spec LzEventSpec[]
for _, _event_spec in pairs(event_spec) do
local event = require('lz.n.handler.event').parse(_event_spec)
result.ft[event.id] = event
end
end
local ft_spec = spec.ft
if ft_spec then
result.ft = {}
end
if type(ft_spec) == 'string' then
local ft = require('lz.n.handler.ft').parse(ft_spec)
result[ft.id] = ft
elseif type(ft_spec) == 'table' then
for _, _ft_spec in pairs(ft_spec) do
local ft = require('lz.n.handler.ft').parse(_ft_spec)
result.ft[ft.id] = ft
end
end
local keys_spec = spec.keys
if keys_spec then
result.keys = {}
end
if type(keys_spec) == 'string' then
local keys = require('lz.n.handler.keys').parse(keys_spec)
result.keys[keys.id] = keys
elseif type(keys_spec) == 'table' then
---@cast keys_spec string[] | LzKeysSpec[]
for _, _keys_spec in pairs(keys_spec) do
local keys = require('lz.n.handler.keys').parse(_keys_spec)
result.keys[keys.id] = keys
end
end
local cmd_spec = spec.cmd
if cmd_spec then
result.cmd = {}
end
if type(cmd_spec) == 'string' then
result.cmd[cmd_spec] = cmd_spec
elseif type(cmd_spec) == 'table' then
for _, _cmd_spec in pairs(cmd_spec) do
result.cmd[_cmd_spec] = _cmd_spec
end
end
return 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.name then
---@cast spec LzPluginSpec
result[spec.name] = parse(spec)
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
View File
@@ -0,0 +1,8 @@
---@mod lz.n.state
local M = {}
---@type table<string, LzPlugin>
M.plugins = {}
return M
+77
View File
@@ -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|LzEventSpec[]
---@field cmd? string[]|string
---@field ft? string[]|string
---@field keys? string|string[]|LzKeysSpec[]
---@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)
+1 -10
View File
@@ -1,18 +1,12 @@
# Add flake.nix test inputs as arguments here
{
self,
neodev-nvim,
plugin-name,
}: final: prev:
with final.lib;
with final.stdenv; let
nvim-nightly = final.neovim-nightly;
neodev-plugin = final.pkgs.vimUtils.buildVimPlugin {
name = "neodev.nvim";
src = neodev-nvim;
};
mkNeorocksTest = {
name,
nvim ? final.neovim-unwrapped,
@@ -63,8 +57,5 @@ in {
name = "neovim-nightly-tests";
nvim = nvim-nightly;
};
inherit
neodev-plugin
docgen
;
inherit docgen;
}
+18
View File
@@ -0,0 +1,18 @@
local keys = require('lz.n.handler.keys')
describe('keys', function()
it('parses ids correctly', function()
local tests = {
{ '<C-/>', '<c-/>', true },
{ '<C-h>', '<c-H>', true },
{ '<C-h>k', '<c-H>K', false },
}
for _, test in ipairs(tests) do
if test[3] then
assert.same(keys.parse(test[1]).id, keys.parse(test[2]).id)
else
assert.is_not.same(keys.parse(test[1]).id, keys.parse(test[2]).id)
end
end
end)
end)