mirror of
https://github.com/zoriya/lz.n.git
synced 2026-06-01 18:35:18 +00:00
draft: initial implementation
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
result
|
||||
.pre-commit-config.yaml
|
||||
.direnv
|
||||
.luarc.json
|
||||
|
||||
Generated
+79
-24
@@ -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"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -0,0 +1,8 @@
|
||||
---@mod lz.n.state
|
||||
|
||||
local M = {}
|
||||
|
||||
---@type table<string, LzPlugin>
|
||||
M.plugins = {}
|
||||
|
||||
return M
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
Reference in New Issue
Block a user