From 303a3ed6a874bb5bdebf11ecdf99e1dfa3eed2c3 Mon Sep 17 00:00:00 2001 From: Folke Lemaitre Date: Wed, 11 Oct 2023 14:24:18 +0200 Subject: [PATCH] feat(event): added support for structured events (see readme on event) --- README.md | 58 +++++++++++------------ lua/lazy/core/handler/event.lua | 83 +++++++++++++++++++++------------ lua/lazy/types.lua | 2 +- 3 files changed, 82 insertions(+), 61 deletions(-) diff --git a/README.md b/README.md index e8d87c7..f17dd46 100644 --- a/README.md +++ b/README.md @@ -79,35 +79,35 @@ require("lazy").setup({ ## 🔌 Plugin Spec -| Property | Type | Description | -| ---------------- | ------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **[1]** | `string?` | Short plugin url. Will be expanded using `config.git.url_format` | -| **dir** | `string?` | A directory pointing to a local plugin | -| **url** | `string?` | A custom git url where the plugin is hosted | -| **name** | `string?` | A custom name for the plugin used for the local plugin directory and as the display name | -| **dev** | `boolean?` | When `true`, a local plugin directory will be used instead. See `config.dev` | -| **lazy** | `boolean?` | When `true`, the plugin will only be loaded when needed. Lazy-loaded plugins are automatically loaded when their Lua modules are `required`, or when one of the lazy-loading handlers triggers | -| **enabled** | `boolean?` or `fun():boolean` | When `false`, or if the `function` returns false, then this plugin will not be included in the spec | -| **cond** | `boolean?` or `fun(LazyPlugin):boolean` | When `false`, or if the `function` returns false, then this plugin will not be loaded. Useful to disable some plugins in vscode, or firenvim for example. | -| **dependencies** | `LazySpec[]` | A list of plugin names or plugin specs that should be loaded when the plugin loads. Dependencies are always lazy-loaded unless specified otherwise. When specifying a name, make sure the plugin spec has been defined somewhere else. | -| **init** | `fun(LazyPlugin)` | `init` functions are always executed during startup | -| **opts** | `table` or `fun(LazyPlugin, opts:table)` | `opts` should be a table (will be merged with parent specs), return a table (replaces parent specs) or should change a table. The table will be passed to the `Plugin.config()` function. Setting this value will imply `Plugin.config()` | -| **config** | `fun(LazyPlugin, opts:table)` or `true` | `config` is executed when the plugin loads. The default implementation will automatically run `require(MAIN).setup(opts)`. Lazy uses several heuristics to determine the plugin's `MAIN` module automatically based on the plugin's **name**. See also `opts`. To use the default implementation without `opts` set `config` to `true`. | -| **main** | `string?` | You can specify the `main` module to use for `config()` and `opts()`, in case it can not be determined automatically. See `config()` | -| **build** | `fun(LazyPlugin)` or `string` or a list of build commands | `build` is executed when a plugin is installed or updated. Before running `build`, a plugin is first loaded. If it's a string it will be ran as a shell command. When prefixed with `:` it is a Neovim command. You can also specify a list to executed multiple build commands. Some plugins provide their own `build.lua` which is automatically used by lazy. So no need to specify a build step for those plugins. | -| **branch** | `string?` | Branch of the repository | -| **tag** | `string?` | Tag of the repository | -| **commit** | `string?` | Commit of the repository | -| **version** | `string?` or `false` to override the default | Version to use from the repository. Full [Semver](https://devhints.io/semver) ranges are supported | -| **pin** | `boolean?` | When `true`, this plugin will not be included in updates | -| **submodules** | `boolean?` | When false, git submodules will not be fetched. Defaults to `true` | -| **event** | `string?` or `string[]` or `fun(self:LazyPlugin, event:string[]):string[]` | Lazy-load on event. Events can be specified as `BufEnter` or with a pattern like `BufEnter *.lua` | -| **cmd** | `string?` or `string[]` or `fun(self:LazyPlugin, cmd:string[]):string[]` | Lazy-load on command | -| **ft** | `string?` or `string[]` or `fun(self:LazyPlugin, ft:string[]):string[]` | Lazy-load on filetype | -| **keys** | `string?` or `string[]` or `LazyKeys[]` or `fun(self:LazyPlugin, keys:string[]):(string \| LazyKeys)[]` | Lazy-load on key mapping | -| **module** | `false?` | Do not automatically load this Lua module when it's required somewhere | -| **priority** | `number?` | Only useful for **start** plugins (`lazy=false`) to force loading certain plugins first. Default priority is `50`. It's recommended to set this to a high number for colorschemes. | -| **optional** | `boolean?` | When a spec is tagged optional, it will only be included in the final spec, when the same plugin has been specified at least once somewhere else without `optional`. This is mainly useful for Neovim distros, to allow setting options on plugins that may/may not be part of the user's plugins | +| Property | Type | Description | +| ---------------- | ----------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **[1]** | `string?` | Short plugin url. Will be expanded using `config.git.url_format` | +| **dir** | `string?` | A directory pointing to a local plugin | +| **url** | `string?` | A custom git url where the plugin is hosted | +| **name** | `string?` | A custom name for the plugin used for the local plugin directory and as the display name | +| **dev** | `boolean?` | When `true`, a local plugin directory will be used instead. See `config.dev` | +| **lazy** | `boolean?` | When `true`, the plugin will only be loaded when needed. Lazy-loaded plugins are automatically loaded when their Lua modules are `required`, or when one of the lazy-loading handlers triggers | +| **enabled** | `boolean?` or `fun():boolean` | When `false`, or if the `function` returns false, then this plugin will not be included in the spec | +| **cond** | `boolean?` or `fun(LazyPlugin):boolean` | When `false`, or if the `function` returns false, then this plugin will not be loaded. Useful to disable some plugins in vscode, or firenvim for example. | +| **dependencies** | `LazySpec[]` | A list of plugin names or plugin specs that should be loaded when the plugin loads. Dependencies are always lazy-loaded unless specified otherwise. When specifying a name, make sure the plugin spec has been defined somewhere else. | +| **init** | `fun(LazyPlugin)` | `init` functions are always executed during startup | +| **opts** | `table` or `fun(LazyPlugin, opts:table)` | `opts` should be a table (will be merged with parent specs), return a table (replaces parent specs) or should change a table. The table will be passed to the `Plugin.config()` function. Setting this value will imply `Plugin.config()` | +| **config** | `fun(LazyPlugin, opts:table)` or `true` | `config` is executed when the plugin loads. The default implementation will automatically run `require(MAIN).setup(opts)`. Lazy uses several heuristics to determine the plugin's `MAIN` module automatically based on the plugin's **name**. See also `opts`. To use the default implementation without `opts` set `config` to `true`. | +| **main** | `string?` | You can specify the `main` module to use for `config()` and `opts()`, in case it can not be determined automatically. See `config()` | +| **build** | `fun(LazyPlugin)` or `string` or a list of build commands | `build` is executed when a plugin is installed or updated. Before running `build`, a plugin is first loaded. If it's a string it will be ran as a shell command. When prefixed with `:` it is a Neovim command. You can also specify a list to executed multiple build commands. Some plugins provide their own `build.lua` which is automatically used by lazy. So no need to specify a build step for those plugins. | +| **branch** | `string?` | Branch of the repository | +| **tag** | `string?` | Tag of the repository | +| **commit** | `string?` | Commit of the repository | +| **version** | `string?` or `false` to override the default | Version to use from the repository. Full [Semver](https://devhints.io/semver) ranges are supported | +| **pin** | `boolean?` | When `true`, this plugin will not be included in updates | +| **submodules** | `boolean?` | When false, git submodules will not be fetched. Defaults to `true` | +| **event** | `string?` or `string[]` or `fun(self:LazyPlugin, event:string[]):string[]` or `{event:string[]\|string, pattern?:string[]\|string}` | Lazy-load on event. Events can be specified as `BufEnter` or with a pattern like `BufEnter *.lua` | +| **cmd** | `string?` or `string[]` or `fun(self:LazyPlugin, cmd:string[]):string[]` | Lazy-load on command | +| **ft** | `string?` or `string[]` or `fun(self:LazyPlugin, ft:string[]):string[]` | Lazy-load on filetype | +| **keys** | `string?` or `string[]` or `LazyKeys[]` or `fun(self:LazyPlugin, keys:string[]):(string \| LazyKeys)[]` | Lazy-load on key mapping | +| **module** | `false?` | Do not automatically load this Lua module when it's required somewhere | +| **priority** | `number?` | Only useful for **start** plugins (`lazy=false`) to force loading certain plugins first. Default priority is `50`. It's recommended to set this to a high number for colorschemes. | +| **optional** | `boolean?` | When a spec is tagged optional, it will only be included in the final spec, when the same plugin has been specified at least once somewhere else without `optional`. This is mainly useful for Neovim distros, to allow setting options on plugins that may/may not be part of the user's plugins | ### Lazy Loading diff --git a/lua/lazy/core/handler/event.lua b/lua/lazy/core/handler/event.lua index 3a8d9a7..69f9086 100644 --- a/lua/lazy/core/handler/event.lua +++ b/lua/lazy/core/handler/event.lua @@ -4,12 +4,14 @@ local Util = require("lazy.core.util") ---@class LazyEventOpts ---@field event string ----@field pattern? string ---@field group? string ---@field exclude? string[] ---@field data? any ---@field buffer? number +---@alias LazyEvent {id:string, event:string[]|string, pattern?:string[]|string} +---@alias LazyEventSpec string|{event?:string|string[], pattern?:string|string[]}|string[] + ---@class LazyEventHandler:LazyHandler ---@field events table ---@field group number @@ -23,28 +25,64 @@ M.triggers = { M.group = vim.api.nvim_create_augroup("lazy_handler_event", { clear = true }) ----@param value string -function M:_add(value) - local event_spec = self:_event(value) - ---@type string?, string? - local event, pattern = event_spec:match("^(%w+)%s+(.*)$") - event = event or event_spec +---@param spec LazyEventSpec +---@return LazyEvent +function M:parse(spec) + local ret = M.mappings[spec] --[[@as LazyEvent?]] + if ret then + return ret + end + if type(spec) == "string" then + local event, pattern = spec:match("^(%w+)%s+(.*)$") + event = event or spec + return { id = spec, event = event, pattern = pattern } + elseif Util.is_list(spec) then + ret = { id = table.concat(spec, "|"), event = spec } + else + ret = spec --[[@as LazyEvent]] + 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 + +---@param plugin LazyPlugin +function M:values(plugin) + ---@type table + local values = {} + ---@diagnostic disable-next-line: no-unknown + for _, value in ipairs(plugin[self.type] or {}) do + local event = self:parse(value) + values[event.id] = event + end + return values +end + +---@param event LazyEvent +function M:_add(event) local done = false - vim.api.nvim_create_autocmd(event, { + vim.api.nvim_create_autocmd(event.event, { group = self.group, once = true, - pattern = pattern, + pattern = event.pattern, callback = function(ev) - if done or not self.active[value] then + if done or not self.active[event.id] then return end + -- HACK: work-around for https://github.com/neovim/neovim/issues/25526 done = true - Util.track({ [self.type] = value }) + Util.track({ [self.type] = event.id }) - local state = M.get_state(ev.event, pattern, ev.buf, ev.data) + local state = M.get_state(ev.event, ev.buf, ev.data) -- load the plugins - Loader.load(self.active[value], { [self.type] = value }) + Loader.load(self.active[event.id], { [self.type] = event.id }) -- check if any plugin created an event handler for this event and fire the group for _, s in ipairs(state) do @@ -57,38 +95,23 @@ end -- Get the current state of the event and all the events that will be fired ---@param event string ----@param pattern? string ---@param buf number ---@param data any -function M.get_state(event, pattern, buf, data) +function M.get_state(event, buf, data) local state = {} ---@type LazyEventOpts[] while event do table.insert(state, 1, { event = event, - pattern = pattern, exclude = event ~= "FileType" and M.get_augroups(event) or nil, buffer = buf, data = data, }) data = nil -- only pass the data to the first event - if event == "FileType" then - pattern = nil -- only use the pattern for the first event - end event = M.triggers[event] end return state end ----@param value string -function M:_event(value) - if value == "VeryLazy" then - return "User VeryLazy" - elseif value == "BufRead" then - return "BufReadPost" - end - return value -end - -- Get all augroups for the events ---@param event string function M.get_augroups(event) @@ -127,7 +150,6 @@ function M._trigger(opts) Util.info({ "# Firing Events", " - **event:** " .. opts.event, - opts.pattern and (" - **pattern:** " .. opts.pattern), opts.group and (" - **group:** " .. opts.group), opts.buffer and (" - **buffer:** " .. opts.buffer), }) @@ -135,7 +157,6 @@ function M._trigger(opts) Util.track({ event = opts.group or opts.event }) Util.try(function() vim.api.nvim_exec_autocmds(opts.event, { - -- pattern = opts.pattern, buffer = opts.buffer, group = opts.group, modeline = false, diff --git a/lua/lazy/types.lua b/lua/lazy/types.lua index 3e7f74d..e83b9c4 100644 --- a/lua/lazy/types.lua +++ b/lua/lazy/types.lua @@ -29,7 +29,7 @@ ---@field opts? PluginOpts ---@class LazyPluginHandlers ----@field event? string[] +---@field event? LazyEventSpec[] ---@field cmd? string[] ---@field ft? string[] ---@field keys? (string|LazyKeysSpec)[]