Compare commits

...

26 Commits
main ... pkg

Author SHA1 Message Date
github-actions[bot] 7dfefcc06b chore(build): auto-generate docs 2024-06-24 15:14:48 +00:00
Folke Lemaitre 624b881570
fix(rocks): only build rockspec when it has deps or an advanced build step 2024-06-24 17:10:17 +02:00
Folke Lemaitre 71dec6eff4
fix(fragments): prevent adding the same spec instance more than once 2024-06-24 16:42:58 +02:00
github-actions[bot] 01249cdcd8 chore(build): auto-generate docs 2024-06-24 13:12:26 +00:00
Folke Lemaitre f8dd28272c
fix(luarocks): cleanup luarocks when deleting a plugin 2024-06-24 14:27:29 +02:00
Folke Lemaitre 97704cf48a
feat(build): build files and functions are now async. use coroutine.yield to interrupt and report progress 2024-06-24 14:16:01 +02:00
Folke Lemaitre 73ea05feda
feat: spec.rocks is no longer needed & added support for installing any luarock 2024-06-24 14:16:01 +02:00
github-actions[bot] 66249054c4 chore(build): auto-generate docs 2024-06-24 06:35:54 +00:00
github-actions[bot] 10114eff90 chore(build): auto-generate docs 2024-06-24 05:13:25 +00:00
Folke Lemaitre cb9c5a9bc1
style: wording 2024-06-24 07:12:33 +02:00
github-actions[bot] 7dcc1c2e09 chore(build): auto-generate docs 2024-06-24 05:10:36 +00:00
Folke Lemaitre 44136549ed
docs: fix default lazy-loading comment 2024-06-24 06:44:33 +02:00
Folke Lemaitre 7e20395324
fix(pkg): automatically update pkgs when editing a pkg file 2024-06-23 22:15:10 +02:00
Folke Lemaitre 70f54078e0
fix(pkg): make sure state dir exists 2024-06-23 22:04:32 +02:00
Folke Lemaitre 5a311d04f6
fix(pkg): versioning and reload specs when pkg-cache is dirty 2024-06-23 22:00:48 +02:00
Folke Lemaitre 66ea0ac86e
feat!: new docs for v11.0 2024-06-23 21:07:44 +02:00
Folke Lemaitre 93552cd18e
fix(pkg): correctly pre-load package specs and remove them when needed during resolve 2024-06-23 17:51:03 +02:00
Folke Lemaitre d7bc9ce11a
feat(meta): check for dir changes for plugins already added to the rtp 2024-06-23 17:48:10 +02:00
Folke Lemaitre 9ca807f6bb
docs: added comment on package source order 2024-06-23 10:28:51 +02:00
Folke Lemaitre c37e1a42fd docs: added doc comments 2024-06-23 07:39:09 +02:00
Folke Lemaitre a2b05c38cc feat(pkg): import package specs in the scope of the plugin 2024-06-23 07:39:09 +02:00
Folke Lemaitre 7070cf6ade feat: rewrite of spec resolving 2024-06-23 07:39:09 +02:00
Folke Lemaitre 59b9d3c2b8 test: fix spec tests 2024-06-23 07:39:09 +02:00
Folke Lemaitre 1e0ab0574e feat: added support for plugin packages by lazy, rockspec and packspec 2024-06-23 07:39:09 +02:00
Folke Lemaitre 2d4f2cb507 feat: luarocks support 2024-06-23 07:39:09 +02:00
Folke Lemaitre c2eff4961b feat: packspec 2024-06-23 07:39:09 +02:00
25 changed files with 1946 additions and 1802 deletions

View File

@ -26,31 +26,10 @@ jobs:
nvim --version
[ ! -d tests ] && exit 0
nvim --headless -u tests/init.lua -c "PlenaryBustedDirectory tests/ {minimal_init = 'tests/init.lua', sequential = true}"
docs:
runs-on: ubuntu-latest
needs: tests
if: ${{ github.ref == 'refs/heads/main' }}
steps:
- uses: actions/checkout@v4
- name: panvimdoc
uses: kdheepak/panvimdoc@main
with:
vimdoc: lazy.nvim
version: "Neovim >= 0.8.0"
demojify: true
treesitter: true
- name: Push changes
uses: stefanzweifel/git-auto-commit-action@v5
with:
commit_message: "chore(build): auto-generate vimdoc"
commit_user_name: "github-actions[bot]"
commit_user_email: "github-actions[bot]@users.noreply.github.com"
commit_author: "github-actions[bot] <github-actions[bot]@users.noreply.github.com>"
release:
name: release
if: ${{ github.ref == 'refs/heads/main' }}
needs:
- docs
- tests
runs-on: ubuntu-latest
steps:

845
README.md
View File

@ -1,4 +1,36 @@
# 💤 lazy.nvim
<h4 align="center">
<a href="https://lazy.folke.io/installation">Install</a>
·
<a href="https://lazy.folke.io/configuration">Configure</a>
·
<a href="https://lazy.folke.io">Docs</a>
</h4>
<div align="center"><p>
<a href="https://github.com/folke/lazy.nvim/releases/latest">
<img alt="Latest release" src="https://img.shields.io/github/v/release/folke/lazy.nvim?style=for-the-badge&logo=starship&color=C9CBFF&logoColor=D9E0EE&labelColor=302D41&include_prerelease&sort=semver" />
</a>
<a href="https://github.com/folke/lazy.nvim/pulse">
<img alt="Last commit" src="https://img.shields.io/github/last-commit/folke/lazy.nvim?style=for-the-badge&logo=starship&color=8bd5ca&logoColor=D9E0EE&labelColor=302D41"/>
</a>
<a href="https://github.com/folke/lazy.nvim/blob/main/LICENSE">
<img alt="License" src="https://img.shields.io/github/license/folke/lazy.nvim?style=for-the-badge&logo=starship&color=ee999f&logoColor=D9E0EE&labelColor=302D41" />
</a>
<a href="https://github.com/folke/lazy.nvim/stargazers">
<img alt="Stars" src="https://img.shields.io/github/stars/folke/lazy.nvim?style=for-the-badge&logo=starship&color=c69ff5&logoColor=D9E0EE&labelColor=302D41" />
</a>
<a href="https://github.com/folke/lazy.nvim/issues">
<img alt="Issues" src="https://img.shields.io/github/issues/folke/lazy.nvim?style=for-the-badge&logo=bilibili&color=F5E0DC&logoColor=D9E0EE&labelColor=302D41" />
</a>
<a href="https://github.com/folke/lazy.nvim">
<img alt="Repo Size" src="https://img.shields.io/github/repo-size/folke/lazy.nvim?color=%23DDB6F2&label=SIZE&logo=codesandbox&style=for-the-badge&logoColor=D9E0EE&labelColor=302D41" />
</a>
<a href="https://twitter.com/intent/follow?screen_name=folke">
<img alt="follow on Twitter" src="https://img.shields.io/twitter/follow/folke?style=for-the-badge&logo=twitter&color=8aadf3&logoColor=D9E0EE&labelColor=302D41" />
</a>
</div>
**lazy.nvim** is a modern plugin manager for Neovim.
@ -30,813 +62,6 @@
- Git >= **2.19.0** (for partial clones support)
- a [Nerd Font](https://www.nerdfonts.com/) **_(optional)_**
## 📦 Installation
## 🚀 Getting Started
You can add the following Lua code to your `init.lua` to bootstrap **lazy.nvim**:
<!-- bootstrap:start -->
```lua
local lazypath = vim.fn.stdpath("data") .. "/lazy/lazy.nvim"
if not (vim.uv or vim.loop).fs_stat(lazypath) then
vim.fn.system({
"git",
"clone",
"--filter=blob:none",
"https://github.com/folke/lazy.nvim.git",
"--branch=stable", -- latest stable release
lazypath,
})
end
vim.opt.rtp:prepend(lazypath)
```
<!-- bootstrap:end -->
Next step is to add **lazy.nvim** below the code added in the prior step in `init.lua`:
```lua
require("lazy").setup(plugins, opts)
```
- **plugins**: this should be a `table` or a `string`
- `table`: a list with your [Plugin Spec](#-plugin-spec)
- `string`: a Lua module name that contains your [Plugin Spec](#-plugin-spec). See [Structuring Your Plugins](#-structuring-your-plugins)
- **opts**: see [Configuration](#%EF%B8%8F-configuration) **_(optional)_**
```lua
-- Example using a list of specs with the default options
vim.g.mapleader = " " -- Make sure to set `mapleader` before lazy so your mappings are correct
vim.g.maplocalleader = "\\" -- Same for `maplocalleader`
require("lazy").setup({
"folke/which-key.nvim",
{ "folke/neoconf.nvim", cmd = "Neoconf" },
"folke/neodev.nvim",
})
```
It is recommended to run `:checkhealth lazy` after installation.
## 🔌 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)` 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 run 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 `LazyKeysSpec[]` or `fun(self:LazyPlugin, keys:string[]):(string \| LazyKeysSpec)[]` | 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
**lazy.nvim** automagically lazy-loads Lua modules, so it is not needed to
specify `module=...` everywhere in your plugin specification. This means that if
you have a plugin `A` that is lazy-loaded and a plugin `B` that requires a
module of plugin `A`, then plugin `A` will be loaded on demand as expected.
If you don't want this behavior for a certain plugin, you can specify that with `module=false`.
You can then manually load the plugin with `:Lazy load foobar.nvim`.
You can configure **lazy.nvim** to lazy-load all plugins by default with `config.defaults.lazy = true`.
Additionally, you can also lazy-load on **events**, **commands**,
**file types** and **key mappings**.
Plugins will be lazy-loaded when one of the following is `true`:
- The plugin only exists as a dependency in your spec
- It has an `event`, `cmd`, `ft` or `keys` key
- `config.defaults.lazy == true`
#### 🌈 Colorschemes
Colorscheme plugins can be configured with `lazy=true`. The plugin will automagically load
when doing `colorscheme foobar`.
> **NOTE:** since **start** plugins can possibly change existing highlight groups,
> it's important to make sure that your main **colorscheme** is loaded first.
> To ensure this you can use the `priority=1000` field. **_(see the examples)_**
#### ⌨️ Lazy Key Mappings
The `keys` property can be a `string` or `string[]` for simple normal-mode mappings, or it
can be a `LazyKeysSpec` table with the following key-value pairs:
- **[1]**: (`string`) lhs **_(required)_**
- **[2]**: (`string|fun()`) rhs **_(optional)_**
- **mode**: (`string|string[]`) mode **_(optional, defaults to `"n"`)_**
- **ft**: (`string|string[]`) `filetype` for buffer-local keymaps **_(optional)_**
- any other option valid for `vim.keymap.set`
Key mappings will load the plugin the first time they get executed.
When `[2]` is `nil`, then the real mapping has to be created by the `config()` function.
```lua
-- Example for neo-tree.nvim
{
"nvim-neo-tree/neo-tree.nvim",
keys = {
{ "<leader>ft", "<cmd>Neotree toggle<cr>", desc = "NeoTree" },
},
config = function()
require("neo-tree").setup()
end,
}
```
### Versioning
If you want to install a specific revision of a plugin, you can use `commit`,
`tag`, `branch`, `version`.
The `version` property supports [Semver](https://semver.org/) ranges.
<details>
<summary>Click to see some examples</summary>
- `*`: latest stable version (this excludes pre-release versions)
- `1.2.x`: any version that starts with `1.2`, such as `1.2.0`, `1.2.3`, etc.
- `^1.2.3`: any version that is compatible with `1.2.3`, such as `1.3.0`, `1.4.5`, etc., but not `2.0.0`.
- `~1.2.3`: any version that is compatible with `1.2.3`, such as `1.2.4`, `1.2.5`, but not `1.3.0`.
- `>1.2.3`: any version that is greater than `1.2.3`, such as `1.3.0`, `1.4.5`, etc.
- `>=1.2.3`: any version that is greater than or equal to `1.2.3`, such as `1.2.3`, `1.3.0`, `1.4.5`, etc.
- `<1.2.3`: any version that is less than `1.2.3`, such as `1.1.0`, `1.0.5`, etc.
- `<=1.2.3`: any version that is less than or equal to `1.2.3`, such as `1.2.3`, `1.1.0`, `1.0.5`, etc
</details>
You can set `config.defaults.version = "*"` to install the latest stable
version of plugins that support Semver.
### Examples
<!-- spec:start -->
```lua
return {
-- the colorscheme should be available when starting Neovim
{
"folke/tokyonight.nvim",
lazy = false, -- make sure we load this during startup if it is your main colorscheme
priority = 1000, -- make sure to load this before all the other start plugins
config = function()
-- load the colorscheme here
vim.cmd([[colorscheme tokyonight]])
end,
},
-- I have a separate config.mappings file where I require which-key.
-- With lazy the plugin will be automatically loaded when it is required somewhere
{ "folke/which-key.nvim", lazy = true },
{
"nvim-neorg/neorg",
-- lazy-load on filetype
ft = "norg",
-- options for neorg. This will automatically call `require("neorg").setup(opts)`
opts = {
load = {
["core.defaults"] = {},
},
},
},
{
"dstein64/vim-startuptime",
-- lazy-load on a command
cmd = "StartupTime",
-- init is called during startup. Configuration for vim plugins typically should be set in an init function
init = function()
vim.g.startuptime_tries = 10
end,
},
{
"hrsh7th/nvim-cmp",
-- load cmp on InsertEnter
event = "InsertEnter",
-- these dependencies will only be loaded when cmp loads
-- dependencies are always lazy-loaded unless specified otherwise
dependencies = {
"hrsh7th/cmp-nvim-lsp",
"hrsh7th/cmp-buffer",
},
config = function()
-- ...
end,
},
-- if some code requires a module from an unloaded plugin, it will be automatically loaded.
-- So for api plugins like devicons, we can always set lazy=true
{ "nvim-tree/nvim-web-devicons", lazy = true },
-- you can use the VeryLazy event for things that can
-- load later and are not important for the initial UI
{ "stevearc/dressing.nvim", event = "VeryLazy" },
{
"Wansmer/treesj",
keys = {
{ "J", "<cmd>TSJToggle<cr>", desc = "Join Toggle" },
},
opts = { use_default_keymaps = false, max_join_length = 150 },
},
{
"monaqa/dial.nvim",
-- lazy-load on keys
-- mode is `n` by default. For more advanced options, check the section on key mappings
keys = { "<C-a>", { "<C-x>", mode = "n" } },
},
-- local plugins need to be explicitly configured with dir
{ dir = "~/projects/secret.nvim" },
-- you can use a custom url to fetch a plugin
{ url = "git@github.com:folke/noice.nvim.git" },
-- local plugins can also be configured with the dev option.
-- This will use {config.dev.path}/noice.nvim/ instead of fetching it from GitHub
-- With the dev option, you can easily switch between the local and installed version of a plugin
{ "folke/noice.nvim", dev = true },
}
```
<!-- spec:end -->
## ⚙️ Configuration
**lazy.nvim** comes with the following defaults:
<!-- config:start -->
```lua
{
root = vim.fn.stdpath("data") .. "/lazy", -- directory where plugins will be installed
defaults = {
lazy = false, -- should plugins be lazy-loaded?
version = nil,
-- default `cond` you can use to globally disable a lot of plugins
-- when running inside vscode for example
cond = nil, ---@type boolean|fun(self:LazyPlugin):boolean|nil
-- version = "*", -- enable this to try installing the latest stable versions of plugins
},
-- 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,
git = {
-- defaults for the `Lazy log` command
-- log = { "--since=3 days ago" }, -- show commits from the last 3 days
log = { "-8" }, -- show the last 8 commits
timeout = 120, -- kill processes that take more than 2 minutes
url_format = "https://github.com/%s.git",
-- lazy.nvim requires git >=2.19.0. If you really want to use lazy with an older version,
-- then set the below to false. This should work, but is NOT supported and will
-- increase downloads a lot.
filter = true,
},
dev = {
---@type string | fun(plugin: LazyPlugin): string directory where you store your local plugin projects
path = "~/projects",
---@type string[] plugins that match these patterns will use your local versions instead of being fetched from GitHub
patterns = {}, -- For example {"folke"}
fallback = false, -- Fallback to git when local plugin doesn't exist
},
install = {
-- install missing plugins on startup. This doesn't increase startup time.
missing = true,
-- try to load one of these colorschemes when starting an installation during startup
colorscheme = { "habamax" },
},
ui = {
-- a number <1 is a percentage., >1 is a fixed size
size = { width = 0.8, height = 0.8 },
wrap = true, -- wrap the lines in the ui
-- The border to use for the UI window. Accepts same border values as |nvim_open_win()|.
border = "none",
-- The backdrop opacity. 0 is fully opaque, 100 is fully transparent.
backdrop = 60,
title = nil, ---@type string only works when border is not "none"
title_pos = "center", ---@type "center" | "left" | "right"
-- Show pills on top of the Lazy window
pills = true, ---@type boolean
icons = {
cmd = " ",
config = "",
event = " ",
ft = " ",
init = " ",
import = " ",
keys = " ",
lazy = "󰒲 ",
loaded = "●",
not_loaded = "○",
plugin = " ",
runtime = " ",
require = "󰢱 ",
source = " ",
start = " ",
task = "✔ ",
list = {
"●",
"➜",
"★",
"",
},
},
-- leave nil, to automatically select a browser depending on your OS.
-- If you want to use a specific browser, you can define it here
browser = nil, ---@type string?
throttle = 20, -- how frequently should the ui process render events
custom_keys = {
-- You can define custom key maps here. If present, the description will
-- be shown in the help menu.
-- To disable one of the defaults, set it to false.
["<localleader>l"] = {
function(plugin)
require("lazy.util").float_term({ "lazygit", "log" }, {
cwd = plugin.dir,
})
end,
desc = "Open lazygit log",
},
["<localleader>t"] = {
function(plugin)
require("lazy.util").float_term(nil, {
cwd = plugin.dir,
})
end,
desc = "Open terminal in plugin dir",
},
},
},
diff = {
-- diff command <d> can be one of:
-- * browser: opens the github compare view. Note that this is always mapped to <K> as well,
-- so you can have a different command for diff <d>
-- * git: will run git diff and open a buffer with filetype git
-- * terminal_git: will open a pseudo terminal with git diff
-- * diffview.nvim: will open Diffview to show the diff
cmd = "git",
},
checker = {
-- automatically check for plugin updates
enabled = false,
concurrency = nil, ---@type number? set to 1 to check for updates very slowly
notify = true, -- get a notification when new updates are found
frequency = 3600, -- check for updates every hour
check_pinned = false, -- check for pinned packages that can't be updated
},
change_detection = {
-- automatically check for config file changes and reload the ui
enabled = true,
notify = true, -- get a notification when changes are found
},
performance = {
cache = {
enabled = true,
},
reset_packpath = true, -- reset the package path to improve startup time
rtp = {
reset = true, -- reset the runtime path to $VIMRUNTIME and your config directory
---@type string[]
paths = {}, -- add any custom paths here that you want to includes in the rtp
---@type string[] list any plugins you want to disable here
disabled_plugins = {
-- "gzip",
-- "matchit",
-- "matchparen",
-- "netrwPlugin",
-- "tarPlugin",
-- "tohtml",
-- "tutor",
-- "zipPlugin",
},
},
},
-- lazy can generate helptags from the headings in markdown readme files,
-- so :help works even for plugins that don't have vim docs.
-- when the readme opens with :help it will be correctly displayed as markdown
readme = {
enabled = true,
root = vim.fn.stdpath("state") .. "/lazy/readme",
files = { "README.md", "lua/**/README.md" },
-- only generate markdown helptags for plugins that dont have docs
skip_if_doc_exists = true,
},
state = vim.fn.stdpath("state") .. "/lazy/state.json", -- state info for checker and other things
build = {
-- Plugins can provide a `build.lua` file that will be executed when the plugin is installed
-- or updated. When the plugin spec also has a `build` command, the plugin's `build.lua` not be
-- executed. In this case, a warning message will be shown.
warn_on_override = true,
},
-- Enable profiling of lazy.nvim. This will add some overhead,
-- so only enable this when you are debugging lazy.nvim
profiling = {
-- Enables extra stats on the debug tab related to the loader cache.
-- Additionally gathers stats about all package.loaders
loader = false,
-- Track each new require in the Lazy profiling tab
require = false,
},
}
```
<!-- config:end -->
<details>
<summary>If you don't want to use a Nerd Font, you can replace the icons with Unicode symbols.</summary>
```lua
{
ui = {
icons = {
cmd = "⌘",
config = "🛠",
event = "📅",
ft = "📂",
init = "⚙",
keys = "🗝",
plugin = "🔌",
runtime = "💻",
require = "🌙",
source = "📄",
start = "🚀",
task = "📌",
lazy = "💤 ",
},
},
}
```
</details>
## 🚀 Usage
Plugins are managed with the `:Lazy` command.
Open the help with `<?>` to see all the key mappings.
You can press `<CR>` on a plugin to show its details. Most properties
can be hovered with `<K>` to open links, help files, readmes,
git commits and git issues.
Lazy can automatically check for updates in the background. This feature
can be enabled with `config.checker.enabled = true`.
Any operation can be started from the UI, with a sub command or an API function:
<!-- commands:start -->
| 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 |
<!-- commands:end -->
Any command can have a **bang** to make the command wait till it finished. For example,
if you want to sync lazy from the cmdline, you can use:
```shell
nvim --headless "+Lazy! sync" +qa
```
`opts` is a table with the following key-values:
- **wait**: when true, then the call will wait till the operation completed
- **show**: when false, the UI will not be shown
- **plugins**: a list of plugin names to run the operation on
- **concurrency**: limit the `number` of concurrently running tasks
Stats API (`require("lazy").stats()`):
<!-- stats:start -->
```lua
{
-- startuptime in milliseconds till UIEnter
startuptime = 0,
-- when true, startuptime is the accurate cputime for the Neovim process. (Linux & macOS)
-- this is more accurate than `nvim --startuptime`, and as such will be slightly higher
-- when false, startuptime is calculated based on a delta with a timestamp when lazy started.
real_cputime = false,
count = 0, -- total number of plugins
loaded = 0, -- number of loaded plugins
---@type table<string, number>
times = {},
}
```
<!-- stats:end -->
**lazy.nvim** provides a statusline component that you can use to show the number of pending updates.
Make sure to enable `config.checker.enabled = true` to make this work.
<details>
<summary>Example of configuring <a href="https://github.com/nvim-lualine/lualine.nvim">lualine.nvim</a></summary>
```lua
require("lualine").setup({
sections = {
lualine_x = {
{
require("lazy.status").updates,
cond = require("lazy.status").has_updates,
color = { fg = "#ff9e64" },
},
},
},
})
```
</details>
### 📆 User Events
The following user events will be triggered:
- **LazyDone**: when lazy has finished starting up and loaded your config
- **LazySync**: after running sync
- **LazyInstall**: after an install
- **LazyUpdate**: after an update
- **LazyClean**: after a clean
- **LazyCheck**: after checking for updates
- **LazyLog**: after running log
- **LazyLoad**: after loading a plugin. The `data` attribute will contain the plugin name.
- **LazySyncPre**: before running sync
- **LazyInstallPre**: before an install
- **LazyUpdatePre**: before an update
- **LazyCleanPre**: before a clean
- **LazyCheckPre**: before checking for updates
- **LazyLogPre**: before running log
- **LazyReload**: triggered by change detection after reloading plugin specs
- **VeryLazy**: triggered after `LazyDone` and processing `VimEnter` auto commands
- **LazyVimStarted**: triggered after `UIEnter` when `require("lazy").stats().startuptime` has been calculated.
Useful to update the startuptime on your dashboard.
## 🔒 Lockfile `lazy-lock.json`
After every **update**, the local lockfile is updated with the installed revisions.
It is recommended to have this file under version control.
If you use your Neovim config on multiple machines, using the lockfile, you can
ensure that the same version of every plugin is installed.
If you are on another machine, you can do `:Lazy restore`, to update all your plugins to
the version from the lockfile.
## ⚡ Performance
Great care has been taken to make the startup code (`lazy.core`) as efficient as possible.
During startup, all Lua files used before `VimEnter` or `BufReadPre` are byte-compiled and cached,
similar to what [impatient.nvim](https://github.com/lewis6991/impatient.nvim) does.
My config for example loads in about `11ms` with `93` plugins. I do a lot of lazy-loading though :)
**lazy.nvim** comes with an advanced profiler `:Lazy profile` to help you improve performance.
The profiling view shows you why and how long it took to load your plugins.
![image](https://user-images.githubusercontent.com/292349/208301766-5c400561-83c3-4811-9667-1ec4bb3c43b8.png)
## 🐛 Debug
See an overview of active lazy-loading handlers and what's in the module cache.
![image](https://user-images.githubusercontent.com/292349/208301790-7eedbfa5-d202-4e70-852e-de68aa47233b.png)
## ▶️ Startup Sequence
**lazy.nvim** does **NOT** use Neovim packages and even disables plugin loading
completely (`vim.go.loadplugins = false`). It takes over the complete
startup sequence for more flexibility and better performance.
In practice this means that step 10 of [Neovim Initialization](https://neovim.io/doc/user/starting.html#initialization) is done by Lazy:
1. All the plugins' `init()` functions are executed
2. All plugins with `lazy=false` are loaded. This includes sourcing `/plugin` and `/ftdetect` files. (`/after` will not be sourced yet)
3. All files from `/plugin` and `/ftdetect` directories in your rtp are sourced (excluding `/after`)
4. All `/after/plugin` files are sourced (this includes `/after` from plugins)
Files from runtime directories are always sourced in alphabetical order.
## 📂 Structuring Your Plugins
Some users may want to split their plugin specs in multiple files.
Instead of passing a spec table to `setup()`, you can use a Lua module.
The specs from the **module** and any top-level **sub-modules** will be merged together in the final spec,
so it is not needed to add `require` calls in your main plugin file to the other files.
The benefits of using this approach:
- Simple to **add** new plugin specs. Just create a new file in your plugins module.
- Allows for **caching** of all your plugin specs. This becomes important if you have a lot of smaller plugin specs.
- Spec changes will automatically be **reloaded** when they're updated, so the `:Lazy` UI is always up to date.
Example:
- `~/.config/nvim/init.lua`
```lua
require("lazy").setup("plugins")
```
- `~/.config/nvim/lua/plugins.lua` or `~/.config/nvim/lua/plugins/init.lua` **_(this file is optional)_**
```lua
return {
"folke/neodev.nvim",
"folke/which-key.nvim",
{ "folke/neoconf.nvim", cmd = "Neoconf" },
}
```
- Any lua file in `~/.config/nvim/lua/plugins/*.lua` will be automatically merged in the main plugin spec
For a real-life example, you can check [LazyVim](https://github.com/LazyVim/LazyVim) and more specifically:
- [lazyvim.plugins](https://github.com/LazyVim/LazyVim/tree/main/lua/lazyvim/plugins) contains all the plugin specs that will be loaded
### ↩️ Importing Specs, `config` & `opts`
As part of a spec, you can add `import` statements to import additional plugin modules.
Both of the `setup()` calls are equivalent:
```lua
require("lazy").setup("plugins")
-- Same as:
require("lazy").setup({{import = "plugins"}})
```
To import multiple modules from a plugin, add additional specs for each import.
For example, to import LazyVim core plugins and an optional plugin:
```lua
require("lazy").setup({
spec = {
{ "LazyVim/LazyVim", import = "lazyvim.plugins" },
{ import = "lazyvim.plugins.extras.coding.copilot" },
}
})
```
When you import specs, you can override them by simply adding a spec for the same plugin to your local
specs, adding any keys you want to override / merge.
`opts`, `dependencies`, `cmd`, `event`, `ft` and `keys` are always merged with the parent spec.
Any other property will override the property from the parent spec.
## 📦 Migration Guide
### [packer.nvim](https://github.com/wbthomason/packer.nvim)
- `setup` ➡️ `init`
- `requires` ➡️ `dependencies`
- `as` ➡️ `name`
- `opt` ➡️ `lazy`
- `run` ➡️ `build`
- `lock` ➡️ `pin`
- `disable=true` ➡️ `enabled = false`
- `tag='*'` ➡️ `version="*"`
- `after` is **_not needed_** for most use-cases. Use `dependencies` otherwise.
- `wants` is **_not needed_** for most use-cases. Use `dependencies` otherwise.
- `config` don't support string type, use `fun(LazyPlugin)` instead.
- `module` is auto-loaded. No need to specify
- `keys` spec is [different](#%EF%B8%8F-lazy-key-mappings)
- `rtp` can be accomplished with:
```lua
config = function(plugin)
vim.opt.rtp:append(plugin.dir .. "/custom-rtp")
end
```
With packer `wants`, `requires` and `after` can be used to manage dependencies.
With lazy, this isn't needed for most of the Lua dependencies. They can be installed just like normal plugins
(even with `lazy=true`) and will be loaded when other plugins need them.
The `dependencies` key can be used to group those required plugins with the one that requires them.
The plugins which are added as `dependencies` will always be lazy-loaded and loaded when the plugin is loaded.
### [paq-nvim](https://github.com/savq/paq-nvim)
- `as` ➡️ `name`
- `opt` ➡️ `lazy`
- `run` ➡️ `build`
## ❌ Uninstalling
To uninstall **lazy.nvim**, you need to remove the following files and directories:
- **data**: `~/.local/share/nvim/lazy`
- **state**: `~/.local/state/nvim/lazy`
- **lockfile**: `~/.config/nvim/lazy-lock.json`
> Paths can differ if you changed `XDG` environment variables.
## 🌈 Highlight Groups
<details>
<summary>Click to see all highlight groups</summary>
<!-- colors:start -->
| 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 |
<!-- colors:end -->
</details>
## 📚 Plugin Authors
If your plugin needs a build step, you can create a file `build.lua` or `build/init.lua`
in the root of your repo. This file will be loaded when the plugin is installed or updated.
This makes it easier for users, as they no longer need to specify a `build` command.
## 📦 Other Neovim Plugin Managers in Lua
- [pckr.nvim](https://github.com/lewis6991/pckr.nvim)
- [packer.nvim](https://github.com/wbthomason/packer.nvim)
- [paq-nvim](https://github.com/savq/paq-nvim)
- [neopm](https://github.com/ii14/neopm)
- [dep](https://github.com/chiyadev/dep)
- [optpack.nvim](https://github.com/notomo/optpack.nvim)
- [pact.nvim](https://github.com/rktjmp/pact.nvim)
Check the [documentation website](https://lazy.folke.io/) for more information.

0
doc/.keep Normal file
View File

File diff suppressed because it is too large Load Diff

View File

@ -7,12 +7,16 @@ local M = {}
M.defaults = {
root = vim.fn.stdpath("data") .. "/lazy", -- directory where plugins will be installed
defaults = {
-- Set this to `true` to have all your plugins lazy-loaded by default.
-- Only do this if you know what you are doing, as it can lead to unexpected behavior.
lazy = false, -- should plugins be lazy-loaded?
version = nil,
-- It's recommended to leave version=false for now, since a lot the plugin that support versioning,
-- have outdated releases, which may break your Neovim install.
version = nil, -- always use the latest git commit
-- version = "*", -- try installing the latest stable version for plugins that support semver
-- default `cond` you can use to globally disable a lot of plugins
-- when running inside vscode for example
cond = nil, ---@type boolean|fun(self:LazyPlugin):boolean|nil
-- version = "*", -- enable this to try installing the latest stable versions of plugins
},
-- leave nil when passing the spec as the first argument to setup()
spec = nil, ---@type LazySpec
@ -31,6 +35,21 @@ M.defaults = {
-- increase downloads a lot.
filter = true,
},
pkg = {
enabled = true,
cache = vim.fn.stdpath("state") .. "/lazy/pkg-cache.lua",
versions = true, -- Honor versions in pkg sources
-- the first package source that is found for a plugin will be used.
sources = {
"lazy",
"rockspec",
"packspec",
},
},
rocks = {
root = vim.fn.stdpath("data") .. "/lazy-rocks",
server = "https://nvim-neorocks.github.io/rocks-binaries/",
},
dev = {
---@type string | fun(plugin: LazyPlugin): string directory where you store your local plugin projects
path = "~/projects",
@ -164,12 +183,6 @@ M.defaults = {
skip_if_doc_exists = true,
},
state = vim.fn.stdpath("state") .. "/lazy/state.json", -- state info for checker and other things
build = {
-- Plugins can provide a `build.lua` file that will be executed when the plugin is installed
-- or updated. When the plugin spec also has a `build` command, the plugin's `build.lua` not be
-- executed. In this case, a warning message will be shown.
warn_on_override = true,
},
-- Enable profiling of lazy.nvim. This will add some overhead,
-- so only enable this when you are debugging lazy.nvim
profiling = {
@ -281,6 +294,18 @@ function M.setup(opts)
require("lazy.manage.checker").start()
end, 10)
end
-- useful for plugin developers when making changes to a packspec file
vim.api.nvim_create_autocmd("BufWritePost", {
pattern = { "lazy.lua", "pkg.json", "*.rockspec" },
callback = function()
require("lazy").pkg({
plugins = {
require("lazy.core.plugin").find(vim.uv.cwd() .. "/lua/"),
},
})
end,
})
end,
})

174
lua/lazy/core/fragments.lua Normal file
View File

@ -0,0 +1,174 @@
local Config = require("lazy.core.config")
local Util = require("lazy.core.util")
--- This class is used to manage the fragments of a plugin spec.
--- It keeps track of the fragments and their relations to other fragments.
--- A fragment can be a dependency (dependencies) or a child (specs) of another fragment.
---@class LazyFragments
---@field fragments table<number, LazyFragment>
---@field frag_stack number[]
---@field dep_stack number[]
---@field dirty table<number, boolean>
---@field plugins table<LazyPlugin, number>
---@field spec LazySpecLoader
local M = {}
M._fid = 0
local function next_id()
M._fid = M._fid + 1
return M._fid
end
---@param spec LazySpecLoader
---@return LazyFragments
function M.new(spec)
local self = setmetatable({}, { __index = M })
self.fragments = {}
self.frag_stack = {}
self.dep_stack = {}
self.spec = spec
self.dirty = {}
self.plugins = {}
return self
end
---@param id number
function M:get(id)
return self.fragments[id]
end
--- Remove a fragment and all its children.
--- This will also remove the fragment from its parent's children list.
---@param id number
function M:del(id)
-- del fragment
local fragment = self.fragments[id]
if not fragment then
return
end
self.dirty[id] = true
-- remove from parent
local pid = fragment.pid
if pid then
local parent = self.fragments[pid]
if parent.frags then
---@param fid number
parent.frags = Util.filter(function(fid)
return fid ~= id
end, parent.frags)
end
if parent.deps then
---@param fid number
parent.deps = Util.filter(function(fid)
return fid ~= id
end, parent.deps)
end
self.dirty[pid] = true
end
-- remove children
if fragment.frags then
for _, fid in ipairs(fragment.frags) do
self:del(fid)
end
end
self.fragments[id] = nil
end
--- Add a fragment to the fragments list.
--- This also resolves its name, url, dir, dependencies and child specs.
---@param plugin LazyPluginSpec
function M:add(plugin)
if self.plugins[plugin] then
return self.fragments[self.plugins[plugin]]
end
local id = next_id()
setmetatable(plugin, nil)
self.plugins[plugin] = id
local pid = self.frag_stack[#self.frag_stack]
---@type LazyFragment
local fragment = {
id = id,
pid = pid,
name = plugin.name,
url = plugin.url,
dir = plugin.dir,
spec = plugin --[[@as LazyPlugin]],
}
-- short url / ref
if plugin[1] then
local slash = plugin[1]:find("/", 1, true)
if slash then
local prefix = plugin[1]:sub(1, 4)
if prefix == "http" or prefix == "git@" then
fragment.url = fragment.url or plugin[1]
else
fragment.name = fragment.name or plugin[1]:sub(slash + 1)
fragment.url = fragment.url or Config.options.git.url_format:format(plugin[1])
end
else
fragment.name = fragment.name or plugin[1]
end
end
-- name
fragment.name = fragment.name
or fragment.url and self.spec.get_name(fragment.url)
or fragment.dir and self.spec.get_name(fragment.dir)
if not fragment.name then
return self.spec:error("Invalid plugin spec " .. vim.inspect(plugin))
end
if type(plugin.config) == "table" then
self.spec:warn(
"{" .. fragment.name .. "}: setting a table to `Plugin.config` is deprecated. Please use `Plugin.opts` instead"
)
---@diagnostic disable-next-line: assign-type-mismatch
plugin.opts = plugin.config
plugin.config = nil
end
self.fragments[id] = fragment
-- add to parent
if pid then
local parent = self.fragments[pid]
parent.frags = parent.frags or {}
table.insert(parent.frags, id)
end
-- add to parent's deps
local did = self.dep_stack[#self.dep_stack]
if did and did == pid then
fragment.dep = true
local parent = self.fragments[did]
parent.deps = parent.deps or {}
table.insert(parent.deps, id)
end
table.insert(self.frag_stack, id)
-- dependencies
if plugin.dependencies then
table.insert(self.dep_stack, id)
self.spec:normalize(plugin.dependencies)
table.remove(self.dep_stack)
end
-- child specs
if plugin.specs then
self.spec:normalize(plugin.specs)
end
table.remove(self.frag_stack)
return fragment
end
return M

View File

@ -44,6 +44,7 @@ function M.setup()
while M.install_missing() do
count = count + 1
if count > 5 then
Util.error("Too many rounds of missing plugins")
break
end
end
@ -66,7 +67,10 @@ end
-- multiple rounds can happen when importing a spec from a missing plugin
function M.install_missing()
for _, plugin in pairs(Config.plugins) do
if not (plugin._.installed or Plugin.has_errors(plugin)) then
local installed = plugin._.installed
local has_errors = Plugin.has_errors(plugin)
if not has_errors and not (installed and not plugin._.build) then
for _, colorscheme in ipairs(Config.options.install.colorscheme) do
if colorscheme == "default" then
break
@ -100,7 +104,7 @@ function M.startup()
M.source(vim.env.VIMRUNTIME .. "/filetype.lua")
-- backup original rtp
local rtp = vim.opt.rtp:get()
local rtp = vim.opt.rtp:get() --[[@as string[] ]]
-- 1. run plugin init
Util.track({ start = "init" })
@ -131,7 +135,7 @@ function M.startup()
if not path:find("after/?$") then
-- these paths don't will already have their ftdetect ran,
-- by sourcing filetype.lua above, so skip them
M.did_ftdetect[path] = true
M.did_ftdetect[path] = path
M.packadd(path)
end
end
@ -139,7 +143,9 @@ function M.startup()
-- 4. load after plugins
Util.track({ start = "after" })
for _, path in ipairs(vim.opt.rtp:get()) do
for _, path in
ipairs(vim.opt.rtp:get() --[[@as string[] ]])
do
if path:find("after/?$") then
M.source_runtime(path, "plugin")
end
@ -337,6 +343,10 @@ function M._load(plugin, reason, opts)
M.add_to_rtp(plugin)
if plugin._.pkg and plugin._.pkg.source == "rockspec" then
M.add_to_luapath(plugin)
end
if plugin.dependencies then
Util.try(function()
M.load(plugin.dependencies, {})
@ -480,6 +490,15 @@ function M.add_to_rtp(plugin)
vim.opt.rtp = rtp
end
---@param plugin LazyPlugin
function M.add_to_luapath(plugin)
local root = Config.options.rocks.root .. "/" .. plugin.name
local path = root .. "/share/lua/5.1"
local cpath = root .. "/lib/lua/5.1"
package.path = package.path .. ";" .. path .. "/?.lua;" .. path .. "/?/init.lua;"
package.cpath = package.cpath .. ";" .. cpath .. "/?." .. (jit.os:find("Windows") and "dll" or "so") .. ";"
end
function M.source(path)
Util.track({ runtime = path })
Util.try(function()

323
lua/lazy/core/meta.lua Normal file
View File

@ -0,0 +1,323 @@
local Config = require("lazy.core.config")
local Pkg = require("lazy.pkg")
local Util = require("lazy.core.util")
--- This class is used to manage the plugins.
--- A plugin is a collection of fragments that are related to each other.
---@class LazyMeta
---@field plugins table<string, LazyPlugin>
---@field str_to_meta table<string, LazyPlugin>
---@field frag_to_meta table<number, LazyPlugin>
---@field dirty table<string, boolean>
---@field spec LazySpecLoader
---@field fragments LazyFragments
---@field pkgs table<string, number>
local M = {}
---@param spec LazySpecLoader
---@return LazyMeta
function M.new(spec)
local self = setmetatable({}, { __index = M })
self.spec = spec
self.fragments = require("lazy.core.fragments").new(spec)
self.plugins = {}
self.frag_to_meta = {}
self.str_to_meta = {}
self.dirty = {}
self.pkgs = {}
return self
end
-- import package specs
function M:load_pkgs()
if not Config.options.pkg.enabled then
return
end
for _, pkg in ipairs(Pkg.get()) do
local meta, fragment = self:add(pkg.spec)
if meta and fragment then
meta._.pkg = pkg
-- tag all package fragments as optional
for _, fid in ipairs(meta._.frags) do
local frag = self.fragments:get(fid)
frag.spec.optional = true
end
-- keep track of the top-level package fragment
self.pkgs[pkg.dir] = fragment.id
end
end
end
--- Remove a plugin and all its fragments.
---@param name string
function M:del(name)
local meta = self.plugins[name]
if not meta then
return
end
for _, fid in ipairs(meta._.frags or {}) do
self.fragments:del(fid)
end
self.plugins[name] = nil
end
--- Add a fragment to a plugin.
--- This will create a new plugin if it does not exist.
--- It also keeps track of renames.
---@param plugin LazyPluginSpec
function M:add(plugin)
local fragment = self.fragments:add(plugin)
if not fragment then
return
end
local meta = self.plugins[fragment.name]
or fragment.url and self.str_to_meta[fragment.url]
or fragment.dir and self.str_to_meta[fragment.dir]
if not meta then
meta = { name = fragment.name, _ = { frags = {} } }
local url, dir = fragment.url, fragment.dir
-- add to index
if url then
self.str_to_meta[url] = meta
end
if dir then
self.str_to_meta[dir] = meta
end
end
table.insert(meta._.frags, fragment.id)
if meta._ and meta._.rtp_loaded then
local old_dir = meta.dir
self:_rebuild(meta.name)
local new_dir = meta.dir
if old_dir ~= new_dir then
local msg = "Plugin `" .. meta.name .. "` changed `dir`:\n- from: `" .. old_dir .. "`\n- to: `" .. new_dir .. "`"
msg = msg .. "\n\nThis plugin was already partially loaded, so things may break.\nPlease fix your config."
self.spec:error(msg)
end
end
if plugin.name then
-- handle renames
if meta.name ~= plugin.name then
self.plugins[meta.name] = nil
meta.name = plugin.name
end
end
self.plugins[meta.name] = meta
self.frag_to_meta[fragment.id] = meta
self.dirty[meta.name] = true
return meta, fragment
end
--- Rebuild all plugins based on dirty fragments,
--- or dirty plugins. Will remove plugins that no longer have fragments.
function M:rebuild()
for fid in pairs(self.fragments.dirty) do
local meta = self.frag_to_meta[fid]
if meta then
if self.fragments:get(fid) then
-- fragment still exists, so mark plugin as dirty
self.dirty[meta.name] = true
else
-- fragment was deleted, so remove it from plugin
self.frag_to_meta[fid] = nil
---@param f number
meta._.frags = Util.filter(function(f)
return f ~= fid
end, meta._.frags)
-- if no fragments left, delete plugin
if #meta._.frags == 0 then
self:del(meta.name)
else
self.dirty[meta.name] = true
end
end
end
end
self.fragments.dirty = {}
for n, _ in pairs(self.dirty) do
self:_rebuild(n)
end
end
--- Rebuild a single plugin.
--- This will resolve the plugin based on its fragments using metatables.
--- This also resolves dependencies, dep, optional, dir, dev, and url.
---@param name string
function M:_rebuild(name)
local plugin = self.plugins[name]
if not plugin or #plugin._.frags == 0 then
self.plugins[name] = nil
return
end
setmetatable(plugin, nil)
plugin.dependencies = {}
local super = nil
plugin.url = nil
plugin._.dep = true
plugin.optional = true
assert(#plugin._.frags > 0, "no fragments found for plugin " .. name)
---@type table<number, boolean>
local added = {}
for _, fid in ipairs(plugin._.frags) do
if not added[fid] then
added[fid] = true
local fragment = self.fragments:get(fid)
assert(fragment, "fragment " .. fid .. " not found, for plugin " .. name)
---@diagnostic disable-next-line: no-unknown
super = setmetatable(fragment.spec, super and { __index = super } or nil)
plugin._.dep = plugin._.dep and fragment.dep
plugin.optional = plugin.optional and (rawget(fragment.spec, "optional") == true)
plugin.url = fragment.url or plugin.url
-- dependencies
for _, dep in ipairs(fragment.deps or {}) do
table.insert(plugin.dependencies, self.fragments:get(dep).name)
end
end
end
super = super or {}
-- dir / dev
plugin.dev = super.dev
plugin.dir = super.dir
if plugin.dir then
plugin.dir = Util.norm(plugin.dir)
else
if plugin.dev == nil and plugin.url then
for _, pattern in ipairs(Config.options.dev.patterns) do
if plugin.url:find(pattern, 1, true) then
plugin.dev = true
break
end
end
end
if plugin.dev == true then
local dev_dir = type(Config.options.dev.path) == "string" and Config.options.dev.path .. "/" .. plugin.name
or Util.norm(Config.options.dev.path(plugin))
if not Config.options.dev.fallback or vim.fn.isdirectory(dev_dir) == 1 then
plugin.dir = dev_dir
else
plugin.dev = false
end
end
plugin.dir = plugin.dir or Config.options.root .. "/" .. plugin.name
end
-- dependencies
if #plugin.dependencies == 0 and not super.dependencies then
plugin.dependencies = nil
end
-- optional
if not plugin.optional and not super.optional then
plugin.optional = nil
end
setmetatable(plugin, { __index = super })
self.dirty[plugin.name] = nil
return plugin
end
--- Disable a plugin.
---@param plugin LazyPlugin
function M:disable(plugin)
plugin._.kind = "disabled"
self:del(plugin.name)
self.spec.disabled[plugin.name] = plugin
end
--- Check if a plugin should be disabled, but ignore uninstalling it.
function M:fix_cond()
for _, plugin in pairs(self.plugins) do
local cond = plugin.cond
if cond == nil then
cond = Config.options.defaults.cond
end
if cond == false or (type(cond) == "function" and not cond(plugin)) then
plugin._.cond = false
local stack = { plugin }
while #stack > 0 do
local p = table.remove(stack) --[[@as LazyPlugin]]
if not self.spec.ignore_installed[p.name] then
for _, dep in ipairs(p.dependencies or {}) do
table.insert(stack, self.plugins[dep])
end
self.spec.ignore_installed[p.name] = true
end
end
plugin.enabled = false
end
end
end
--- Removes plugins for which all its fragments are optional.
function M:fix_optional()
if self.spec.optional then
return 0
end
local changes = 0
for _, plugin in pairs(self.plugins) do
if plugin.optional then
changes = changes + 1
self:del(plugin.name)
end
end
self:rebuild()
return changes
end
--- Removes plugins that are disabled.
function M:fix_disabled()
local changes = 0
for _, plugin in pairs(self.plugins) do
if plugin.enabled == false or (type(plugin.enabled) == "function" and not plugin.enabled()) then
changes = changes + 1
self:disable(plugin)
end
end
self:rebuild()
return changes
end
--- Removes package fragments for plugins that no longer use the same directory.
function M:fix_pkgs()
for dir, fid in pairs(self.pkgs) do
local plugin = self.frag_to_meta[fid]
plugin = plugin and self.plugins[plugin.name]
if plugin then
-- check if plugin is still in the same directory
if plugin.dir ~= dir then
self.fragments:del(fid)
end
end
end
self:rebuild()
end
--- Resolve all plugins, based on cond, enabled and optional.
function M:resolve()
Util.track("resolve plugins")
self:rebuild()
self:fix_pkgs()
self:fix_cond()
-- selene: allow(empty_loop)
while self:fix_disabled() + self:fix_optional() > 0 do
end
Util.track()
end
return M

View File

@ -1,4 +1,6 @@
local Config = require("lazy.core.config")
local Meta = require("lazy.core.meta")
local Pkg = require("lazy.pkg")
local Util = require("lazy.core.util")
---@class LazyCorePlugin
@ -6,10 +8,9 @@ local M = {}
M.loading = false
---@class LazySpecLoader
---@field meta LazyMeta
---@field plugins table<string, LazyPlugin>
---@field fragments table<number, LazyPlugin>
---@field disabled table<string, LazyPlugin>
---@field dirty table<string, true>
---@field ignore_installed table<string, true>
---@field modules string[]
---@field notifs {msg:string, level:number, file?:string}[]
@ -17,34 +18,42 @@ M.loading = false
---@field optional? boolean
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}
function Spec.new(spec, opts)
local self = setmetatable({}, { __index = Spec })
self.plugins = {}
self.fragments = {}
local self = setmetatable({}, Spec)
self.meta = Meta.new(self)
self.disabled = {}
self.modules = {}
self.dirty = {}
self.notifs = {}
self.ignore_installed = {}
self.optional = opts and opts.optional
self.meta:load_pkgs()
if spec then
self:parse(spec)
end
return self
end
function Spec:__index(key)
if Spec[key] then
return Spec[key]
end
if key == "plugins" then
self.meta:rebuild()
return self.meta.plugins
end
end
function Spec:parse(spec)
self:normalize(spec)
self:fix_disabled()
self.meta:resolve()
end
-- PERF: optimized code to get package name without using lua patterns
---@return string
function Spec.get_name(pkg)
local name = pkg:sub(-4) == ".git" and pkg:sub(1, -5) or pkg
name = name:sub(-1) == "/" and name:sub(1, -2) or name
@ -52,120 +61,6 @@ function Spec.get_name(pkg)
return slash and name:sub(#name - slash + 2) or pkg:gsub("%W+", "_")
end
---@param plugin LazyPlugin
---@param results? string[]
function Spec:add(plugin, results)
-- check if we already processed this spec. Can happen when a user uses the same instance of a spec in multiple specs
-- see https://github.com/folke/lazy.nvim/issues/45
if rawget(plugin, "_") then
if results then
table.insert(results, plugin.name)
end
return plugin
end
local is_ref = plugin[1] and not plugin[1]:find("/", 1, true)
if not plugin.url and not is_ref and plugin[1] then
local prefix = plugin[1]:sub(1, 4)
if prefix == "http" or prefix == "git@" then
plugin.url = plugin[1]
else
plugin.url = Config.options.git.url_format:format(plugin[1])
end
end
---@type string?
local dir
if plugin.dir then
dir = Util.norm(plugin.dir)
-- local plugin
plugin.name = plugin.name or Spec.get_name(plugin.dir)
elseif plugin.url then
plugin.name = plugin.name or Spec.get_name(plugin.url)
-- check for dev plugins
if plugin.dev == nil then
for _, pattern in ipairs(Config.options.dev.patterns) do
if plugin.url:find(pattern, 1, true) then
plugin.dev = true
break
end
end
end
elseif is_ref then
plugin.name = plugin[1]
else
self:error("Invalid plugin spec " .. vim.inspect(plugin))
return
end
if not plugin.name or plugin.name == "" then
self:error("Plugin spec " .. vim.inspect(plugin) .. " has no name")
return
end
-- dev plugins
if plugin.dev then
local dir_dev
if type(Config.options.dev.path) == "string" then
dir_dev = Config.options.dev.path .. "/" .. plugin.name
else
dir_dev = Util.norm(Config.options.dev.path(plugin))
end
if not Config.options.dev.fallback or vim.fn.isdirectory(dir_dev) == 1 then
dir = dir_dev
end
elseif plugin.dev == false then
-- explicitly select the default path
dir = Config.options.root .. "/" .. plugin.name
end
if type(plugin.config) == "table" then
self:warn(
"{" .. plugin.name .. "}: setting a table to `Plugin.config` is deprecated. Please use `Plugin.opts` instead"
)
---@diagnostic disable-next-line: assign-type-mismatch
plugin.opts = plugin.config
plugin.config = nil
end
local fpid = M.fid_stack[#M.fid_stack]
M.last_fid = M.last_fid + 1
plugin._ = {
dir = dir,
fid = M.last_fid,
fpid = fpid,
dep = fpid ~= nil,
module = self.importing,
}
self.fragments[plugin._.fid] = plugin
-- remote plugin
plugin.dir = plugin._.dir or (plugin.name and (Config.options.root .. "/" .. plugin.name)) or nil
if fpid then
local parent = self.fragments[fpid]
parent._.fdeps = parent._.fdeps or {}
table.insert(parent._.fdeps, plugin._.fid)
end
if plugin.dependencies then
table.insert(M.fid_stack, plugin._.fid)
plugin.dependencies = self:normalize(plugin.dependencies, {})
table.remove(M.fid_stack)
end
if self.plugins[plugin.name] then
plugin = self:merge(self.plugins[plugin.name], plugin)
end
self.plugins[plugin.name] = plugin
if results then
table.insert(results, plugin.name)
end
return plugin
end
function Spec:error(msg)
self:log(msg, vim.log.levels.ERROR)
end
@ -174,166 +69,6 @@ function Spec:warn(msg)
self:log(msg, vim.log.levels.WARN)
end
--- Rebuilds a plugin spec excluding any removed fragments
---@param name? string
function Spec:rebuild(name)
if not name then
for n, _ in pairs(self.dirty) do
self:rebuild(n)
end
self.dirty = {}
end
local plugin = self.plugins[name]
if not plugin then
return
end
local fragments = {} ---@type LazyPlugin[]
repeat
local super = plugin._.super
if self.fragments[plugin._.fid] then
plugin._.dep = plugin._.fpid ~= nil
plugin._.super = nil
if plugin._.fdeps then
plugin.dependencies = {}
for _, cid in ipairs(plugin._.fdeps) do
if self.fragments[cid] then
table.insert(plugin.dependencies, self.fragments[cid].name)
end
end
end
setmetatable(plugin, nil)
table.insert(fragments, 1, plugin)
end
plugin = super
until not plugin
if #fragments == 0 then
self.plugins[name] = nil
return
end
plugin = fragments[1]
for i = 2, #fragments do
plugin = self:merge(plugin, fragments[i])
end
self.plugins[name] = plugin
end
--- Recursively removes all fragments from a plugin spec or a given fragment
---@param id string|number Plugin name or fragment id
---@param opts {self: boolean}
function Spec:remove_fragments(id, opts)
local fids = {} ---@type number[]
if type(id) == "number" then
fids[1] = id
else
local plugin = self.plugins[id]
repeat
if plugin._.fpid then
local parent = self.fragments[plugin._.fpid]
if parent then
parent._.fdeps = vim.tbl_filter(function(fid)
return fid ~= plugin._.fid
end, parent._.fdeps)
self.dirty[parent.name] = true
end
end
fids[#fids + 1] = plugin._.fid
plugin = plugin._.super
until not plugin
end
for _, fid in ipairs(fids) do
local fragment = self.fragments[fid]
if fragment then
for _, cid in ipairs(fragment._.fdeps or {}) do
self:remove_fragments(cid, { self = true })
end
if opts.self then
self.fragments[fid] = nil
end
self.dirty[fragment.name] = true
end
end
end
function Spec:fix_cond()
for _, plugin in pairs(self.plugins) do
local cond = plugin.cond
if cond == nil then
cond = Config.options.defaults.cond
end
if cond == false or (type(cond) == "function" and not cond(plugin)) then
plugin._.cond = false
local stack = { plugin }
while #stack > 0 do
local p = table.remove(stack)
if not self.ignore_installed[p.name] then
for _, dep in ipairs(p.dependencies or {}) do
table.insert(stack, self.plugins[dep])
end
self.ignore_installed[p.name] = true
end
end
plugin.enabled = false
end
end
end
function Spec:fix_optional()
if not self.optional then
---@param plugin LazyPlugin
local function all_optional(plugin)
return (not plugin) or (rawget(plugin, "optional") and all_optional(plugin._.super))
end
-- handle optional plugins
for _, plugin in pairs(self.plugins) do
if plugin.optional and all_optional(plugin) then
-- remove all optional fragments
self:remove_fragments(plugin.name, { self = true })
self.plugins[plugin.name] = nil
end
end
end
end
function Spec:fix_disabled()
for _, plugin in pairs(self.plugins) do
if not plugin.name or not plugin.dir then
self:error("Plugin spec for **" .. plugin.name .. "** not found.\n```lua\n" .. vim.inspect(plugin) .. "\n```")
self.plugins[plugin.name] = nil
end
end
self:fix_optional()
self:rebuild()
self:fix_cond()
self:rebuild()
self.dirty = {}
for _, plugin in pairs(self.plugins) do
local disabled = plugin.enabled == false or (type(plugin.enabled) == "function" and not plugin.enabled())
if disabled then
plugin._.kind = "disabled"
-- remove all child fragments
self:remove_fragments(plugin.name, { self = false })
self.plugins[plugin.name] = nil
self.disabled[plugin.name] = plugin
end
end
self:rebuild()
-- check optional plugins again
self:fix_optional()
self:rebuild()
end
---@param msg string
---@param level number
function Spec:log(msg, level)
@ -353,29 +88,21 @@ function Spec:report(level)
end
---@param spec LazySpec|LazySpecImport
---@param results? string[]
function Spec:normalize(spec, results)
function Spec:normalize(spec)
if type(spec) == "string" then
if not spec:find("/", 1, true) then
-- spec is a plugin name
if results then
table.insert(results, spec)
end
else
self:add({ spec }, results)
end
self.meta:add({ spec })
elseif #spec > 1 or Util.is_list(spec) then
---@cast spec LazySpec[]
for _, s in ipairs(spec) do
self:normalize(s, results)
self:normalize(s)
end
elseif spec[1] or spec.dir or spec.url then
---@cast spec LazyPlugin
local plugin = self:add(spec, results)
---@cast spec LazyPluginSpec
self.meta:add(spec)
---@diagnostic disable-next-line: cast-type-mismatch
---@cast plugin LazySpecImport
if plugin and plugin.import then
self:import(plugin)
---@cast spec LazySpecImport
if spec and spec.import then
self:import(spec)
end
elseif spec.import then
---@cast spec LazySpecImport
@ -383,7 +110,6 @@ function Spec:normalize(spec, results)
else
self:error("Invalid plugin spec " .. vim.inspect(spec))
end
return results
end
---@param spec LazySpecImport
@ -467,41 +193,6 @@ function Spec:import(spec)
end
end
---@param old LazyPlugin
---@param new LazyPlugin
---@return LazyPlugin
function Spec:merge(old, new)
new._.dep = old._.dep and new._.dep
if new.url and old.url and new.url ~= old.url then
self:warn("Two plugins with the same name and different url:\n" .. vim.inspect({ old = old, new = new }))
end
if new.dependencies and old.dependencies then
Util.extend(new.dependencies, old.dependencies)
end
local new_dir = new._.dir or old._.dir or (new.name and (Config.options.root .. "/" .. new.name)) or nil
if new_dir ~= old.dir then
local msg = "Plugin `" .. new.name .. "` changed `dir`:\n- from: `" .. old.dir .. "`\n- to: `" .. new_dir .. "`"
if new._.rtp_loaded or old._.rtp_loaded then
msg = msg
.. "\n\nThis plugin was already partially loaded, so we did not change it's `dir`.\nPlease fix your config."
self:error(msg)
new_dir = old.dir
else
self:warn(msg)
end
end
new.dir = new_dir
new._.rtp_loaded = new._.rtp_loaded or old._.rtp_loaded
new._.super = old
setmetatable(new, { __index = old })
return new
end
function M.update_state()
---@type string[]
local cloning = {}
@ -545,6 +236,8 @@ function M.update_state()
installed[name] = nil
end
M.update_rocks_state()
Config.to_clean = {}
for pack, dir_type in pairs(installed) do
table.insert(Config.to_clean, {
@ -560,6 +253,23 @@ function M.update_state()
end
end
function M.update_rocks_state()
local root = Config.options.rocks.root
---@type table<string,string>
local installed = {}
Util.ls(root, function(_, name, type)
if type == "directory" then
installed[name] = name
end
end)
for _, plugin in pairs(Config.plugins) do
if plugin._.pkg and plugin._.pkg.source == "rockspec" then
plugin._.build = not installed[plugin.name]
end
end
end
---@param path string
function M.local_spec(path)
local file = vim.secure.read(path)
@ -604,6 +314,7 @@ function M.load()
Config.spec = Spec.new()
local specs = {
---@diagnostic disable-next-line: param-type-mismatch
vim.deepcopy(Config.options.spec),
}
specs[#specs + 1] = M.find_local_spec()
@ -627,11 +338,10 @@ function M.load()
-- copy state. This wont do anything during startup
for name, plugin in pairs(existing) do
if Config.plugins[name] then
local dep = Config.plugins[name]._.dep
local super = Config.plugins[name]._.super
local new_state = Config.plugins[name]._
Config.plugins[name]._ = plugin._
Config.plugins[name]._.dep = dep
Config.plugins[name]._.super = super
Config.plugins[name]._.dep = new_state.dep
Config.plugins[name]._.frags = new_state.frags
end
end
Util.track()
@ -639,6 +349,12 @@ function M.load()
Util.track("state")
M.update_state()
Util.track()
if Config.options.pkg.enabled and Pkg.dirty then
Pkg.update()
return M.load()
end
M.loading = false
vim.api.nvim_exec_autocmds("User", { pattern = "LazyPlugins", modeline = false })
end
@ -698,8 +414,9 @@ function M._values(root, plugin, prop, is_list)
if not plugin[prop] then
return {}
end
local super = getmetatable(plugin)
---@type table
local ret = plugin._.super and M._values(root, plugin._.super, prop, is_list) or {}
local ret = super and M._values(root, super.__index, prop, is_list) or {}
local values = rawget(plugin, prop)
if not values then
@ -715,6 +432,7 @@ function M._values(root, plugin, prop, is_list)
else
---@type {path:string[], list:any[]}[]
local lists = {}
---@diagnostic disable-next-line: no-unknown
for _, key in ipairs(plugin[prop .. "_extend"] or {}) do
local path = vim.split(key, ".", { plain = true })
local r = Util.key_get(ret, path)

View File

@ -28,6 +28,20 @@ function M.track(data, time)
end
end
---@generic T
---@param list T[]
---@param fn fun(v: T):boolean?
---@return T[]
function M.filter(fn, list)
local ret = {}
for _, v in ipairs(list) do
if fn(v) then
table.insert(ret, v)
end
end
return ret
end
---@generic F: fun()
---@param data (string|{[string]:string})?
---@param fn F
@ -93,7 +107,7 @@ function M.pretty_trace(opts)
end
---@generic R
---@param fn fun():R
---@param fn fun():R?
---@param opts? string|{msg:string, on_error:fun(msg)}
---@return R
function M.try(fn, opts)

View File

@ -59,7 +59,6 @@ function M.check()
else
for _, plugin in pairs(spec.plugins) do
M.check_valid(plugin)
M.check_override(plugin)
end
if #spec.notifs > 0 then
error("Issues were reported when loading your specs:")
@ -88,23 +87,6 @@ function M.check_valid(plugin)
end
end
---@param plugin LazyPlugin
function M.check_override(plugin)
if not plugin._.super then
return
end
local Handler = require("lazy.core.handler")
local skip = { "dependencies", "_", "opts", 1 }
vim.list_extend(skip, vim.tbl_values(Handler.types))
for key, value in pairs(plugin._.super) do
if not vim.tbl_contains(skip, key) and plugin[key] and plugin[key] ~= value then
warn("{" .. plugin.name .. "}: overriding <" .. key .. ">")
end
end
end
M.valid = {
1,
"_",

View File

@ -87,11 +87,12 @@ function M.install(opts)
"plugin.build",
},
plugins = function(plugin)
return plugin.url and not plugin._.installed
return plugin.url and not (plugin._.installed and not plugin._.build)
end,
}, opts):wait(function()
require("lazy.manage.lock").update()
require("lazy.help").update()
require("lazy.pkg").update()
end)
end
@ -116,6 +117,7 @@ function M.update(opts)
}, opts):wait(function()
require("lazy.manage.lock").update()
require("lazy.help").update()
require("lazy.pkg").update()
end)
end
--
@ -220,7 +222,7 @@ function M.clear(plugins)
if plugin._.tasks then
---@param task LazyTask
plugin._.tasks = vim.tbl_filter(function(task)
return task:is_running()
return task:is_running() or task.error
end, plugin._.tasks)
end
end

View File

@ -4,17 +4,9 @@ local Util = require("lazy.util")
---@type table<string, LazyTaskDef>
local M = {}
M.clean = {
skip = function(plugin)
return plugin._.is_local
end,
run = function(self)
local dir = self.plugin.dir:gsub("/+$", "")
assert(dir:find(Config.options.root, 1, true) == 1, self.plugin.dir .. " should be under packpath!")
local function rm(dir)
local stat = vim.uv.fs_lstat(dir)
assert(stat and stat.type == "directory", self.plugin.dir .. " should be a directory!")
assert(stat and stat.type == "directory", dir .. " should be a directory!")
Util.walk(dir, function(path, _, type)
if type == "directory" then
vim.uv.fs_rmdir(path)
@ -23,6 +15,21 @@ M.clean = {
end
end)
vim.uv.fs_rmdir(dir)
end
M.clean = {
skip = function(plugin)
return plugin._.is_local
end,
run = function(self)
local dir = self.plugin.dir:gsub("/+$", "")
assert(dir:find(Config.options.root, 1, true) == 1, self.plugin.dir .. " should be under packpath!")
rm(dir)
local rock_root = Config.options.rocks.root .. "/" .. self.plugin.name
if vim.uv.fs_stat(rock_root) then
rm(rock_root)
end
self.plugin._.installed = false
end,

View File

@ -74,6 +74,32 @@ function Task:start()
self:_check()
end
---@param fn async fun()
function Task:async(fn)
local co = coroutine.create(fn)
local check = vim.uv.new_check()
check:start(vim.schedule_wrap(function()
local status = coroutine.status(co)
if status == "dead" then
check:stop()
self:_check()
elseif status == "suspended" then
local ok, res = coroutine.resume(co)
if not ok then
error(res)
elseif res then
self.status = res
self.output = self.output .. "\n" .. res
vim.api.nvim_exec_autocmds("User", { pattern = "LazyRender", modeline = false })
end
end
end))
table.insert(self._running, function()
return check:is_active()
end)
end
---@private
function Task:_check()
for _, state in ipairs(self._running) do

View File

@ -8,25 +8,67 @@ local M = {}
---@param plugin LazyPlugin
local function get_build_file(plugin)
for _, path in ipairs({ "build.lua", "build/init.lua" }) do
path = plugin.dir .. "/" .. path
if Util.file_exists(path) then
if Util.file_exists(plugin.dir .. "/" .. path) then
return path
end
end
end
local B = {}
---@param task LazyTask
function B.rockspec(task)
local root = Config.options.rocks.root .. "/" .. task.plugin.name
vim.fn.mkdir(root, "p")
task:spawn("luarocks", {
args = {
"--tree",
root,
"--server",
Config.options.rocks.server,
"--dev",
"--lua-version",
"5.1",
"make",
"--force-fast",
},
cwd = task.plugin.dir,
})
end
---@param task LazyTask
---@param build string
function B.cmd(task, build)
local cmd = vim.api.nvim_parse_cmd(build:sub(2), {}) --[[@as vim.api.keyset.cmd]]
task.output = vim.api.nvim_cmd(cmd, { output = true })
end
---@param task LazyTask
---@param build string
function B.shell(task, build)
local shell = vim.env.SHELL or vim.o.shell
local shell_args = shell:find("cmd.exe", 1, true) and "/c" or "-c"
task:spawn(shell, {
args = { shell_args, build },
cwd = task.plugin.dir,
})
end
M.build = {
---@param opts? {force:boolean}
skip = function(plugin, opts)
if opts and opts.force then
return false
end
return not (plugin._.dirty and (plugin.build or get_build_file(plugin)))
return not ((plugin._.dirty or plugin._.build) and (plugin.build or get_build_file(plugin)))
end,
run = function(self)
vim.cmd([[silent! runtime plugin/rplugin.vim]])
if self.plugin.build ~= "rockspec" then
Loader.load(self.plugin, { task = "build" })
end
local builders = self.plugin.build
@ -35,39 +77,29 @@ M.build = {
return
end
local build_file = get_build_file(self.plugin)
if build_file then
if builders then
if Config.options.build.warn_on_override then
Util.warn(
("Plugin **%s** provides its own build script, but you also defined a `build` command.\nThe `build.lua` file will not be used"):format(
self.plugin.name
)
)
end
else
builders = function()
Loader.source(build_file)
end
end
end
builders = builders or get_build_file(self.plugin)
if builders then
builders = type(builders) == "table" and builders or { builders }
---@cast builders (string|fun(LazyPlugin))[]
for _, build in ipairs(builders) do
if type(build) == "string" and build:sub(1, 1) == ":" then
local cmd = vim.api.nvim_parse_cmd(build:sub(2), {})
self.output = vim.api.nvim_cmd(cmd, { output = true })
elseif type(build) == "function" then
if type(build) == "function" then
self:async(function()
build(self.plugin)
end)
elseif build == "rockspec" then
B.rockspec(self)
elseif build:sub(1, 1) == ":" then
B.cmd(self, build)
elseif build:match("%.lua$") then
local file = self.plugin.dir .. "/" .. build
local chunk, err = loadfile(file)
if not chunk or err then
error(err)
end
self:async(chunk)
else
local shell = vim.env.SHELL or vim.o.shell
local shell_args = shell:find("cmd.exe", 1, true) and "/c" or "-c"
self:spawn(shell, {
args = { shell_args, build },
cwd = self.plugin.dir,
})
B.shell(self, build)
end
end
end

133
lua/lazy/pkg/init.lua Normal file
View File

@ -0,0 +1,133 @@
local Config = require("lazy.core.config")
local Util = require("lazy.util")
local M = {}
M.VERSION = 10
M.dirty = false
---@class LazyPkg
---@field name string
---@field dir string
---@field source "lazy" | "packspec" | "rockspec"
---@field file string
---@field spec LazyPluginSpec
---@class LazyPkgSpec
---@field file string
---@field spec? LazySpec
---@field code? string
---@class LazyPkgSource
---@field name string
---@field get fun(plugin:LazyPlugin):LazyPkgSpec?
---@class LazyPkgCache
---@field pkgs LazyPkg[]
---@field version number
---@type LazyPkg[]?
M.cache = nil
function M.update()
---@type LazyPkgSource[]
local sources = {}
for _, s in ipairs(Config.options.pkg.sources) do
sources[#sources + 1] = {
name = s,
get = require("lazy.pkg." .. s).get,
}
end
---@type LazyPkgCache
local ret = {
version = M.VERSION,
pkgs = {},
}
for _, plugin in pairs(Config.plugins) do
if plugin._.installed then
for _, source in ipairs(sources) do
local spec = source.get(plugin)
if spec then
---@type LazyPkg
local pkg = {
name = plugin.name,
dir = plugin.dir,
source = source.name,
file = spec.file,
spec = spec.spec or {},
}
if type(spec.code) == "string" then
pkg.spec = { _raw = spec.code }
end
table.insert(ret.pkgs, pkg)
break
end
end
end
end
table.sort(ret.pkgs, function(a, b)
return a.name < b.name
end)
local code = "return " .. Util.dump(ret)
vim.fn.mkdir(vim.fn.fnamemodify(Config.options.pkg.cache, ":h"), "p")
Util.write_file(Config.options.pkg.cache, code)
M.dirty = false
M.cache = nil
end
local function _load()
Util.track("pkg")
M.cache = nil
if vim.uv.fs_stat(Config.options.pkg.cache) then
Util.try(function()
local chunk, err = loadfile(Config.options.pkg.cache)
if not chunk then
error(err)
end
---@type LazyPkgCache?
local ret = chunk()
if ret and ret.version == M.VERSION then
M.cache = {}
for _, pkg in ipairs(ret.pkgs) do
if type(pkg.spec) == "function" then
pkg.spec = pkg.spec()
end
-- wrap in the scope of the plugin
pkg.spec = { pkg.name, specs = pkg.spec }
end
M.cache = ret.pkgs
end
end, "Error loading pkg:")
end
if rawget(M, "cache") then
M.dirty = false
else
M.cache = {}
M.dirty = true
end
Util.track()
end
---@param dir string
---@return LazyPkg?
---@overload fun():LazyPkg[]
function M.get(dir)
if dir then
for _, pkg in ipairs(M.cache) do
if pkg.dir == dir then
return pkg
end
end
return
end
return M.cache
end
return setmetatable(M, {
__index = function(_, key)
if key == "cache" then
_load()
return M.cache
end
end,
})

29
lua/lazy/pkg/lazy.lua Normal file
View File

@ -0,0 +1,29 @@
local Util = require("lazy.util")
local M = {}
M.lazy_file = "lazy.lua"
---@param plugin LazyPlugin
---@return LazyPkg?
function M.get(plugin)
local file = Util.norm(plugin.dir .. "/" .. M.lazy_file)
if Util.file_exists(file) then
---@type fun(): LazySpec
local chunk = Util.try(function()
local ret, err = loadfile(file)
return err and error(err) or ret
end, "`" .. M.lazy_file .. "` for **" .. plugin.name .. "** has errors:")
if not chunk then
Util.error("Invalid `" .. M.lazy_file .. "` for **" .. plugin.name .. "**")
return
end
return {
source = "lazy",
file = M.lazy_file,
code = "function()\n" .. Util.read_file(file) .. "\nend",
}
end
end
return M

54
lua/lazy/pkg/packspec.lua Normal file
View File

@ -0,0 +1,54 @@
local Util = require("lazy.util")
---@class PackSpec
---@field dependencies? table<string, string>
---@field lazy? LazyPluginSpec
---
local M = {}
M.pkg_file = "pkg.json"
---@param plugin LazyPlugin
---@return LazyPkg?
function M.get(plugin)
local file = Util.norm(plugin.dir .. "/" .. M.pkg_file)
if not Util.file_exists(file) then
return
end
---@type PackSpec
local pkg = Util.try(function()
return vim.json.decode(Util.read_file(file))
end, "`" .. M.pkg_file .. "` for **" .. plugin.name .. "** has errors:")
if not pkg then
return
end
---@type LazySpec
local ret = {}
if pkg.dependencies then
for url, version in pairs(pkg.dependencies) do
-- HACK: Add `.git` to github urls
if url:find("github") and not url:match("%.git$") then
url = url .. ".git"
end
ret[#ret + 1] = { url = url, version = version }
end
end
local p = pkg.lazy
if p then
p.url = p.url or plugin.url
p.dir = p.dir or plugin.dir
ret[#ret + 1] = p
end
if pkg.lazy then
ret[#ret + 1] = pkg.lazy
end
return {
source = "lazy",
file = M.pkg_file,
spec = ret,
}
end
return M

81
lua/lazy/pkg/rockspec.lua Normal file
View File

@ -0,0 +1,81 @@
--# selene:allow(incorrect_standard_library_use)
local Util = require("lazy.core.util")
local M = {}
M.dev_suffix = "-1.rockspec"
M.skip = { "lua" }
---@class RockSpec
---@field rockspec_format string
---@field package string
---@field version string
---@field dependencies string[]
---@field build? {build_type?: string, modules?: any[]}
---@param plugin LazyPlugin
---@return LazyPkgSpec?
function M.get(plugin)
local rockspec_file ---@type string?
Util.ls(plugin.dir, function(path, name, t)
if t == "file" and name:sub(-#M.dev_suffix) == M.dev_suffix then
rockspec_file = path
return false
end
end)
if not rockspec_file then
return
end
---@type RockSpec?
---@diagnostic disable-next-line: missing-fields
local rockspec = {}
local load, err = loadfile(rockspec_file, "t", rockspec)
if not load then
error(err)
end
load()
if not rockspec then
return
end
local has_lua = not not vim.uv.fs_stat(plugin.dir .. "/lua")
---@param dep string
local rocks = vim.tbl_filter(function(dep)
local name = dep:gsub("%s.*", "")
return not vim.tbl_contains(M.skip, name)
end, rockspec.dependencies or {})
local use = not has_lua
or #rocks > 0
or (
rockspec.build
and rockspec.build.build_type
and rockspec.build.build_type ~= "none"
and not (rockspec.build.build_type == "builtin" and not rockspec.build.modules)
)
if not use then
return
end
local lazy = nil
if not has_lua then
lazy = false
end
return {
file = vim.fn.fnamemodify(rockspec_file, ":t"),
spec = {
plugin.name,
build = "rockspec",
lazy = lazy,
},
} or nil
end
return M

View File

@ -2,28 +2,26 @@
---@alias LazyPluginKind "normal"|"clean"|"disabled"
---@class LazyPluginState
---@field fid number id of the plugin spec fragment
---@field fpid? number parent id of the plugin spec fragment
---@field fdeps? number[] children ids of the fragment
---@field loaded? {[string]:string}|{time:number}
---@field installed? boolean
---@field tasks? LazyTask[]
---@field working? boolean
---@field dirty? boolean
---@field updated? {from:string, to:string}
---@field is_local? boolean
---@field updates? {from:GitInfo, to:GitInfo}
---@field cloned? boolean
---@field outdated? boolean
---@field kind? LazyPluginKind
---@field dep? boolean True if this plugin is only in the spec as a dependency
---@field cond? boolean
---@field super? LazyPlugin
---@field module? string
---@field dir? string Explicit dir or dev set for this plugin
---@field rtp_loaded? boolean
---@field handlers? LazyPluginHandlers
---@field cache? table<string,any>
---@field cloned? boolean
---@field cond? boolean
---@field dep? boolean True if this plugin is only in the spec as a dependency
---@field dir? string Explicit dir or dev set for this plugin
---@field dirty? boolean
---@field build? boolean
---@field frags? number[]
---@field handlers? LazyPluginHandlers
---@field installed? boolean
---@field is_local? boolean
---@field kind? LazyPluginKind
---@field loaded? {[string]:string}|{time:number}
---@field outdated? boolean
---@field rtp_loaded? boolean
---@field tasks? LazyTask[]
---@field updated? {from:string, to:string}
---@field updates? {from:GitInfo, to:GitInfo}
---@field working? boolean
---@field pkg? LazyPkg
---@alias PluginOpts table|fun(self:LazyPlugin, opts:table):table?
@ -31,7 +29,7 @@
---@field init? fun(self:LazyPlugin) Will always be run
---@field deactivate? fun(self:LazyPlugin) Unload/Stop a plugin
---@field config? fun(self:LazyPlugin, opts:table)|true Will be executed when loading the plugin
---@field build? string|fun(self:LazyPlugin)|(string|fun(self:LazyPlugin))[]
---@field build? string|async fun(self:LazyPlugin)|(string|async fun(self:LazyPlugin))[]
---@field opts? PluginOpts
---@class LazyPluginHandlers
@ -60,9 +58,11 @@
---@field lazy? boolean
---@field priority? number Only useful for lazy=false plugins to force loading certain plugins first. Default priority is 50
---@field dev? boolean If set, then link to the respective folder under your ~/projects
---@field rocks? string[]
---@class LazyPlugin: LazyPluginBase,LazyPluginHandlers,LazyPluginHooks,LazyPluginRef
---@field dependencies? string[]
---@field specs? string|string[]|LazyPluginSpec[]
---@field _ LazyPluginState
---@class LazyPluginSpecHandlers
@ -74,6 +74,7 @@
---@class LazyPluginSpec: LazyPluginBase,LazyPluginSpecHandlers,LazyPluginHooks,LazyPluginRef
---@field dependencies? string|string[]|LazyPluginSpec[]
---@field specs? string|string[]|LazyPluginSpec[]
---@alias LazySpec string|LazyPluginSpec|LazySpecImport|LazySpec[]
@ -82,3 +83,15 @@
---@field name? string
---@field enabled? boolean|(fun():boolean)
---@field cond? boolean|(fun():boolean)
---@class LazyFragment
---@field id number
---@field pkg? boolean
---@field pid? number
---@field deps? number[]
---@field frags? number[]
---@field dep? boolean
---@field name string
---@field url? string
---@field dir? string
---@field spec LazyPlugin

View File

@ -231,6 +231,18 @@ function M.markdown(msg, opts)
)
end
---@async
---@param ms number
function M.sleep(ms)
local continue = false
vim.defer_fn(function()
continue = true
end, ms)
while not continue do
coroutine.yield()
end
end
function M._dump(value, result)
local t = type(value)
if t == "number" or t == "boolean" then
@ -241,22 +253,21 @@ function M._dump(value, result)
table.insert(result, value._raw)
elseif t == "table" then
table.insert(result, "{")
local i = 1
for _, v in ipairs(value) do
M._dump(v, result)
table.insert(result, ",")
end
---@diagnostic disable-next-line: no-unknown
for k, v in pairs(value) do
if k == i then
elseif type(k) == "string" then
if type(k) == "string" then
if k:match("^[a-zA-Z]+$") then
table.insert(result, ("%s="):format(k))
else
table.insert(result, ("[%q]="):format(k))
end
else
table.insert(result, k .. "=")
end
M._dump(v, result)
table.insert(result, ",")
i = i + 1
end
end
table.insert(result, "}")
else

View File

@ -34,6 +34,21 @@ M.commands = {
health = function()
vim.cmd.checkhealth("lazy")
end,
---@param opts ManagerOpts
pkg = function(opts)
local Pkg = require("lazy.pkg")
Pkg.update()
require("lazy.manage.reloader").reload({
{
file = "pkg",
what = "changed",
},
})
for _, plugin in pairs(opts and opts.plugins or {}) do
local spec = Pkg.get(plugin.dir)
Util.info(vim.inspect(spec), { lang = "lua", title = plugin.name })
end
end,
home = function()
View.show("home")
end,
@ -74,7 +89,7 @@ M.commands = {
}
function M.complete(cmd, prefix)
if not (ViewConfig.commands[cmd] or {}).plugins then
if not (ViewConfig.commands[cmd] or {}).plugins and cmd ~= "pkg" then
return
end
---@type string[]

View File

@ -1 +1,4 @@
std="vim"
[lints]
mixed_table="allow"

View File

@ -6,6 +6,10 @@ local assert = require("luassert")
Config.setup()
local function inspect(obj)
return vim.inspect(obj):gsub("%s+", " ")
end
---@param plugins LazyPlugin[]|LazyPlugin
local function clean(plugins)
local p = plugins
@ -14,6 +18,7 @@ local function clean(plugins)
plugin._.fid = nil
plugin._.fpid = nil
plugin._.fdeps = nil
plugin._.frags = nil
if plugin._.dep == false then
plugin._.dep = nil
end
@ -28,8 +33,16 @@ describe("plugin spec url/name", function()
{ { "foo/bar" }, { [1] = "foo/bar", name = "bar", url = "https://github.com/foo/bar.git" } },
{ { "https://foo.bar" }, { [1] = "https://foo.bar", name = "foo.bar", url = "https://foo.bar" } },
{ { "foo/bar", name = "foobar" }, { [1] = "foo/bar", name = "foobar", url = "https://github.com/foo/bar.git" } },
{ { "foo/bar", url = "123" }, { [1] = "foo/bar", name = "123", url = "123" } },
{ { "foo/bar", url = "123" }, { [1] = "foo/bar", name = "bar", url = "123" } },
{ { url = "https://foobar" }, { name = "foobar", url = "https://foobar" } },
{
{ { url = "https://foo", name = "foobar" }, { url = "https://foo" } },
{ name = "foobar", url = "https://foo" },
},
{
{ { url = "https://foo" }, { url = "https://foo", name = "foobar" } },
{ name = "foobar", url = "https://foo" },
},
{ { url = "ssh://foobar" }, { name = "foobar", url = "ssh://foobar" } },
{ "foo/bar", { [1] = "foo/bar", name = "bar", url = "https://github.com/foo/bar.git" } },
{ { { { "foo/bar" } } }, { [1] = "foo/bar", name = "bar", url = "https://github.com/foo/bar.git" } },
@ -37,15 +50,23 @@ describe("plugin spec url/name", function()
for _, test in ipairs(tests) do
test[2]._ = {}
it("parses " .. vim.inspect(test[1]):gsub("%s+", " "), function()
it("parses " .. inspect(test[1]), function()
if not test[2].dir then
test[2].dir = Config.options.root .. "/" .. test[2].name
end
local spec = Plugin.Spec.new(test[1])
local plugins = vim.tbl_values(spec.plugins)
plugins[1]._ = {}
assert(#spec.notifs == 0)
assert.equal(1, #plugins)
local all = vim.deepcopy(spec.plugins)
local plugins = vim.tbl_values(all)
plugins = vim.tbl_map(function(plugin)
plugin._ = {}
return plugin
end, plugins)
local notifs = vim.tbl_filter(function(notif)
return notif.level > 3
end, spec.notifs)
assert(#notifs == 0, vim.inspect(spec.notifs))
assert.equal(1, #plugins, vim.inspect(all))
plugins[1]._.super = nil
assert.same(test[2], plugins[1])
end)
end
@ -78,7 +99,40 @@ describe("plugin spec dir", function()
for _, test in ipairs(tests) do
local dir = vim.fn.expand(test[1])
local input = vim.list_slice(test, 2)
it("parses dir " .. vim.inspect(input):gsub("%s+", " "), function()
it("parses dir " .. inspect(input), function()
local spec = Plugin.Spec.new(input)
local plugins = vim.tbl_values(spec.plugins)
assert(spec:report() == 0)
assert.equal(1, #plugins)
assert.same(dir, plugins[1].dir)
end)
end
end)
describe("plugin dev", function()
local tests = {
{
{ "lewis6991/gitsigns.nvim", opts = {}, dev = true },
{ "lewis6991/gitsigns.nvim" },
},
{
{ "lewis6991/gitsigns.nvim", opts = {}, dev = true },
{ "gitsigns.nvim" },
},
{
{ "lewis6991/gitsigns.nvim", opts = {} },
{ "lewis6991/gitsigns.nvim", dev = true },
},
{
{ "lewis6991/gitsigns.nvim", opts = {} },
{ "gitsigns.nvim", dev = true },
},
}
for _, test in ipairs(tests) do
local dir = vim.fn.expand("~/projects/gitsigns.nvim")
local input = test
it("parses dir " .. inspect(input), function()
local spec = Plugin.Spec.new(input)
local plugins = vim.tbl_values(spec.plugins)
assert(spec:report() == 0)
@ -114,7 +168,7 @@ describe("plugin spec opt", function()
for _, plugin in pairs(spec.plugins) do
plugin.dir = nil
end
assert.same(clean(spec.plugins), {
assert.same({
bar = {
"foo/bar",
_ = {},
@ -138,7 +192,7 @@ describe("plugin spec opt", function()
name = "dep2",
url = "https://github.com/foo/dep2.git",
},
})
}, clean(spec.plugins))
end
end)
@ -357,7 +411,6 @@ describe("plugin spec opt", function()
end)
describe("plugin opts", function()
it("correctly parses opts", function()
---@type {spec:LazySpec, opts:table}[]
local tests = {
{
@ -391,11 +444,12 @@ describe("plugin opts", function()
}
for _, test in ipairs(tests) do
it("correctly parses opts for " .. inspect(test.spec), function()
local spec = Plugin.Spec.new(test.spec)
assert(spec.plugins.foo)
assert.same(test.opts, Plugin.values(spec.plugins.foo, "opts"))
end
end)
end
end)
describe("plugin spec", function()

View File

@ -1,3 +1,4 @@
---@module 'luassert'
local Keys = require("lazy.core.handler.keys")
describe("keys", function()