From 9dde1f1bce44a8fd8cb885b5a8e8d47d8fd7b8c1 Mon Sep 17 00:00:00 2001 From: Folke Lemaitre Date: Sun, 26 May 2024 16:43:52 +0200 Subject: [PATCH] feat: added support for local spec files `.lazy.lua` --- README.md | 105 ++++++++++++++++++++------------------- lua/lazy/core/config.lua | 1 + lua/lazy/core/plugin.lua | 42 +++++++++++++--- 3 files changed, 89 insertions(+), 59 deletions(-) diff --git a/README.md b/README.md index 5465737..d3c1461 100644 --- a/README.md +++ b/README.md @@ -93,7 +93,7 @@ require("lazy").setup({ | **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)` if `opts` or `config = true` is set. 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`. | +| **config** | `fun(LazyPlugin, opts:table)` or `true` | `config` is executed when the plugin loads. The default implementation will automatically run `require(MAIN).setup(opts)` if `opts` or `config = true` is set. 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 | @@ -307,6 +307,7 @@ return { }, -- leave nil when passing the spec as the first argument to setup() spec = nil, ---@type LazySpec + local_spec = true, -- load project specific .lazy.lua spec files. They will be added at the end of the spec. lockfile = vim.fn.stdpath("config") .. "/lazy-lock.json", -- lockfile generated after running update. ---@type number? limit the maximum amount of concurrent tasks concurrency = jit.os:find("Windows") and (vim.uv.available_parallelism() * 2) or nil, @@ -516,24 +517,24 @@ Any operation can be started from the UI, with a sub command or an API function: -| Command | Lua | Description | -| --- | --- | --- | -| `:Lazy build {plugins}` | `require("lazy").build(opts)` | Rebuild a plugin | -| `:Lazy check [plugins]` | `require("lazy").check(opts?)` | Check for updates and show the log (git fetch) | -| `:Lazy clean [plugins]` | `require("lazy").clean(opts?)` | Clean plugins that are no longer needed | -| `:Lazy clear` | `require("lazy").clear()` | Clear finished tasks | -| `:Lazy debug` | `require("lazy").debug()` | Show debug information | -| `:Lazy health` | `require("lazy").health()` | Run `:checkhealth lazy` | -| `:Lazy help` | `require("lazy").help()` | Toggle this help page | -| `:Lazy home` | `require("lazy").home()` | Go back to plugin list | -| `:Lazy install [plugins]` | `require("lazy").install(opts?)` | Install missing plugins | -| `:Lazy load {plugins}` | `require("lazy").load(opts)` | Load a plugin that has not been loaded yet. Similar to `:packadd`. Like `:Lazy load foo.nvim`. Use `:Lazy! load` to skip `cond` checks. | -| `:Lazy log [plugins]` | `require("lazy").log(opts?)` | Show recent updates | -| `:Lazy profile` | `require("lazy").profile()` | Show detailed profiling | -| `:Lazy reload {plugins}` | `require("lazy").reload(opts)` | Reload a plugin (experimental!!) | +| Command | Lua | Description | +| ------------------------- | -------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | +| `:Lazy build {plugins}` | `require("lazy").build(opts)` | Rebuild a plugin | +| `:Lazy check [plugins]` | `require("lazy").check(opts?)` | Check for updates and show the log (git fetch) | +| `:Lazy clean [plugins]` | `require("lazy").clean(opts?)` | Clean plugins that are no longer needed | +| `:Lazy clear` | `require("lazy").clear()` | Clear finished tasks | +| `:Lazy debug` | `require("lazy").debug()` | Show debug information | +| `:Lazy health` | `require("lazy").health()` | Run `:checkhealth lazy` | +| `:Lazy help` | `require("lazy").help()` | Toggle this help page | +| `:Lazy home` | `require("lazy").home()` | Go back to plugin list | +| `:Lazy install [plugins]` | `require("lazy").install(opts?)` | Install missing plugins | +| `:Lazy load {plugins}` | `require("lazy").load(opts)` | Load a plugin that has not been loaded yet. Similar to `:packadd`. Like `:Lazy load foo.nvim`. Use `:Lazy! load` to skip `cond` checks. | +| `:Lazy log [plugins]` | `require("lazy").log(opts?)` | Show recent updates | +| `:Lazy profile` | `require("lazy").profile()` | Show detailed profiling | +| `:Lazy reload {plugins}` | `require("lazy").reload(opts)` | Reload a plugin (experimental!!) | | `:Lazy restore [plugins]` | `require("lazy").restore(opts?)` | Updates all plugins to the state in the lockfile. For a single plugin: restore it to the state in the lockfile or to a given commit under the cursor | -| `:Lazy sync [plugins]` | `require("lazy").sync(opts?)` | Run install, clean and update | -| `:Lazy update [plugins]` | `require("lazy").update(opts?)` | Update plugins. This will also update the lockfile | +| `:Lazy sync [plugins]` | `require("lazy").sync(opts?)` | Run install, clean and update | +| `:Lazy update [plugins]` | `require("lazy").update(opts?)` | Update plugins. This will also update the lockfile | @@ -784,40 +785,40 @@ To uninstall **lazy.nvim**, you need to remove the following files and directori -| Highlight Group | Default Group | Description | -| --- | --- | --- | -| **LazyButton** | ***CursorLine*** | | -| **LazyButtonActive** | ***Visual*** | | -| **LazyComment** | ***Comment*** | | -| **LazyCommit** | ***@variable.builtin*** | commit ref | -| **LazyCommitIssue** | ***Number*** | | -| **LazyCommitScope** | ***Italic*** | conventional commit scope | -| **LazyCommitType** | ***Title*** | conventional commit type | -| **LazyDimmed** | ***Conceal*** | property | -| **LazyDir** | ***@markup.link*** | directory | -| **LazyH1** | ***IncSearch*** | home button | -| **LazyH2** | ***Bold*** | titles | -| **LazyLocal** | ***Constant*** | | -| **LazyNoCond** | ***DiagnosticWarn*** | unloaded icon for a plugin where `cond()` was false | -| **LazyNormal** | ***NormalFloat*** | | -| **LazyProgressDone** | ***Constant*** | progress bar done | -| **LazyProgressTodo** | ***LineNr*** | progress bar todo | -| **LazyProp** | ***Conceal*** | property | -| **LazyReasonCmd** | ***Operator*** | | -| **LazyReasonEvent** | ***Constant*** | | -| **LazyReasonFt** | ***Character*** | | -| **LazyReasonImport** | ***Identifier*** | | -| **LazyReasonKeys** | ***Statement*** | | -| **LazyReasonPlugin** | ***Special*** | | -| **LazyReasonRequire** | ***@variable.parameter*** | | -| **LazyReasonRuntime** | ***@macro*** | | -| **LazyReasonSource** | ***Character*** | | -| **LazyReasonStart** | ***@variable.member*** | | -| **LazySpecial** | ***@punctuation.special*** | | -| **LazyTaskError** | ***ErrorMsg*** | task errors | -| **LazyTaskOutput** | ***MsgArea*** | task output | -| **LazyUrl** | ***@markup.link*** | url | -| **LazyValue** | ***@string*** | value of a property | +| Highlight Group | Default Group | Description | +| --------------------- | -------------------------- | --------------------------------------------------- | +| **LazyButton** | **_CursorLine_** | | +| **LazyButtonActive** | **_Visual_** | | +| **LazyComment** | **_Comment_** | | +| **LazyCommit** | **_@variable.builtin_** | commit ref | +| **LazyCommitIssue** | **_Number_** | | +| **LazyCommitScope** | **_Italic_** | conventional commit scope | +| **LazyCommitType** | **_Title_** | conventional commit type | +| **LazyDimmed** | **_Conceal_** | property | +| **LazyDir** | **_@markup.link_** | directory | +| **LazyH1** | **_IncSearch_** | home button | +| **LazyH2** | **_Bold_** | titles | +| **LazyLocal** | **_Constant_** | | +| **LazyNoCond** | **_DiagnosticWarn_** | unloaded icon for a plugin where `cond()` was false | +| **LazyNormal** | **_NormalFloat_** | | +| **LazyProgressDone** | **_Constant_** | progress bar done | +| **LazyProgressTodo** | **_LineNr_** | progress bar todo | +| **LazyProp** | **_Conceal_** | property | +| **LazyReasonCmd** | **_Operator_** | | +| **LazyReasonEvent** | **_Constant_** | | +| **LazyReasonFt** | **_Character_** | | +| **LazyReasonImport** | **_Identifier_** | | +| **LazyReasonKeys** | **_Statement_** | | +| **LazyReasonPlugin** | **_Special_** | | +| **LazyReasonRequire** | **_@variable.parameter_** | | +| **LazyReasonRuntime** | **_@macro_** | | +| **LazyReasonSource** | **_Character_** | | +| **LazyReasonStart** | **_@variable.member_** | | +| **LazySpecial** | **_@punctuation.special_** | | +| **LazyTaskError** | **_ErrorMsg_** | task errors | +| **LazyTaskOutput** | **_MsgArea_** | task output | +| **LazyUrl** | **_@markup.link_** | url | +| **LazyValue** | **_@string_** | value of a property | diff --git a/lua/lazy/core/config.lua b/lua/lazy/core/config.lua index a964712..c95931c 100644 --- a/lua/lazy/core/config.lua +++ b/lua/lazy/core/config.lua @@ -16,6 +16,7 @@ M.defaults = { }, -- leave nil when passing the spec as the first argument to setup() spec = nil, ---@type LazySpec + local_spec = true, -- load project specific .lazy.lua spec files. They will be added at the end of the spec. lockfile = vim.fn.stdpath("config") .. "/lazy-lock.json", -- lockfile generated after running update. ---@type number? limit the maximum amount of concurrent tasks concurrency = jit.os:find("Windows") and (vim.uv.available_parallelism() * 2) or nil, diff --git a/lua/lazy/core/plugin.lua b/lua/lazy/core/plugin.lua index 64c93cd..7bae395 100644 --- a/lua/lazy/core/plugin.lua +++ b/lua/lazy/core/plugin.lua @@ -1,5 +1,4 @@ local Config = require("lazy.core.config") -local Handler = require("lazy.core.handler") local Util = require("lazy.core.util") ---@class LazyCorePlugin @@ -20,6 +19,7 @@ local Spec = {} M.Spec = Spec M.last_fid = 0 M.fid_stack = {} ---@type number[] +M.LOCAL_SPEC = ".lazy.lua" ---@param spec? LazySpec ---@param opts? {optional?:boolean} @@ -399,10 +399,15 @@ function Spec:import(spec) ---@type string[] local modnames = {} - Util.lsmod(spec.import, function(modname) - modnames[#modnames + 1] = modname - end) - table.sort(modnames) + + if spec.import == M.LOCAL_SPEC then + modnames = { spec.import } + else + Util.lsmod(spec.import, function(modname) + modnames[#modnames + 1] = modname + end) + table.sort(modnames) + end for _, modname in ipairs(modnames) do imported = imported + 1 @@ -412,7 +417,12 @@ function Spec:import(spec) ---@diagnostic disable-next-line: no-unknown package.loaded[modname] = nil Util.try(function() - local mod = require(modname) + local mod = nil + if modname == M.LOCAL_SPEC then + mod = M.local_spec() + else + mod = require(modname) + end if type(mod) ~= "table" then self.importing = nil return self:error( @@ -533,12 +543,30 @@ function M.update_state() end end +function M.local_spec() + local filepath = vim.fn.fnamemodify(".lazy.lua", ":p") + local file = vim.secure.read(filepath) + if file then + return loadstring(file)() + end + return {} +end + function M.load() M.loading = true -- load specs Util.track("spec") Config.spec = Spec.new() - Config.spec:parse({ vim.deepcopy(Config.options.spec), { "folke/lazy.nvim" } }) + Config.spec:parse({ + vim.deepcopy(Config.options.spec), + { + import = ".lazy.lua", + cond = function() + return Config.options.local_spec and vim.fn.filereadable(M.LOCAL_SPEC) == 1 + end, + }, + { "folke/lazy.nvim" }, + }) -- override some lazy props local lazy = Config.spec.plugins["lazy.nvim"]