diff --git a/README.md b/README.md index d2f1e49b..e568d314 100644 --- a/README.md +++ b/README.md @@ -79,32 +79,32 @@ 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():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("plugin").setup(opts)`. See also `opts`. | -| **build** | `fun(LazyPlugin)` or `string` or a list of build commands | `build` is executed when a plugin is installed or updated. 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 | -| **branch** | `string?` | Branch of the repository | -| **tag** | `string?` | Tag of the repository | -| **commit** | `string?` | Commit of the repository | -| **version** | `string?` | 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 | -| **event** | `string?` or `string[]` | Lazy-load on event. Events can be specified as `BufEnter` or with a pattern like `BufEnter *.lua` | -| **cmd** | `string?` or `string[]` | Lazy-load on command | -| **ft** | `string?` or `string[]` | Lazy-load on filetype | -| **keys** | `string?` or `string[]` or `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. | +| 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():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("plugin").setup(opts)`. See also `opts`. | +| **build** | `fun(LazyPlugin)` or `string` or a list of build commands | `build` is executed when a plugin is installed or updated. 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 | +| **branch** | `string?` | Branch of the repository | +| **tag** | `string?` | Tag of the repository | +| **commit** | `string?` | Commit of the repository | +| **version** | `string?` | 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 | +| **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. | ### Lazy Loading diff --git a/lua/lazy/core/cache.lua b/lua/lazy/core/cache.lua index bad46abc..4cf01b5e 100644 --- a/lua/lazy/core/cache.lua +++ b/lua/lazy/core/cache.lua @@ -346,7 +346,7 @@ function M.find(modname, opts) local updated = false ---@type LazyCoreConfig local Config = package.loaded["lazy.core.config"] - if Config then + if Config and Config.spec then for _, plugin in pairs(Config.spec.plugins) do if not (M.indexed[plugin.dir] or plugin._.loaded or plugin.module == false) then updated = M._index(plugin.dir) or updated diff --git a/lua/lazy/core/loader.lua b/lua/lazy/core/loader.lua index 1ed73619..663a6daa 100644 --- a/lua/lazy/core/loader.lua +++ b/lua/lazy/core/loader.lua @@ -231,33 +231,14 @@ function M._load(plugin, reason, opts) end) end --- Merges super opts or runs the opts function to override opts or return new ones ----@param plugin LazyPlugin -function M.opts(plugin) - ---@type table - local opts = plugin._.super and M.opts(plugin._.super) or {} - ---@type PluginOpts? - local plugin_opts = rawget(plugin, "opts") - - if type(plugin_opts) == "table" then - opts = Util.merge(opts, plugin_opts) - elseif type(plugin_opts) == "function" then - local new_opts = plugin_opts(plugin, opts) - if new_opts then - opts = new_opts - end - end - - return opts -end - --- runs plugin config ---@param plugin LazyPlugin function M.config(plugin) + local opts = Plugin.values(plugin, "opts", false) local fn if type(plugin.config) == "function" then fn = function() - plugin.config(plugin, M.opts(plugin)) + plugin.config(plugin, opts) end else local normname = Util.normname(plugin.name) @@ -274,7 +255,7 @@ function M.config(plugin) end if #mods == 1 then fn = function() - require(mods[1]).setup(M.opts(plugin)) + require(mods[1]).setup(opts) end else return Util.error( diff --git a/lua/lazy/core/plugin.lua b/lua/lazy/core/plugin.lua index 88713e42..21634a47 100644 --- a/lua/lazy/core/plugin.lua +++ b/lua/lazy/core/plugin.lua @@ -7,9 +7,6 @@ local Cache = require("lazy.core.cache") local M = {} M.loading = false -local list_merge = { "dependencies" } -vim.list_extend(list_merge, vim.tbl_values(Handler.types)) - ---@class LazySpecLoader ---@field plugins table ---@field disabled table @@ -27,11 +24,26 @@ function Spec.new(spec) self.modules = {} self.notifs = {} if spec then - self:normalize(spec) + self:parse(spec) end return self end +function Spec:parse(spec) + self:normalize(spec) + + -- calculate handlers + for _, plugin in pairs(self.plugins) do + for _, handler in pairs(Handler.types) do + if plugin[handler] then + plugin[handler] = M.values(plugin, handler, true) + end + end + end + + self:fix_disabled() +end + -- PERF: optimized code to get package name without using lua patterns function Spec.get_name(pkg) local name = pkg:sub(-4) == ".git" and pkg:sub(1, -5) or pkg @@ -88,11 +100,6 @@ function Spec:add(plugin, results, is_dep) return end - plugin.event = type(plugin.event) == "string" and { plugin.event } or plugin.event - plugin.keys = type(plugin.keys) == "string" and { plugin.keys } or plugin.keys - plugin.cmd = type(plugin.cmd) == "string" and { plugin.cmd } or plugin.cmd - plugin.ft = type(plugin.ft) == "string" and { plugin.ft } or plugin.ft - if type(plugin.config) == "table" then self:warn( "{" .. plugin.name .. "}: setting a table to `Plugin.config` is deprecated. Please use `Plugin.opts` instead" @@ -272,14 +279,10 @@ function Spec:merge(old, new) self:error("Two plugins with the same name and different url:\n" .. vim.inspect({ old = old, new = new })) end - for _, prop in ipairs(list_merge) do - if new[prop] and old[prop] then - if new[prop].__merge == nil then - new[prop].__merge = true - end - new[prop] = Util.merge(old[prop], new[prop]) - end + if new.dependencies and old.dependencies then + vim.list_extend(new.dependencies, old.dependencies) end + new._.super = old setmetatable(new, { __index = old }) @@ -335,10 +338,8 @@ function M.load() -- load specs Util.track("spec") Config.spec = Spec.new() - Config.spec:normalize(vim.deepcopy(Config.options.spec)) + Config.spec:parse({ vim.deepcopy(Config.options.spec), "folke/lazy.nvim" }) - -- add ourselves - Config.spec:add({ "folke/lazy.nvim" }) -- override some lazy props local lazy = Config.spec.plugins["lazy.nvim"] if lazy then @@ -349,7 +350,6 @@ function M.load() end lazy._.loaded = {} end - Config.spec:fix_disabled() local existing = Config.plugins Config.plugins = Config.spec.plugins @@ -395,4 +395,25 @@ function M.has_errors(plugin) return false end +-- Merges super values or runs the values function to override values or return new ones +-- Used for opts, cmd, event, ft and keys +---@param plugin LazyPlugin +---@param prop string +---@param is_list? boolean +function M.values(plugin, prop, is_list) + ---@type table + local ret = plugin._.super and M.values(plugin._.super, prop) or {} + local values = rawget(plugin, prop) + + if not values then + return ret + elseif type(values) == "function" then + ret = values(plugin, ret) or ret + return type(ret) == "table" and ret or { ret } + end + + values = type(values) == "table" and values or { values } + return is_list and vim.list_extend(ret, values) or Util.merge(ret, values) +end + return M diff --git a/lua/lazy/types.lua b/lua/lazy/types.lua index 557a2f44..6a0c055c 100644 --- a/lua/lazy/types.lua +++ b/lua/lazy/types.lua @@ -27,7 +27,7 @@ ---@field event? string[] ---@field cmd? string[] ---@field ft? string[] ----@field keys? string[] +---@field keys? (string|LazyKeys)[] ---@field module? false ---@class LazyPluginRef @@ -53,10 +53,10 @@ ---@field _ LazyPluginState ---@class LazyPluginSpecHandlers ----@field event? string[]|string ----@field cmd? string[]|string ----@field ft? string[]|string ----@field keys? string|string[]|LazyKeys[] +---@field event? string[]|string|fun(self:LazyPlugin, event:string[]):string[] +---@field cmd? string[]|string|fun(self:LazyPlugin, cmd:string[]):string[] +---@field ft? string[]|string|fun(self:LazyPlugin, ft:string[]):string[] +---@field keys? string|string[]|LazyKeys[]|fun(self:LazyPlugin, keys:string[]):(string|LazyKeys)[] ---@field module? false ---@class LazyPluginSpec: LazyPluginBase,LazyPluginSpecHandlers,LazyPluginHooks,LazyPluginRef diff --git a/tests/core/plugin_spec.lua b/tests/core/plugin_spec.lua index ac8b3cb8..d9488177 100644 --- a/tests/core/plugin_spec.lua +++ b/tests/core/plugin_spec.lua @@ -262,7 +262,6 @@ describe("plugin spec opt", function() } for test, ret in pairs(tests) do local spec = Plugin.Spec.new(test) - spec:fix_disabled() assert(#spec.notifs == 0) if ret then assert(spec.plugins.bar)