Compare commits

..

No commits in common. "main" and "v7.4.2" have entirely different histories.

96 changed files with 3381 additions and 129030 deletions

11
.busted
View file

@ -1,11 +0,0 @@
return {
_all = {
coverage = false,
},
default = {
verbose = true,
},
tests = {
verbose = true,
},
}

View file

@ -1,7 +0,0 @@
root = true
[*]
insert_final_newline = true
indent_style = space
indent_size = 2
charset = utf-8

View file

@ -1,3 +0,0 @@
{
".": "11.17.1"
}

View file

@ -6,22 +6,17 @@ body:
- type: markdown
attributes:
value: |
**Before** reporting an issue, make sure to read the [documentation](https://github.com/folke/lazy.nvim)
and search [existing issues](https://github.com/folke/lazy.nvim/issues).
Usage questions such as ***"How do I...?"*** belong in [Discussions](https://github.com/folke/lazy.nvim/discussions) and will be closed.
**Before** reporting an issue, make sure to read the [documentation](https://github.com/folke/lazy.nvim) and search [existing issues](https://github.com/folke/lazy.nvim/issues). Usage questions such as ***"How do I...?"*** belong in [Discussions](https://github.com/folke/lazy.nvim/discussions) and will be closed.
- type: checkboxes
attributes:
label: Did you check docs and existing issues?
description: Make sure you checked all of the below before submitting an issue
options:
- label: I have read all the lazy.nvim docs
- label: I have read all the lazy docs
required: true
- label: I have updated the plugin to the latest version before submitting this issue
- label: I have searched the existing issues of lazy
required: true
- label: I have searched the existing issues of lazy.nvim
required: true
- label: I have searched the existing issues of plugins related to this issue
- label: I have searched the exsiting issues of the plugin I have a problem with
required: true
- type: input
attributes:
@ -62,14 +57,39 @@ body:
label: Repro
description: Minimal `init.lua` to reproduce this issue. Save as `repro.lua` and run with `nvim -u repro.lua`
value: |
vim.env.LAZY_STDPATH = ".repro"
load(vim.fn.system("curl -s https://raw.githubusercontent.com/folke/lazy.nvim/main/bootstrap.lua"))()
-- DO NOT change the paths and don't remove the colorscheme
local root = vim.fn.fnamemodify("./.repro", ":p")
require("lazy.minit").repro({
spec = {
-- add any other plugins here
},
-- set stdpaths to use .repro
for _, name in ipairs({ "config", "data", "state", "cache" }) do
vim.env[("XDG_%s_HOME"):format(name:upper())] = root .. "/" .. name
end
-- bootstrap lazy
local lazypath = root .. "/plugins/lazy.nvim"
if not vim.loop.fs_stat(lazypath) then
vim.fn.system({
"git",
"clone",
"--filter=blob:none",
"--single-branch",
"https://github.com/folke/lazy.nvim.git",
lazypath,
})
render: lua
end
vim.opt.runtimepath:prepend(lazypath)
-- install plugins
local plugins = {
"folke/tokyonight.nvim",
-- add any other plugins here
}
require("lazy").setup(plugins, {
root = root .. "/plugins",
})
vim.cmd.colorscheme("tokyonight")
-- add anything else here
render: Lua
validations:
required: false

View file

@ -1,5 +0,0 @@
blank_issues_enabled: false
contact_links:
- name: Ask a question
url: https://github.com/folke/lazy.nvim/discussions
about: Use Github discussions instead

View file

@ -8,7 +8,7 @@ body:
label: Did you check the docs?
description: Make sure you read all the docs before submitting a feature request
options:
- label: I have read all the lazy.nvim docs
- label: I have read all the lazy docs
required: true
- type: textarea
validations:

View file

@ -1,16 +0,0 @@
## Description
<!-- Describe the big picture of your changes to communicate to the maintainers
why we should accept this pull request. -->
## Related Issue(s)
<!--
If this PR fixes any issues, please link to the issue here.
- Fixes #<issue_number>
-->
## Screenshots
<!-- Add screenshots of the changes if applicable. -->

View file

@ -1,6 +0,0 @@
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"

View file

@ -1,9 +0,0 @@
{
"$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json",
"packages": {
".": {
"release-type": "simple",
"extra-files": ["lua/lazy/core/config.lua"]
}
}
}

View file

@ -1,15 +1,71 @@
name: CI
on:
push:
branches: [main, master]
pull_request:
jobs:
ci:
uses: folke/github/.github/workflows/ci.yml@main
secrets: inherit
tests:
strategy:
matrix:
# os: [ubuntu-latest, windows-latest]
os: [ubuntu-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v3
- name: Install Neovim
shell: bash
run: |
if [ "$RUNNER_OS" == "Linux" ]; then
wget -q https://github.com/neovim/neovim/releases/download/nightly/nvim-linux64.deb -O /tmp/nvim.deb
sudo dpkg -i /tmp/nvim.deb
else
choco install neovim --pre
echo "C:/tools/neovim/nvim-win64/bin" >> $GITHUB_PATH
fi
- name: Run Tests
run: |
nvim --version
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@v3
- name: panvimdoc
uses: kdheepak/panvimdoc@main
with:
plugin: lazy.nvim
repo: folke/lazy.nvim
docs: false
vimdoc: lazy.nvim
version: "Neovim >= 0.8.0"
demojify: true
- name: Code Blocks
run: sed -i 's/^>$/>lua/' doc/lazy.nvim.txt
- name: Push changes
uses: stefanzweifel/git-auto-commit-action@v4
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:
- uses: google-github-actions/release-please-action@v3
with:
release-type: simple
package-name: lazy.nvim
- name: tag stable versions
if: ${{ steps.release.outputs.releases_created }}
run: |
git config user.name github-actions[bot]
git config user.email github-actions[bot]@users.noreply.github.com
git remote add gh-token "https://${{ secrets.GITHUB_TOKEN }}@github.com/google-github-actions/release-please-action.git"
git tag -d stable || true
git push origin :stable || true
git tag -a stable -m "Last Stable Release"
git push origin stable

View file

@ -1,30 +0,0 @@
name: Community
on:
push:
branches:
- main
jobs:
community:
runs-on: ubuntu-latest
if: ${{ github.ref == 'refs/heads/main' && github.repository_owner == 'folke' }}
steps:
- uses: actions/checkout@v4
- uses: folke/github/neovim@main
- name: Rockspec Build
id: rockspec-build
uses: actions/cache@v4
with:
path: build
key: rockspec-build
- name: Generate Rockspec
if: steps.rockspec-build.cache-hit != 'true'
run: |
nvim -l lua/lazy/build.lua
- name: Push changes
uses: stefanzweifel/git-auto-commit-action@v5
with:
commit_message: "chore(build): auto-generate rockspec mappings"
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>"

View file

@ -1,19 +0,0 @@
name: Docs
on:
push:
branches:
- main
jobs:
docs:
runs-on: ubuntu-latest
if: ${{ github.ref == 'refs/heads/main' && github.repository_owner == 'folke' }}
env:
GH_TOKEN: ${{ github.token }}
steps:
- uses: actions/checkout@v4
with:
ref: docs
- name: Generate Docs
shell: bash
run: gh workflow run "Deploy to Github Pages" --ref docs

View file

@ -1,8 +0,0 @@
name: "PR Labeler"
on:
- pull_request_target
jobs:
labeler:
uses: folke/github/.github/workflows/labeler.yml@main
secrets: inherit

View file

@ -1,18 +0,0 @@
name: PR Title
on:
pull_request_target:
types:
- opened
- edited
- synchronize
- reopened
- ready_for_review
permissions:
pull-requests: read
jobs:
pr-title:
uses: folke/github/.github/workflows/pr.yml@main
secrets: inherit

View file

@ -1,11 +0,0 @@
name: Stale Issues & PRs
on:
schedule:
- cron: "30 1 * * *"
jobs:
stale:
if: contains(fromJSON('["folke", "LazyVim"]'), github.repository_owner)
uses: folke/github/.github/workflows/stale.yml@main
secrets: inherit

View file

@ -1,13 +0,0 @@
name: Update Repo
on:
workflow_dispatch:
schedule:
# Run every hour
- cron: "0 * * * *"
jobs:
update:
if: contains(fromJSON('["folke", "LazyVim"]'), github.repository_owner)
uses: folke/github/.github/workflows/update.yml@main
secrets: inherit

13
.gitignore vendored
View file

@ -1,9 +1,6 @@
*.log
/.repro
/.tests
/build
/debug
/doc/tags
foo.*
node_modules
tt.*
.tests
doc/tags
debug
.repro
foo.*

View file

@ -7,7 +7,7 @@
}
},
"lspconfig": {
"lua_ls": {
"sumneko_lua": {
"Lua.runtime.version": "LuaJIT",
"Lua.workspace.checkThirdParty": false
}

View file

@ -1 +0,0 @@
lua/lazy/community/_generated.lua

File diff suppressed because it is too large Load diff

759
README.md
View file

@ -1,36 +1,4 @@
<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
**lazy.nvim** is a modern plugin manager for Neovim.
@ -39,9 +7,9 @@
## ✨ Features
- 📦 Manage all your Neovim plugins with a powerful UI
- 🚀 Fast startup times thanks to automatic caching and bytecode compilation of Lua modules
- 🚀 Fast startup times thanks to automatic caching and bytecode compilation of lua modules
- 💾 Partial clones instead of shallow clones
- 🔌 Automatic lazy-loading of Lua modules and lazy-loading on events, commands, filetypes, and key mappings
- 🔌 Automatic lazy-loading of lua modules and lazy-loading on events, commands, filetypes, and key mappings
- ⏳ Automatically install missing plugins before starting up Neovim, allowing you to start using it right away
- 💪 Async execution for improved performance
- 🛠️ No need to manually compile plugins
@ -61,9 +29,722 @@
- Neovim >= **0.8.0** (needs to be built with **LuaJIT**)
- Git >= **2.19.0** (for partial clones support)
- a [Nerd Font](https://www.nerdfonts.com/) **_(optional)_**
- [luarocks](https://luarocks.org/) to install rockspecs.
You can remove `rockspec` from `opts.pkg.sources` to disable this feature.
## 🚀 Getting Started
## 📦 Installation
Check the [documentation website](https://lazy.folke.io/) for more information.
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.loop.fs_stat(lazypath) then
vim.fn.system({
"git",
"clone",
"--filter=blob:none",
"--single-branch",
"https://github.com/folke/lazy.nvim.git",
lazypath,
})
end
vim.opt.runtimepath:prepend(lazypath)
```
<!-- bootstrap:end -->
Next step is to add **lazy.nvim** to the top of your `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
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():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 |
| **config** | `fun(LazyPlugin)` or `true` or `table` | `config` is executed when the plugin loads. You can also set to `true` or pass a `table`, that will be passed to `require("plugin").setup(opts)` |
| **build** | `fun(LazyPlugin)` or `string` or a list of build commands | `build` is executed when a plugin is installed or updated. If it's a string it will be ran as a shell command. When prefixed with `:` it is a Neovim command. You can also specify a list to executed multiple build commands |
| **branch** | `string?` | Branch of the repository |
| **tag** | `string?` | Tag of the repository |
| **commit** | `string?` | Commit of the repository |
| **version** | `string?` | Version to use from the repository. Full [Semver](https://devhints.io/semver) ranges are supported |
| **pin** | `boolean?` | When `true`, this plugin will not be included in updates |
| **event** | `string?` or `string[]` | Lazy-load on event. Events can be specified as `BufEnter` or with a pattern like `BufEnter *.lua` |
| **cmd** | `string?` or `string[]` | Lazy-load on command |
| **ft** | `string?` or `string[]` | Lazy-load on filetype |
| **keys** | `string?` or `string[]` or `LazyKeys[]` | Lazy-load on key mapping |
| **module** | `false?` | Do not automatically load this Lua module when it's required somewhere |
| **priority** | `number?` | Only useful for **start** plugins (`lazy=false`) to force loading certain plugins first. Default priority is `50`. It's recommended to set this to a high number for colorschemes. |
### Lazy Loading
**lazy.nvim** automagically lazy-loads Lua modules, so it is not needed to
specify `module=...` everywhere in your plugin specification. This mean 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 `LazyKeys` table with the following key-value pairs:
- **[1]**: (`string`) lhs **_(required)_**
- **[2]**: (`string|fun()`) rhs **_(optional)_**
- **mode**: (`string|string[]`) mode **_(optional, defaults to `"n"`)_**
- 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",
-- custom config that will be executed when loading the plugin
config = function()
require("neorg").setup()
end,
},
-- the above could also be written as:
{
"nvim-neorg/neorg",
ft = "norg",
config = true, -- run require("neorg").setup()
},
-- or set a custom config:
{
"nvim-neorg/neorg",
ft = "norg",
config = { foo = "bar" }, -- run require("neorg").setup({foo = "bar"})
},
{
"dstein64/vim-startuptime",
-- lazy-load on a command
cmd = "StartupTime",
},
{
"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,
},
-- 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" },
{
"cshuaimin/ssr.nvim",
-- init is always executed during startup, but doesn't load the plugin yet.
init = function()
vim.keymap.set({ "n", "x" }, "<leader>cR", function()
-- this require will automatically load the plugin
require("ssr").open()
end, { desc = "Structural Replace" })
end,
},
{
"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 configure 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,
-- version = "*", -- enable this to try installing the latest stable versions of plugins
},
lockfile = vim.fn.stdpath("config") .. "/lazy-lock.json", -- lockfile generated after running update.
concurrency = nil, ---@type number limit the maximum amount of concurrent tasks
git = {
-- defaults for the `Lazy log` command
-- log = { "-10" }, -- show the last 10 commits
log = { "--since=3 days ago" }, -- show commits from the last 3 days
timeout = 120, -- kill processes that take more than 2 minutes
url_format = "https://github.com/%s.git",
},
dev = {
-- 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"}
},
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 },
-- The border to use for the UI window. Accepts same border values as |nvim_open_win()|.
border = "none",
icons = {
loaded = "●",
not_loaded = "○",
cmd = " ",
config = "",
event = "",
ft = " ",
init = " ",
keys = " ",
plugin = " ",
runtime = " ",
source = " ",
start = "",
task = "✔ ",
lazy = "鈴 ",
list = {
"●",
"➜",
"★",
"",
},
},
throttle = 20, -- how frequently should the ui process render events
custom_keys = {
-- you can define custom key maps here.
-- To disable one of the defaults, set it to false
-- open lazygit log
["<localleader>l"] = function(plugin)
require("lazy.util").open_cmd({ "lazygit", "log" }, {
cwd = plugin.dir,
terminal = true,
close_on_exit = true,
enter = true,
})
end,
-- open a terminal for the plugin dir
["<localleader>t"] = function(plugin)
require("lazy.util").open_cmd({ vim.go.shell }, {
cwd = plugin.dir,
terminal = true,
close_on_exit = true,
enter = true,
})
end,
},
},
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
},
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,
path = vim.fn.stdpath("cache") .. "/lazy/cache",
-- Once one of the following events triggers, caching will be disabled.
-- To cache all modules, set this to `{}`, but that is not recommended.
-- The default is to disable on:
-- * VimEnter: not useful to cache anything else beyond startup
-- * BufReadPre: this will be triggered early when opening a file from the command line directly
disable_events = { "VimEnter", "BufReadPre" },
ttl = 3600 * 24 * 5, -- keep unused modules for up to 5 days
},
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 indluce 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 = {
root = vim.fn.stdpath("state") .. "/lazy/readme",
files = { "README.md" },
-- only generate markdown helptags for plugins that dont have docs
skip_if_doc_exists = true,
},
}
```
<!-- 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 = "💻",
source = "📄",
start = "🚀",
task = "📌",
},
},
}
```
</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 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 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` |
| `:Lazy log [plugins]` | `require("lazy").log(opts?)` | Show recent updates |
| `:Lazy profile` | `require("lazy").profile()` | Show detailed profiling |
| `:Lazy restore [plugins]` | `require("lazy").restore(opts?)` | Updates all plugins to the state in 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 |
<!-- 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.
startuptime_cputime = false,
count = 0, -- total number of plugins
loaded = 0, -- number of loaded plugins
}
```
<!-- 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
- **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 you rtp are sourced (excluding `/after`)
4. all `/after/plugin` files are sourced (this inludes `/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 my personal dots:
- [init.lua](https://github.com/folke/dot/blob/master/config/nvim/init.lua) where I require `config.lazy`
- [config.lazy](https://github.com/folke/dot/blob/master/config/nvim/lua/config/lazy.lua) where I bootstrap and setup **lazy.nvim**
- [config.plugins](https://github.com/folke/dot/blob/master/config/nvim/lua/config/plugins/init.lua) is my main plugin config module
- Any submodule of [config.plugins (submodules)](https://github.com/folke/dot/tree/master/config/nvim/lua/config/plugins) will be automatically loaded as well.
## 📦 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` **_not needed_** for most use-cases. Use `dependencies` otherwise.
- `wants` **_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)
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 |
| **LazyDir** | **_@text.reference_** | directory |
| **LazyH1** | **_IncSearch_** | home button |
| **LazyH2** | **_Bold_** | titles |
| **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_** | |
| **LazyReasonKeys** | **_Statement_** | |
| **LazyReasonPlugin** | **_Special_** | |
| **LazyReasonRuntime** | **_@macro_** | |
| **LazyReasonSource** | **_Character_** | |
| **LazyReasonStart** | **_@field_** | |
| **LazySpecial** | **_@punctuation.special_** | |
| **LazyTaskError** | **_ErrorMsg_** | task errors |
| **LazyTaskOutput** | **_MsgArea_** | task output |
| **LazyUrl** | **_@text.reference_** | url |
| **LazyValue** | **_@string_** | value of a property |
<!-- colors:end -->
</details>
## 📦 Other Neovim Plugin Managers in Lua
- [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)

14
TODO.md
View file

@ -1,15 +1,5 @@
# ✅ TODO
- [x] progress bar?
- [x] options when opening file
- [x] lazy notify? not ideal when installing missing stuff
- [x] topmods?
- [ ] better merging options?
- [ ] especially what to do with merging of handlers?
- [ ] overwriting keymaps probably doesn't work
- [ ] disabled deps?
- [x] fancy UI to manage all your Neovim plugins
- [x] auto lazy-loading of lua modules
- [x] lazy-loading on events, commands, filetypes and key mappings
@ -51,7 +41,7 @@
- [ ] add support to specify `engines`, `os` and `cpu` like in `package.json`
- [ ] semver merging. Should check if two or more semver ranges are compatible and calculate the union range
- default semver merging strategy: if no version matches all, then use the highest version?
- default semver merging strategy: if no version matches all, then use highest version?
- [ ] package meta index (package.lua cache for all packages)
- [x] document highlight groups
@ -65,7 +55,7 @@
Maybe a quick, "for example, if you have a lua file
`~/.config/nvim/lua/config/plugins.lua` that returns a table" or something it'd
remove most question marks I think.
- [x] When auto-installing the plugins the cursor isn't focused on the floating
- [x] When autoinstalling the plugins the cursor isn't focused on the floating
window, but on the non-floating window in the background.
- [x] Doing `:Lazy clean` doesn't show which plugins were removed.
- [x] Shouldn't the "Versioning" section be in the "Lockfile" chapter?

View file

@ -1,51 +0,0 @@
-- Lazy Bootstrapper
-- Usage:
-- ```lua
-- load(vim.fn.system("curl -s https://raw.githubusercontent.com/folke/lazy.nvim/main/bootstrap.lua"))()
-- ```
local M = {}
function M.setup()
local uv = vim.uv or vim.loop
if vim.env.LAZY_STDPATH then
local root = vim.fn.fnamemodify(vim.env.LAZY_STDPATH, ":p"):gsub("[\\/]$", "")
for _, name in ipairs({ "config", "data", "state", "cache" }) do
vim.env[("XDG_%s_HOME"):format(name:upper())] = root .. "/" .. name
end
end
if vim.env.LAZY_PATH and not uv.fs_stat(vim.env.LAZY_PATH) then
vim.env.LAZY_PATH = nil
end
local lazypath = vim.env.LAZY_PATH or vim.fn.stdpath("data") .. "/lazy/lazy.nvim"
if not vim.env.LAZY_PATH and not uv.fs_stat(lazypath) then
vim.api.nvim_echo({
{
"Cloning lazy.nvim\n\n",
"DiagnosticInfo",
},
}, true, {})
local lazyrepo = "https://github.com/folke/lazy.nvim.git"
local ok, out = pcall(vim.fn.system, {
"git",
"clone",
"--filter=blob:none",
lazyrepo,
lazypath,
})
if not ok or vim.v.shell_error ~= 0 then
vim.api.nvim_echo({
{ "Failed to clone lazy.nvim\n", "ErrorMsg" },
{ vim.trim(out or ""), "WarningMsg" },
{ "\nPress any key to exit...", "MoreMsg" },
}, true, {})
vim.fn.getchar()
os.exit(1)
end
end
vim.opt.rtp:prepend(lazypath)
end
M.setup()
return M

View file

File diff suppressed because it is too large Load diff

View file

@ -1,222 +0,0 @@
local Util = require("lazy.core.util")
local M = {}
---@type Async[]
M._active = {}
---@type Async[]
M._suspended = {}
M._executor = assert(vim.loop.new_check())
M.BUDGET = 10
---@type table<thread, Async>
M._threads = setmetatable({}, { __mode = "k" })
---@alias AsyncEvent "done" | "error" | "yield" | "ok"
---@class Async
---@field _co thread
---@field _fn fun()
---@field _suspended? boolean
---@field _on table<AsyncEvent, fun(res:any, async:Async)[]>
local Async = {}
---@param fn async fun()
---@return Async
function Async.new(fn)
local self = setmetatable({}, { __index = Async })
return self:init(fn)
end
---@param fn async fun()
---@return Async
function Async:init(fn)
self._fn = fn
self._on = {}
self._co = coroutine.create(function()
local ok, err = pcall(self._fn)
if not ok then
self:_emit("error", err)
end
self:_emit("done")
end)
M._threads[self._co] = self
return M.add(self)
end
---@param event AsyncEvent
---@param cb async fun(res:any, async:Async)
function Async:on(event, cb)
self._on[event] = self._on[event] or {}
table.insert(self._on[event], cb)
return self
end
---@private
---@param event AsyncEvent
---@param res any
function Async:_emit(event, res)
for _, cb in ipairs(self._on[event] or {}) do
cb(res, self)
end
end
function Async:running()
return coroutine.status(self._co) ~= "dead"
end
---@async
function Async:sleep(ms)
vim.defer_fn(function()
self:resume()
end, ms)
self:suspend()
end
---@async
---@param yield? boolean
function Async:suspend(yield)
self._suspended = true
if coroutine.running() == self._co and yield ~= false then
M.yield()
end
end
function Async:resume()
self._suspended = false
M._run()
end
---@async
---@param yield? boolean
function Async:wake(yield)
local async = M.running()
assert(async, "Not in an async context")
self:on("done", function()
async:resume()
end)
async:suspend(yield)
end
---@async
function Async:wait()
if coroutine.running() == self._co then
error("Cannot wait on self")
end
local async = M.running()
if async then
self:wake()
else
while self:running() do
vim.wait(10)
end
end
return self
end
function Async:step()
if self._suspended then
return true
end
local status = coroutine.status(self._co)
if status == "suspended" then
local ok, res = coroutine.resume(self._co)
if not ok then
error(res)
elseif res then
self:_emit("yield", res)
end
end
return self:running()
end
function M.abort()
for _, async in ipairs(M._active) do
coroutine.resume(async._co, "abort")
end
end
function M.yield()
if coroutine.yield() == "abort" then
error("aborted", 2)
end
end
function M.step()
local start = vim.uv.hrtime()
for _ = 1, #M._active do
if Util.exiting() or vim.uv.hrtime() - start > M.BUDGET * 1e6 then
break
end
local state = table.remove(M._active, 1)
if state:step() then
if state._suspended then
table.insert(M._suspended, state)
else
table.insert(M._active, state)
end
end
end
for _ = 1, #M._suspended do
local state = table.remove(M._suspended, 1)
table.insert(state._suspended and M._suspended or M._active, state)
end
-- M.debug()
if #M._active == 0 or Util.exiting() then
return M._executor:stop()
end
end
function M.debug()
local lines = {
"- active: " .. #M._active,
"- suspended: " .. #M._suspended,
}
for _, async in ipairs(M._active) do
local info = debug.getinfo(async._fn)
local file = vim.fn.fnamemodify(info.short_src:sub(1), ":~:.")
table.insert(lines, ("%s:%d"):format(file, info.linedefined))
if #lines > 10 then
break
end
end
local msg = table.concat(lines, "\n")
M._notif = vim.notify(msg, nil, { replace = M._notif })
end
---@param async Async
function M.add(async)
table.insert(M._active, async)
M._run()
return async
end
function M._run()
if not Util.exiting() and not M._executor:is_active() then
M._executor:start(vim.schedule_wrap(M.step))
end
end
function M.running()
local co = coroutine.running()
if co then
return M._threads[co]
end
end
---@async
---@param ms number
function M.sleep(ms)
local async = M.running()
assert(async, "Not in an async context")
async:sleep(ms)
end
M.Async = Async
M.new = Async.new
return M

View file

@ -1,100 +0,0 @@
vim.opt.rtp:append(".")
local Rocks = require("lazy.pkg.rockspec")
local Semver = require("lazy.manage.semver")
local Util = require("lazy.util")
local M = {}
M.patterns = { "nvim", "treesitter", "tree-sitter", "cmp", "neo" }
function M.fetch(url, file, prefix)
if not vim.uv.fs_stat(file) then
print((prefix or "") .. "Fetching " .. url .. " to " .. file .. "\n")
vim.cmd.redraw()
local out = vim.fn.system("wget " .. url .. " -O " .. file)
if vim.v.shell_error ~= 0 then
pcall(vim.uv.fs_unlink, file)
error("Failed to fetch " .. url .. ":\n" .. out)
end
end
end
---@return RockManifest?
function M.fetch_manifest()
local manifest_file = "build/manifest.lua"
M.fetch("https://luarocks.org/manifest-5.1", manifest_file)
return Rocks.parse(manifest_file)
end
function M.fetch_rockspec(name, version, prefix)
version = version or "scm-1"
local url = "https://luarocks.org/" .. name .. "-" .. version .. ".rockspec"
M.fetch(url, "build/" .. name .. ".rockspec", prefix)
end
function M.build()
vim.fn.mkdir("build", "p")
local manifest = M.fetch_manifest() or {}
---@type {name:string, version:string, url:string}[]
local nvim_rocks = {}
for rock, vv in pairs(manifest.repository or {}) do
local matches = false
for _, pattern in ipairs(M.patterns) do
if rock:find(pattern, 1, true) then
matches = true
break
end
end
if matches then
local versions = vim.tbl_map(Semver.version, vim.tbl_keys(vv))
versions = vim.tbl_filter(function(v)
return not not v
end, versions)
local last = Semver.last(versions) or next(vv)
last = type(last) == "table" and last.input or last
table.insert(nvim_rocks, { name = rock, version = last })
end
end
table.sort(nvim_rocks, function(a, b)
return a.name < b.name
end)
for r, rock in ipairs(nvim_rocks) do
local progress = string.format("[%d/%d] ", r, #nvim_rocks)
local ok, err = pcall(M.fetch_rockspec, rock.name, rock.version, progress)
if not ok then
err = vim.trim("Error: " .. err)
local lines = vim.split(err, "\n")
lines = vim.tbl_map(function(line)
return " " .. line
end, lines)
print(table.concat(lines, "\n") .. "\n")
end
end
for _, rock in ipairs(nvim_rocks) do
local rockspec = Rocks.rockspec("build/" .. rock.name .. ".rockspec")
if rockspec then
local url = rockspec.source and rockspec.source.url
-- parse github short url
if url and url:find("://github.com/") then
url = url:gsub("^.*://github.com/", "")
local parts = vim.split(url, "/")
url = parts[1] .. "/" .. parts[2]
url = url:gsub("%.git$", "")
end
if url then
rock.url = url
print(rock.name .. " " .. url)
else
print("Error: " .. rock.name .. " missing source url\n\n")
print(vim.inspect(rockspec) .. "\n")
end
end
end
Util.write_file("lua/lazy/community/_generated.lua", "return \n" .. vim.inspect(nvim_rocks))
end
M.build()
return M

File diff suppressed because it is too large Load diff

View file

@ -1,28 +0,0 @@
local M = {}
---@type table<string, string>
local mapping = nil
local function load()
if not mapping then
mapping = {}
---@type {name:string, url:string, version:string}[]
local gen = require("lazy.community._generated")
for _, rock in ipairs(gen) do
mapping[rock.name] = rock.url
end
end
return mapping
end
---@param rock string
---@return string?
function M.get_url(rock)
return load()[rock]
end
function M.get_spec(name)
return require("lazy.community.specs")[name]
end
return M

View file

@ -1,7 +0,0 @@
---@type table<string, LazySpec>
return {
["plenary.nvim"] = {
"nvim-lua/plenary.nvim",
lazy = true,
},
}

View file

@ -1,133 +1,414 @@
local uv = vim.uv or vim.loop
local ffi = require("ffi")
---@diagnostic disable-next-line: no-unknown
local uv = vim.loop
local M = {}
M.dirty = false
M.VERSION = "1" .. jit.version
---@class LazyCacheConfig
M.config = {
enabled = true,
path = vim.fn.stdpath("cache") .. "/lazy/cache",
-- Once one of the following events triggers, caching will be disabled.
-- To cache all modules, set this to `{}`, but that is not recommended.
-- The default is to disable on:
-- * VimEnter: not useful to cache anything else beyond startup
-- * BufReadPre: this will be triggered early when opening a file from the command line directly
disable_events = { "VimEnter", "BufReadPre" },
ttl = 3600 * 24 * 5, -- keep unused modules for up to 5 days
}
M.debug = false
---@type CacheHash
local cache_hash
---@alias CacheHash {mtime: {sec:number, nsec:number}, size:number}
---@alias CacheEntry {hash:CacheHash, chunk:string}
---@class ModuleFindOpts
---@field all? boolean Search for all matches (defaults to `false`)
---@field rtp? boolean Search for modname in the runtime path (defaults to `true`)
---@field patterns? string[] Patterns to use (defaults to `{"/init.lua", ".lua"}`)
---@field paths? string[] Extra paths to search for modname
---@class ModuleInfo
---@field modpath string Path of the module
---@field modname string Name of the module
---@field stat? uv_fs_t File stat of the module path
---@alias LoaderStats table<string, {total:number, time:number, [string]:number?}?>
M.path = vim.fn.stdpath("cache") .. "/luac"
M.enabled = false
---@class Loader
---@field _rtp string[]
---@field _rtp_pure string[]
---@field _rtp_key string
local Loader = {
VERSION = 3,
---@type table<string, table<string,ModuleInfo>>
_indexed = {},
---@type table<string, string[]>
_topmods = {},
_loadfile = loadfile,
---@type LoaderStats
_stats = {
find = { total = 0, time = 0, not_found = 0 },
},
---@alias CacheEntry {hash:CacheHash, modpath:string, chunk:string, used:number}
---@type table<string,CacheEntry?>
M.cache = {}
M.enabled = true
---@type string[]
M.rtp = nil
M.rtp_total = 0
M.stats = {
find = { total = 0, time = 0, rtp = 0, unloaded = 0, index = 0, stat = 0, not_found = 0 },
autoload = { total = 0, time = 0 },
}
M.me = debug.getinfo(1, "S").source:sub(2)
M.me = vim.fn.fnamemodify(M.me, ":p:h:h:h:h"):gsub("\\", "/")
---@type table<string, table<string,string>>
M.topmods = { lazy = { [M.me] = M.me } }
---@type table<string, true>
M.indexed = { [M.me] = true }
M.indexed_unloaded = false
M.indexed_rtp = 0
-- selene:allow(global_usage)
M._loadfile = _G.loadfile
--- Tracks the time spent in a function
---@private
function Loader.track(stat, start)
Loader._stats[stat] = Loader._stats[stat] or { total = 0, time = 0 }
Loader._stats[stat].total = Loader._stats[stat].total + 1
Loader._stats[stat].time = Loader._stats[stat].time + uv.hrtime() - start
-- checks whether the cached modpath is still valid
function M.check_path(modname, modpath)
-- HACK: never return packer paths
if modpath:find("/site/pack/packer/", 1, true) then
return false
end
--- slightly faster/different version than vim.fs.normalize
--- we also need to have it here, since the loader will load vim.fs
---@private
function Loader.normalize(path)
if path:sub(1, 1) == "~" then
local home = uv.os_homedir() or "~"
if home:sub(-1) == "\\" or home:sub(-1) == "/" then
home = home:sub(1, -2)
-- check rtp excluding plugins. This is a very small list, so should be fast
for _, path in ipairs(M.get_rtp()) do
if modpath:find(path, 1, true) == 1 then
return true
end
path = home .. path:sub(2)
end
path = path:gsub("\\", "/"):gsub("/+", "/")
return path:sub(-1) == "/" and path:sub(1, -2) or path
end
--- Gets the rtp excluding after directories.
--- The result is cached, and will be updated if the runtime path changes.
--- When called from a fast event, the cached value will be returned.
--- @return string[] rtp, boolean updated
---@private
function Loader.get_rtp()
-- the correct lazy path should be part of rtp.
-- so if we get here, this is folke using the local dev instance ;)
if modname:sub(1, 4) == "lazy" then
return false
end
return M.check_autoload(modname, modpath)
end
function M.check_autoload(modname, modpath)
local start = uv.hrtime()
if vim.in_fast_event() then
Loader.track("get_rtp", start)
return (Loader._rtp or {}), false
M.stats.autoload.total = M.stats.autoload.total + 1
-- check plugins. Again fast, since we check the plugin name from the path.
-- only needed when the plugin mod has been loaded
---@type LazyCorePlugin
local Plugin = package.loaded["lazy.core.plugin"]
if Plugin then
local plugin = Plugin.find(modpath)
if plugin and modpath:find(plugin.dir, 1, true) == 1 then
-- we're not interested in loader time, so calculate delta here
M.stats.autoload.time = M.stats.autoload.time + uv.hrtime() - start
if not plugin._.loaded then
if plugin.module == false then
error("Plugin " .. plugin.name .. " is not loaded and is configured with module=false")
end
require("lazy.core.loader").load(plugin, { require = modname })
end
return true
end
end
M.stats.autoload.time = M.stats.autoload.time + uv.hrtime() - start
return false
end
function M.disable()
if not M.enabled then
return
end
-- selene:allow(global_usage)
_G.loadfile = M._loadfile
M.enabled = false
end
function M.check_loaded(modname)
---@diagnostic disable-next-line: no-unknown
local mod = package.loaded[modname]
if type(mod) == "table" then
return function()
return mod
end
end
end
---@param modname string
---@return fun()|string
function M.loader(modname)
modname = modname:gsub("/", ".")
local entry = M.cache[modname]
local chunk, err
if entry and M.check_path(modname, entry.modpath) then
M.stats.find.total = M.stats.find.total + 1
chunk, err = M.load(modname, entry.modpath)
end
if not chunk then
-- find the modpath and load the module
local modpath = M.find(modname)
if modpath then
M.check_autoload(modname, modpath)
if M.enabled then
chunk, err = M.load(modname, modpath)
else
chunk = M.check_loaded(modname)
if not chunk then
chunk, err = M._loadfile(modpath)
end
end
end
end
return chunk or err or ("module " .. modname .. " not found")
end
---@param modpath string
---@return any, string?
function M.loadfile(modpath)
modpath = modpath:gsub("\\", "/")
return M.load(modpath, modpath)
end
---@param modkey string
---@param modpath string
---@return function?, string? error_message
function M.load(modkey, modpath)
local chunk, err
chunk = M.check_loaded(modkey)
if chunk then
return chunk
end
modpath = modpath:gsub("\\", "/")
local hash = M.hash(modpath)
if not hash then
-- trigger correct error
return M._loadfile(modpath)
end
local entry = M.cache[modkey]
if entry then
entry.modpath = modpath
entry.used = os.time()
if M.eq(entry.hash, hash) then
-- found in cache and up to date
chunk, err = loadstring(entry.chunk --[[@as string]], "@" .. entry.modpath)
if not (err and err:find("cannot load incompatible bytecode", 1, true)) then
return chunk, err
end
end
else
entry = { hash = hash, modpath = modpath, used = os.time() }
M.cache[modkey] = entry
end
entry.hash = hash
if M.debug and M.enabled then
vim.schedule(function()
vim.notify("[cache:load] " .. modpath)
end)
end
chunk, err = M._loadfile(entry.modpath)
if chunk then
M.dirty = true
entry.chunk = string.dump(chunk)
end
return chunk, err
end
function M.require(modname)
local chunk = M.loader(modname)
if type(chunk) == "string" then
error(chunk)
end
---@diagnostic disable-next-line: no-unknown
local mod = chunk()
---@diagnostic disable-next-line: no-unknown
package.loaded[modname] = mod
return mod
end
-- index the top-level lua modules for this path
function M._index(path)
if not M.indexed[path] and path:sub(-6, -1) ~= "/after" then
M.stats.find.index = M.stats.find.index + 1
---@type LazyUtilCore
local Util = package.loaded["lazy.core.util"]
if not Util then
return false
end
M.indexed[path] = true
Util.ls(path .. "/lua", function(_, name, t)
local topname
if name:sub(-4) == ".lua" then
topname = name:sub(1, -5)
elseif t == "link" or t == "directory" then
topname = name
end
if topname then
M.topmods[topname] = M.topmods[topname] or {}
M.topmods[topname][path] = path
end
end)
return true
end
return false
end
---@param modname string
---@return string?
function M.find(modname)
M.stats.find.total = M.stats.find.total + 1
local start = uv.hrtime()
local basename = modname:gsub("%.", "/")
local idx = modname:find(".", 1, true)
local topmod = idx and modname:sub(1, idx - 1) or modname
-- search for a directory first when topmod == modname
local patterns = topmod == modname and { "/init.lua", ".lua" } or { ".lua", "/init.lua" }
-- check top-level mods to find the module
local function _find()
for _, toppath in pairs(M.topmods[topmod] or {}) do
for _, pattern in ipairs(patterns) do
local path = toppath .. "/lua/" .. basename .. pattern
M.stats.find.stat = M.stats.find.stat + 1
if uv.fs_stat(path) then
return path
end
end
end
end
local modpath = _find()
if not modpath then
-- update rtp
local rtp = M.list_rtp()
if #rtp ~= M.indexed_rtp then
M.indexed_rtp = #rtp
local updated = false
local key = vim.go.rtp
if key ~= Loader._rtp_key then
Loader._rtp = {}
for _, path in ipairs(vim.api.nvim_get_runtime_file("", true)) do
path = Loader.normalize(path)
-- skip after directories
if path:sub(-6, -1) ~= "/after" and not (Loader._indexed[path] and vim.tbl_isempty(Loader._indexed[path])) then
Loader._rtp[#Loader._rtp + 1] = path
for _, path in ipairs(rtp) do
updated = M._index(path) or updated
end
if updated then
modpath = _find()
end
updated = true
Loader._rtp_key = key
end
Loader.track("get_rtp", start)
return Loader._rtp, updated
end
--- Returns the cache file name
---@param name string can be a module name, or a file name
---@return string file_name
---@private
function Loader.cache_file(name)
local ret = M.path .. "/" .. name:gsub("[/\\:]", "%%")
return ret:sub(-4) == ".lua" and (ret .. "c") or (ret .. ".luac")
-- update unloaded
if not modpath and not M.indexed_unloaded then
M.indexed_unloaded = true
local updated = false
---@type LazyCoreConfig
local Config = package.loaded["lazy.core.config"]
if Config then
for _, plugin in pairs(Config.plugins) do
if not (M.indexed[plugin.dir] or plugin._.loaded or plugin.module == false) then
updated = M._index(plugin.dir) or updated
end
end
end
if updated then
modpath = _find()
end
end
--- Saves the cache entry for a given module or file
---@param name string module name or filename
---@param entry CacheEntry
---@private
function Loader.write(name, entry)
local cname = Loader.cache_file(name)
local f = assert(uv.fs_open(cname, "w", 438))
-- module not found
if not modpath then
M.stats.find.not_found = M.stats.find.not_found + 1
end
end
M.stats.find.time = M.stats.find.time + uv.hrtime() - start
return modpath
end
-- returns the cached RTP excluding plugin dirs
function M.get_rtp()
local rtp = M.list_rtp()
if not M.rtp or #rtp ~= M.rtp_total then
M.rtp_total = #rtp
M.rtp = {}
---@type table<string,true>
local skip = {}
-- only skip plugins once Config has been setup
---@type LazyCoreConfig
local Config = package.loaded["lazy.core.config"]
if Config then
for _, plugin in pairs(Config.plugins) do
if plugin.name ~= "lazy.nvim" then
skip[plugin.dir] = true
end
end
end
for _, path in ipairs(rtp) do
---@type string
path = path:gsub("\\", "/")
if not skip[path] and not path:find("after/?$") then
M.rtp[#M.rtp + 1] = path
end
end
end
return M.rtp
end
function M.list_rtp()
return vim.api.nvim_get_runtime_file("", true)
end
---@param opts? LazyConfig
function M.setup(opts)
-- no fancy deep extend here. just set the options
if opts and opts.performance and opts.performance.cache then
---@diagnostic disable-next-line: no-unknown
for k, v in pairs(opts.performance.cache) do
---@diagnostic disable-next-line: no-unknown
M.config[k] = v
end
end
M.debug = opts and opts.debug
M.enabled = M.config.enabled
if M.enabled then
table.insert(package.loaders, 2, M.loader)
M.load_cache()
-- selene:allow(global_usage)
_G.loadfile = M.loadfile
if #M.config.disable_events > 0 then
vim.api.nvim_create_autocmd(M.config.disable_events, { once = true, callback = M.disable })
end
else
-- we need to always add the loader since this will autoload unloaded modules
table.insert(package.loaders, M.loader)
end
return M
end
---@return CacheHash?
function M.hash(file)
local ok, ret = pcall(uv.fs_stat, file)
return ok and ret or nil
end
---@param h1 CacheHash
---@param h2 CacheHash
function M.eq(h1, h2)
return h1 and h2 and h1.size == h2.size and h1.mtime.sec == h2.mtime.sec and h1.mtime.nsec == h2.mtime.nsec
end
function M.save_cache()
vim.fn.mkdir(vim.fn.fnamemodify(M.config.path, ":p:h"), "p")
local f = assert(uv.fs_open(M.config.path, "w", 438))
uv.fs_write(f, M.VERSION)
uv.fs_write(f, "\0")
for modname, entry in pairs(M.cache) do
if entry.used > os.time() - M.config.ttl then
entry.modname = modname
local header = {
Loader.VERSION,
entry.hash.size,
entry.hash.mtime.sec,
entry.hash.mtime.nsec,
#modname,
#entry.chunk,
#entry.modpath,
entry.used,
}
uv.fs_write(f, table.concat(header, ",") .. "\0")
uv.fs_write(f, ffi.string(ffi.new("const uint32_t[7]", header), 28))
uv.fs_write(f, modname)
uv.fs_write(f, entry.chunk)
uv.fs_write(f, entry.modpath)
end
end
uv.fs_close(f)
end
--- Loads the cache entry for a given module or file
---@param name string module name or filename
---@return CacheEntry?
---@private
function Loader.read(name)
local start = uv.hrtime()
local cname = Loader.cache_file(name)
local f = uv.fs_open(cname, "r", 438)
function M.load_cache()
M.cache = {}
local f = uv.fs_open(M.config.path, "r", 438)
if f then
local hash = uv.fs_fstat(f) --[[@as CacheHash]]
local data = uv.fs_read(f, hash.size, 0) --[[@as string]]
cache_hash = uv.fs_fstat(f) --[[@as CacheHash]]
local data = uv.fs_read(f, cache_hash.size, 0) --[[@as string]]
uv.fs_close(f)
local zero = data:find("\0", 1, true)
@ -135,394 +416,42 @@ function Loader.read(name)
return
end
---@type integer[]|{[0]:integer}
local header = vim.split(data:sub(1, zero - 1), ",")
if tonumber(header[1]) ~= Loader.VERSION then
if M.VERSION ~= data:sub(1, zero - 1) then
return
end
Loader.track("read", start)
return {
hash = { size = tonumber(header[2]), mtime = { sec = tonumber(header[3]), nsec = tonumber(header[4]) } },
chunk = data:sub(zero + 1),
local offset = zero + 1
while offset + 1 < #data do
local header = ffi.cast("uint32_t*", ffi.new("const char[28]", data:sub(offset, offset + 27)))
offset = offset + 28
local modname = data:sub(offset, offset + header[3] - 1)
offset = offset + header[3]
local chunk = data:sub(offset, offset + header[4] - 1)
offset = offset + header[4]
local file = data:sub(offset, offset + header[5] - 1)
offset = offset + header[5]
M.cache[modname] = {
hash = { size = header[0], mtime = { sec = header[1], nsec = header[2] } },
chunk = chunk,
modpath = file,
used = header[6],
}
end
Loader.track("read", start)
end
--- The `package.loaders` loader for lua files using the cache.
---@param modname string module name
---@return string|function
---@private
function Loader.loader(modname)
local start = uv.hrtime()
local ret = M.find(modname)[1]
if ret then
local chunk, err = Loader.load(ret.modpath, { hash = ret.stat })
Loader.track("loader", start)
return chunk or error(err)
end
Loader.track("loader", start)
return "\ncache_loader: module " .. modname .. " not found"
end
--- The `package.loaders` loader for libs
---@param modname string module name
---@return string|function
---@private
function Loader.loader_lib(modname)
local start = uv.hrtime()
local sysname = uv.os_uname().sysname:lower() or ""
local is_win = sysname:find("win", 1, true) and not sysname:find("darwin", 1, true)
local ret = M.find(modname, { patterns = is_win and { ".dll" } or { ".so" } })[1]
---@type function?, string?
if ret then
-- Making function name in Lua 5.1 (see src/loadlib.c:mkfuncname) is
-- a) strip prefix up to and including the first dash, if any
-- b) replace all dots by underscores
-- c) prepend "luaopen_"
-- So "foo-bar.baz" should result in "luaopen_bar_baz"
local dash = modname:find("-", 1, true)
local funcname = dash and modname:sub(dash + 1) or modname
local chunk, err = package.loadlib(ret.modpath, "luaopen_" .. funcname:gsub("%.", "_"))
Loader.track("loader_lib", start)
return chunk or error(err)
end
Loader.track("loader_lib", start)
return "\ncache_loader_lib: module " .. modname .. " not found"
end
--- `loadfile` using the cache
---@param filename? string
---@param mode? "b"|"t"|"bt"
---@param env? table
---@param hash? CacheHash
---@return function?, string? error_message
---@private
-- luacheck: ignore 312
function Loader.loadfile(filename, mode, env, hash)
local start = uv.hrtime()
filename = Loader.normalize(filename)
mode = nil -- ignore mode, since we byte-compile the lua source files
local chunk, err = Loader.load(filename, { mode = mode, env = env, hash = hash })
Loader.track("loadfile", start)
return chunk, err
end
--- Checks whether two cache hashes are the same based on:
--- * file size
--- * mtime in seconds
--- * mtime in nanoseconds
---@param h1 CacheHash
---@param h2 CacheHash
---@private
function Loader.eq(h1, h2)
return h1 and h2 and h1.size == h2.size and h1.mtime.sec == h2.mtime.sec and h1.mtime.nsec == h2.mtime.nsec
end
--- Loads the given module path using the cache
---@param modpath string
---@param opts? {hash?: CacheHash, mode?: "b"|"t"|"bt", env?:table} (table|nil) Options for loading the module:
--- - hash: (table) the hash of the file to load if it is already known. (defaults to `vim.uv.fs_stat({modpath})`)
--- - mode: (string) the mode to load the module with. "b"|"t"|"bt" (defaults to `nil`)
--- - env: (table) the environment to load the module in. (defaults to `nil`)
---@see |luaL_loadfile()|
---@return function?, string? error_message
---@private
function Loader.load(modpath, opts)
local start = uv.hrtime()
opts = opts or {}
local hash = opts.hash or uv.fs_stat(modpath)
---@type function?, string?
local chunk, err
if not hash then
-- trigger correct error
chunk, err = Loader._loadfile(modpath, opts.mode, opts.env)
Loader.track("load", start)
return chunk, err
end
local entry = Loader.read(modpath)
if entry and Loader.eq(entry.hash, hash) then
-- found in cache and up to date
-- selene: allow(incorrect_standard_library_use)
chunk, err = load(entry.chunk --[[@as string]], "@" .. modpath, opts.mode, opts.env)
if not (err and err:find("cannot load incompatible bytecode", 1, true)) then
Loader.track("load", start)
return chunk, err
end
end
entry = { hash = hash, modpath = modpath }
chunk, err = Loader._loadfile(modpath, opts.mode, opts.env)
if chunk then
entry.chunk = string.dump(chunk)
Loader.write(modpath, entry)
end
Loader.track("load", start)
return chunk, err
end
--- Finds lua modules for the given module name.
---@param modname string Module name, or `"*"` to find the top-level modules instead
---@param opts? ModuleFindOpts (table|nil) Options for finding a module:
--- - rtp: (boolean) Search for modname in the runtime path (defaults to `true`)
--- - paths: (string[]) Extra paths to search for modname (defaults to `{}`)
--- - patterns: (string[]) List of patterns to use when searching for modules.
--- A pattern is a string added to the basename of the Lua module being searched.
--- (defaults to `{"/init.lua", ".lua"}`)
--- - all: (boolean) Return all matches instead of just the first one (defaults to `false`)
---@return ModuleInfo[] (list) A list of results with the following properties:
--- - modpath: (string) the path to the module
--- - modname: (string) the name of the module
--- - stat: (table|nil) the fs_stat of the module path. Won't be returned for `modname="*"`
function M.find(modname, opts)
local start = uv.hrtime()
opts = opts or {}
modname = modname:gsub("/", ".")
local basename = modname:gsub("%.", "/")
local idx = modname:find(".", 1, true)
-- HACK: fix incorrect require statements. Really not a fan of keeping this,
-- but apparently the regular lua loader also allows this
if idx == 1 then
modname = modname:gsub("^%.+", "")
basename = modname:gsub("%.", "/")
idx = modname:find(".", 1, true)
end
-- get the top-level module name
local topmod = idx and modname:sub(1, idx - 1) or modname
-- OPTIM: search for a directory first when topmod == modname
local patterns = opts.patterns or (topmod == modname and { "/init.lua", ".lua" } or { ".lua", "/init.lua" })
for p, pattern in ipairs(patterns) do
patterns[p] = "/lua/" .. basename .. pattern
end
---@type ModuleInfo[]
local results = {}
-- Only continue if we haven't found anything yet or we want to find all
---@private
local function continue()
return #results == 0 or opts.all
end
-- Checks if the given paths contain the top-level module.
-- If so, it tries to find the module path for the given module name.
---@param paths string[]
---@private
local function _find(paths)
for _, path in ipairs(paths) do
if topmod == "*" then
for _, r in pairs(Loader.lsmod(path)) do
results[#results + 1] = r
if not continue() then
return
end
end
elseif Loader.lsmod(path)[topmod] then
for _, pattern in ipairs(patterns) do
local modpath = path .. pattern
Loader._stats.find.stat = (Loader._stats.find.stat or 0) + 1
local hash = uv.fs_stat(modpath)
if hash then
results[#results + 1] = { modpath = modpath, stat = hash, modname = modname }
if not continue() then
return
end
end
end
end
end
end
-- always check the rtp first
if opts.rtp ~= false then
_find(Loader._rtp or {})
if continue() then
local rtp, updated = Loader.get_rtp()
if updated then
_find(rtp)
function M.autosave()
vim.api.nvim_create_autocmd("VimLeavePre", {
callback = function()
if M.dirty then
local hash = M.hash(M.config.path)
-- abort when the file was changed in the meantime
if hash == nil or M.eq(cache_hash, hash) then
M.save_cache()
end
end
end
-- check any additional paths
if continue() and opts.paths then
_find(opts.paths)
end
Loader.track("find", start)
if #results == 0 then
-- module not found
Loader._stats.find.not_found = Loader._stats.find.not_found + 1
end
return results
end
--- Resets the topmods cache for the path, or all the paths
--- if path is nil.
---@param path string? path to reset
function M.reset(path)
if path then
Loader._indexed[Loader.normalize(path)] = nil
else
Loader._indexed = {}
end
end
--- Enables the experimental Lua module loader:
--- * overrides loadfile
--- * adds the lua loader using the byte-compilation cache
--- * adds the libs loader
--- * removes the default Neovim loader
function M.enable()
if M.enabled then
return
end
M.enabled = true
vim.fn.mkdir(vim.fn.fnamemodify(M.path, ":p"), "p")
-- selene: allow(global_usage)
_G.loadfile = Loader.loadfile
-- add lua loader
table.insert(package.loaders, 2, Loader.loader)
-- add libs loader
table.insert(package.loaders, 3, Loader.loader_lib)
-- remove Neovim loader
for l, loader in ipairs(package.loaders) do
if loader == vim._load_package then
table.remove(package.loaders, l)
break
end
end
-- this will reset the top-mods in case someone adds a new
-- top-level lua module to a path already on the rtp
vim.api.nvim_create_autocmd("BufWritePost", {
group = vim.api.nvim_create_augroup("cache_topmods_reset", { clear = true }),
callback = function(event)
local bufname = event.match ---@type string
local idx = bufname:find("/lua/", 1, true)
if idx then
M.reset(bufname:sub(1, idx - 1))
end
end,
})
end
--- Disables the experimental Lua module loader:
--- * removes the loaders
--- * adds the default Neovim loader
function M.disable()
if not M.enabled then
return
end
M.enabled = false
-- selene: allow(global_usage)
_G.loadfile = Loader._loadfile
---@diagnostic disable-next-line: no-unknown
for l, loader in ipairs(package.loaders) do
if loader == Loader.loader or loader == Loader.loader_lib then
table.remove(package.loaders, l)
end
end
table.insert(package.loaders, 2, vim._load_package)
vim.api.nvim_del_augroup_by_name("cache_topmods_reset")
end
--- Return the top-level `/lua/*` modules for this path
---@param path string path to check for top-level lua modules
---@private
function Loader.lsmod(path)
if not Loader._indexed[path] then
local start = uv.hrtime()
Loader._indexed[path] = {}
local handle = uv.fs_scandir(path .. "/lua")
while handle do
local name, t = uv.fs_scandir_next(handle)
if not name then
break
end
local modpath = path .. "/lua/" .. name
-- HACK: type is not always returned due to a bug in luv
t = t or uv.fs_stat(modpath).type
---@type string
local topname
local ext = name:sub(-4)
if ext == ".lua" or ext == ".dll" then
topname = name:sub(1, -5)
elseif name:sub(-3) == ".so" then
topname = name:sub(1, -4)
elseif t == "link" or t == "directory" then
topname = name
end
if topname then
Loader._indexed[path][topname] = { modpath = modpath, modname = topname }
Loader._topmods[topname] = Loader._topmods[topname] or {}
if not vim.tbl_contains(Loader._topmods[topname], path) then
table.insert(Loader._topmods[topname], path)
end
end
end
Loader.track("lsmod", start)
end
return Loader._indexed[path]
end
--- Debug function that wraps all loaders and tracks stats
---@private
function M._profile_loaders()
for l, loader in pairs(package.loaders) do
local loc = debug.getinfo(loader, "Sn").source:sub(2)
package.loaders[l] = function(modname)
local start = uv.hrtime()
local ret = loader(modname)
Loader.track("loader " .. l .. ": " .. loc, start)
Loader.track("loader_all", start)
return ret
end
end
end
--- Prints all cache stats
---@param opts? {print?:boolean}
---@return LoaderStats
---@private
function M._inspect(opts)
if opts and opts.print then
---@private
local function ms(nsec)
return math.floor(nsec / 1e6 * 1000 + 0.5) / 1000 .. "ms"
end
local chunks = {} ---@type string[][]
---@type string[]
local stats = vim.tbl_keys(Loader._stats)
table.sort(stats)
for _, stat in ipairs(stats) do
vim.list_extend(chunks, {
{ "\n" .. stat .. "\n", "Title" },
{ "* total: " },
{ tostring(Loader._stats[stat].total) .. "\n", "Number" },
{ "* time: " },
{ ms(Loader._stats[stat].time) .. "\n", "Bold" },
{ "* avg time: " },
{ ms(Loader._stats[stat].time / Loader._stats[stat].total) .. "\n", "Bold" },
})
for k, v in pairs(Loader._stats[stat]) do
if not vim.tbl_contains({ "time", "total" }, k) then
chunks[#chunks + 1] = { "* " .. k .. ":" .. string.rep(" ", 9 - #k) }
chunks[#chunks + 1] = { tostring(v) .. "\n", "Number" }
end
end
end
vim.api.nvim_echo(chunks, true, {})
end
return Loader._stats
end
return M

View file

@ -7,73 +7,24 @@ 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?
-- 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 = 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,
concurrency = nil, ---@type number limit the maximum amount of concurrent tasks
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
-- log = { "-10" }, -- show the last 10 commits
log = { "--since=3 days ago" }, -- show commits from the last 3 days
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,
-- rate of network related git operations (clone, fetch, checkout)
throttle = {
enabled = false, -- not enabled by default
-- max 2 ops every 5 seconds
rate = 2,
duration = 5 * 1000, -- in ms
},
-- Time in seconds to wait before running fetch again for a plugin.
-- Repeated update/check operations will not run again until this
-- cooldown period has passed.
cooldown = 0,
},
pkg = {
enabled = true,
cache = vim.fn.stdpath("state") .. "/lazy/pkg-cache.lua",
-- the first package source that is found for a plugin will be used.
sources = {
"lazy",
"rockspec", -- will only be used when rocks.enabled is true
"packspec",
},
},
rocks = {
enabled = true,
root = vim.fn.stdpath("data") .. "/lazy-rocks",
server = "https://nvim-neorocks.github.io/rocks-binaries/",
-- use hererocks to install luarocks?
-- set to `nil` to use hererocks when luarocks is not found
-- set to `true` to always use hererocks
-- set to `false` to always use luarocks
hererocks = nil,
},
dev = {
-- Directory where you store your local plugin projects. If a function is used,
-- the plugin directory (e.g. `~/projects/plugin-name`) must be returned.
---@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.
@ -84,34 +35,23 @@ M.defaults = {
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 = "",
debug = "",
event = "",
favorite = "",
ft = "",
init = "",
import = "",
keys = "",
lazy = "󰒲 ",
loaded = "",
not_loaded = "",
cmd = "",
config = "",
event = "",
ft = "",
init = "",
keys = "",
plugin = "",
runtime = "",
require = "󰢱 ",
source = "",
start = "",
task = "",
lazy = "",
list = {
"",
"",
@ -119,55 +59,32 @@ M.defaults = {
"",
},
},
-- 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 = 1000 / 30, -- how frequently should the ui process render events
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.
-- you can define custom key maps here.
-- To disable one of the defaults, set it to false
["<localleader>l"] = {
function(plugin)
require("lazy.util").float_term({ "lazygit", "log" }, {
-- open lazygit log
["<localleader>l"] = function(plugin)
require("lazy.util").open_cmd({ "lazygit", "log" }, {
cwd = plugin.dir,
terminal = true,
close_on_exit = true,
enter = true,
})
end,
desc = "Open lazygit log",
},
["<localleader>i"] = {
function(plugin)
Util.notify(vim.inspect(plugin), {
title = "Inspect " .. plugin.name,
lang = "lua",
})
end,
desc = "Inspect Plugin",
},
["<localleader>t"] = {
function(plugin)
require("lazy.util").float_term(nil, {
-- open a terminal for the plugin dir
["<localleader>t"] = function(plugin)
require("lazy.util").open_cmd({ vim.go.shell }, {
cwd = plugin.dir,
terminal = true,
close_on_exit = true,
enter = true,
})
end,
desc = "Open terminal in plugin dir",
},
},
},
-- Output options for headless mode
headless = {
-- show the output from process commands like git
process = true,
-- show log messages
log = true,
-- show task start/end
task = true,
-- use ansi colors
colors = true,
},
diff = {
-- diff command <d> can be one of:
-- * browser: opens the github compare view. Note that this is always mapped to <K> as well,
@ -183,7 +100,6 @@ M.defaults = {
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
@ -191,14 +107,13 @@ M.defaults = {
notify = true, -- get a notification when changes are found
},
performance = {
cache = {
enabled = true,
},
---@type LazyCacheConfig
cache = nil,
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
paths = {}, -- add any custom paths here that you want to indluce in the rtp
---@type string[] list any plugins you want to disable here
disabled_plugins = {
-- "gzip",
@ -216,37 +131,17 @@ M.defaults = {
-- 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 don't have docs
files = { "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
-- 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,
},
debug = false,
}
function M.hererocks()
if M.options.rocks.hererocks == nil then
M.options.rocks.hererocks = vim.fn.executable("luarocks") == 0
end
return M.options.rocks.hererocks
end
M.version = "11.17.1" -- x-release-please-version
M.ns = vim.api.nvim_create_namespace("lazy")
---@type LazySpecLoader
---@type string|LazySpec Should be either a string pointing to a module, or a spec
M.spec = nil
---@type table<string, LazyPlugin>
@ -264,51 +159,33 @@ M.me = nil
---@type string
M.mapleader = nil
---@type string
M.maplocalleader = nil
M.suspended = false
function M.headless()
return not M.suspended and #vim.api.nvim_list_uis() == 0
end
M.headless = #vim.api.nvim_list_uis() == 0
---@param spec LazySpec
---@param opts? LazyConfig
function M.setup(opts)
function M.setup(spec, opts)
M.spec = spec
M.options = vim.tbl_deep_extend("force", M.defaults, opts or {})
if type(M.options.spec) == "string" then
M.options.spec = { import = M.options.spec }
end
M.options.performance.cache = require("lazy.core.cache")
table.insert(M.options.install.colorscheme, "habamax")
-- root
M.options.root = Util.norm(M.options.root)
if type(M.options.dev.path) == "string" then
M.options.dev.path = Util.norm(M.options.dev.path)
end
M.options.lockfile = Util.norm(M.options.lockfile)
M.options.readme.root = Util.norm(M.options.readme.root)
vim.fn.mkdir(M.options.root, "p")
if M.options.performance.reset_packpath then
vim.go.packpath = vim.env.VIMRUNTIME
end
M.me = debug.getinfo(1, "S").source:sub(2)
M.me = Util.norm(vim.fn.fnamemodify(M.me, ":p:h:h:h:h"))
local lib = vim.fn.fnamemodify(vim.v.progpath, ":p:h:h") .. "/lib"
lib = vim.uv.fs_stat(lib .. "64") and (lib .. "64") or lib
lib = lib .. "/nvim"
if M.options.performance.rtp.reset then
---@type vim.Option
vim.opt.rtp = {
vim.fn.stdpath("config"),
vim.fn.stdpath("data") .. "/site",
M.me,
vim.env.VIMRUNTIME,
lib,
vim.fn.fnamemodify(vim.v.progpath, ":p:h:h") .. "/lib/nvim",
vim.fn.stdpath("config") .. "/after",
}
end
@ -320,22 +197,21 @@ function M.setup(opts)
-- disable plugin loading since we do all of that ourselves
vim.go.loadplugins = false
M.mapleader = vim.g.mapleader
M.maplocalleader = vim.g.maplocalleader
if M.headless then
require("lazy.view.commands").setup()
else
vim.api.nvim_create_autocmd("UIEnter", {
once = true,
callback = function()
require("lazy.stats").on_ui_enter()
end,
})
if M.headless() then
require("lazy.view.commands").setup()
else
vim.api.nvim_create_autocmd("User", {
pattern = "VeryLazy",
once = true,
callback = function()
require("lazy.core.cache").autosave()
require("lazy.view.commands").setup()
if M.options.change_detection.enabled then
require("lazy.manage.reloader").enable()
@ -345,23 +221,6 @@ 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()
local plugin = require("lazy.core.plugin").find(vim.uv.cwd() .. "/lua/")
if plugin then
require("lazy").pkg({ plugins = { plugin } })
end
end,
})
vim.api.nvim_create_autocmd({ "VimSuspend", "VimResume" }, {
callback = function(ev)
M.suspended = ev.event == "VimSuspend"
end,
})
end,
})
end

View file

@ -1,174 +0,0 @@
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 or 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

@ -1,5 +1,5 @@
local Loader = require("lazy.core.loader")
local Util = require("lazy.core.util")
local Loader = require("lazy.core.loader")
---@class LazyCmdHandler:LazyHandler
local M = {}
@ -14,38 +14,16 @@ end
---@param cmd string
function M:_add(cmd)
vim.api.nvim_create_user_command(cmd, function(event)
local command = {
cmd = cmd,
bang = event.bang or nil,
mods = event.smods,
args = event.fargs,
count = event.count >= 0 and event.range == 0 and event.count or nil,
}
if event.range == 1 then
command.range = { event.line1 }
elseif event.range == 2 then
command.range = { event.line1, event.line2 }
end
---@type string
local plugins = "`" .. table.concat(vim.tbl_values(self.active[cmd]), ", ") .. "`"
self:_load(cmd)
local info = vim.api.nvim_get_commands({})[cmd] or vim.api.nvim_buf_get_commands(0, {})[cmd]
if not info then
vim.schedule(function()
Util.error("Command `" .. cmd .. "` not found after loading " .. plugins)
end)
return
end
command.nargs = info.nargs
if event.args and event.args ~= "" and info.nargs and info.nargs:find("[1?]") then
command.args = { event.args }
end
vim.cmd(command)
vim.cmd(
("%s %s%s%s %s"):format(
event.mods or "",
event.line1 == event.line2 and "" or event.line1 .. "," .. event.line2,
cmd,
event.bang and "!" or "",
event.args or ""
)
)
end, {
bang = true,
range = true,

View file

@ -1,171 +1,86 @@
local Util = require("lazy.core.util")
local Config = require("lazy.core.config")
local Loader = require("lazy.core.loader")
local Util = require("lazy.core.util")
---@class LazyEventOpts
---@field event string
---@field group? string
---@field exclude? string[]
---@field data? any
---@field buffer? number
---@alias LazyEvent {id:string, event:string[]|string, pattern?:string[]|string}
---@alias LazyEventSpec string|{event?:string|string[], pattern?:string|string[]}|string[]
---@class LazyEventHandler:LazyHandler
---@field events table<string,true>
---@field group number
local M = {}
-- Event dependencies
M.triggers = {
FileType = "BufReadPost",
BufReadPost = "BufReadPre",
M.trigger_events = {
BufRead = { "BufReadPre", "BufRead" },
BufReadPost = { "BufReadPre", "BufRead", "BufReadPost" },
}
-- A table of mappings for custom events
-- Can be used by distros to add custom events (see usage in LazyVim)
---@type table<string, LazyEvent>
M.mappings = {
VeryLazy = { id = "VeryLazy", event = "User", pattern = "VeryLazy" },
-- Example:
-- LazyFile = { id = "LazyFile", event = { "BufReadPost", "BufNewFile", "BufWritePre" } },
}
M.mappings["User VeryLazy"] = M.mappings.VeryLazy
M.group = vim.api.nvim_create_augroup("lazy_handler_event", { clear = true })
---@param spec LazyEventSpec
---@return LazyEvent
function M:_parse(spec)
local ret = M.mappings[spec] --[[@as LazyEvent?]]
if ret then
return ret
end
if type(spec) == "string" then
local event, pattern = spec:match("^(%w+)%s+(.*)$")
event = event or spec
return { id = spec, event = event, pattern = pattern }
elseif Util.is_list(spec) then
ret = { id = table.concat(spec, "|"), event = spec }
else
ret = spec --[[@as LazyEvent]]
if not ret.id then
---@diagnostic disable-next-line: assign-type-mismatch, param-type-mismatch
ret.id = type(ret.event) == "string" and ret.event or table.concat(ret.event, "|")
if ret.pattern then
---@diagnostic disable-next-line: assign-type-mismatch, param-type-mismatch
ret.id = ret.id .. " " .. (type(ret.pattern) == "string" and ret.pattern or table.concat(ret.pattern, ", "))
end
end
end
return ret
end
---@param event LazyEvent
function M:_add(event)
local done = false
vim.api.nvim_create_autocmd(event.event, {
---@param value string
function M:_add(value)
local event_spec = self:_event(value)
---@type string?, string?
local event, pattern = event_spec:match("^(%w+)%s+(.*)$")
event = event or event_spec
vim.api.nvim_create_autocmd(event, {
group = self.group,
once = true,
pattern = event.pattern,
callback = function(ev)
if done or not self.active[event.id] then
pattern = pattern,
callback = function()
if not self.active[value] then
return
end
-- HACK: work-around for https://github.com/neovim/neovim/issues/25526
done = true
if event.id ~= "VeryLazy" then
Util.track({ [self.type] = event.id })
end
local state = M.get_state(ev.event, ev.buf, ev.data)
Util.track({ [self.type] = value })
local groups = M.get_augroups(event, pattern)
-- load the plugins
Loader.load(self.active[event.id], { [self.type] = event.id })
Loader.load(self.active[value], { [self.type] = value })
-- check if any plugin created an event handler for this event and fire the group
for _, s in ipairs(state) do
M.trigger(s)
end
if event.id ~= "VeryLazy" then
self:trigger(event, pattern, groups)
Util.track()
end
end,
})
end
-- Get the current state of the event and all the events that will be fired
---@param event string
---@param buf number
---@param data any
function M.get_state(event, buf, data)
local state = {} ---@type LazyEventOpts[]
while event do
table.insert(state, 1, {
event = event,
exclude = event ~= "FileType" and M.get_augroups(event) or nil,
buffer = buf,
data = data,
})
data = nil -- only pass the data to the first event
event = M.triggers[event]
end
return state
---@param value string
function M:_event(value)
return value == "VeryLazy" and "User VeryLazy" or value
end
-- Get all augroups for the events
---@param event string
function M.get_augroups(event)
local groups = {} ---@type string[]
for _, autocmd in ipairs(vim.api.nvim_get_autocmds({ event = event })) do
if autocmd.group_name then
table.insert(groups, autocmd.group_name)
---@param pattern? string
function M.get_augroups(event, pattern)
local events = M.trigger_events[event] or { event }
---@type table<string,true>
local groups = {}
for _, autocmd in ipairs(vim.api.nvim_get_autocmds({ event = events, pattern = pattern })) do
if autocmd.group then
groups[autocmd.group] = true
end
end
return groups
end
-- Trigger an event. When a group is given, only the events in that group will be triggered.
-- When exclude is set, the events in those groups will be skipped.
---@param opts LazyEventOpts
function M.trigger(opts)
if opts.group or opts.exclude == nil then
return M._trigger(opts)
end
local done = {} ---@type table<string,true>
for _, autocmd in ipairs(vim.api.nvim_get_autocmds({ event = opts.event })) do
local id = autocmd.event .. ":" .. (autocmd.group or "") ---@type string
local skip = done[id] or (opts.exclude and vim.tbl_contains(opts.exclude, autocmd.group_name))
done[id] = true
if autocmd.group and not skip then
opts.group = autocmd.group_name
M._trigger(opts)
end
end
end
-- Trigger an event
---@param opts LazyEventOpts
function M._trigger(opts)
---@param event string|string[]
---@param pattern? string
---@param groups table<string,true>
function M:trigger(event, pattern, groups)
local events = M.trigger_events[event] or { event }
---@cast events string[]
for _, e in ipairs(events) do
for _, autocmd in ipairs(vim.api.nvim_get_autocmds({ event = e, pattern = pattern })) do
if autocmd.event == e and autocmd.group and not groups[autocmd.group] then
if Config.options.debug then
Util.info({
"# Firing Events",
" - **event:** " .. opts.event,
opts.group and (" - **group:** " .. opts.group),
opts.buffer and (" - **buffer:** " .. opts.buffer),
" - **group:** `" .. autocmd.group_name .. "`",
" - **event:** " .. autocmd.event,
pattern and (" - **pattern:** " .. pattern),
})
end
Util.track({ event = opts.group or opts.event })
Util.try(function()
vim.api.nvim_exec_autocmds(opts.event, {
buffer = opts.buffer,
group = opts.group,
modeline = false,
data = opts.data,
})
Util.track()
vim.api.nvim_exec_autocmds(autocmd.event, { group = autocmd.group, modeline = false })
end)
end
end
end
end
return M

View file

@ -1,10 +1,17 @@
local Event = require("lazy.core.handler.event")
local Util = require("lazy.core.util")
local Loader = require("lazy.core.loader")
local Config = require("lazy.core.config")
---@class LazyFiletypeHandler:LazyEventHandler
local M = {}
M.extends = Event
---@param value string
function M:_event(value)
return "FileType " .. value
end
---@param plugin LazyPlugin
function M:add(plugin)
self.super.add(self, plugin)
@ -13,13 +20,21 @@ function M:add(plugin)
end
end
---@return LazyEvent
function M:_parse(value)
return {
id = value,
event = "FileType",
pattern = value,
}
---@param pattern? string
function M:trigger(_, pattern, _)
for _, group in ipairs({ "filetypeplugin", "filetypeindent" }) do
Util.try(function()
if Config.options.debug then
Util.info({
"# Firing Events",
" - **group:** `" .. group .. "`",
" - **event:** FileType",
pattern and (" - **pattern:** " .. pattern),
})
end
vim.api.nvim_exec_autocmds("FileType", { group = group, modeline = false, pattern = pattern })
end)
end
end
return M

View file

@ -1,11 +1,9 @@
local Config = require("lazy.core.config")
local Util = require("lazy.core.util")
---@class LazyHandler
---@field type LazyHandlerTypes
---@field extends? LazyHandler
---@field active table<string,table<string,string>>
---@field managed table<string,string> mapping handler keys to plugin names
---@field super LazyHandler
local M = {}
@ -20,38 +18,31 @@ M.types = {
---@type table<string,LazyHandler>
M.handlers = {}
M.did_setup = false
function M.init()
function M.setup()
for _, type in pairs(M.types) do
M.handlers[type] = M.new(type)
end
end
function M.setup()
M.did_setup = true
for _, plugin in pairs(Config.plugins) do
Util.try(function()
M.enable(plugin)
end, "Failed to setup handlers for " .. plugin.name)
end
end
---@param plugin LazyPlugin
function M.disable(plugin)
for type in pairs(plugin._.handlers or {}) do
M.handlers[type]:del(plugin)
for type, handler in pairs(M.handlers) do
if plugin[type] then
handler:del(plugin)
end
end
end
---@param plugin LazyPlugin
function M.enable(plugin)
if not plugin._.loaded then
if not plugin._.handlers then
M.resolve(plugin)
for type, handler in pairs(M.handlers) do
if plugin[type] then
handler:add(plugin)
end
for type in pairs(plugin._.handlers or {}) do
M.handlers[type]:add(plugin)
end
end
end
@ -64,57 +55,30 @@ function M.new(type)
local self = setmetatable({}, { __index = setmetatable(handler, { __index = super }) })
self.super = super
self.active = {}
self.managed = {}
self.type = type
return self
end
---@param _value string
---@param value string
---@protected
function M:_add(_value) end
function M:_add(value) end
---@param _value string
---@param value string
---@protected
function M:_del(_value) end
function M:_del(value) end
---@param value any
---@param _plugin LazyPlugin
---@return string|{id:string}
function M:_parse(value, _plugin)
assert(type(value) == "string", "Expected string, got " .. vim.inspect(value))
---@return string
function M:key(value)
return value
end
---@param values any[]
---@param plugin LazyPlugin
function M:_values(values, plugin)
---@type table<string,any>
local ret = {}
for _, value in ipairs(values) do
local parsed = self:_parse(value, plugin)
ret[type(parsed) == "string" and parsed or parsed.id] = parsed
end
return ret
end
---@param plugin LazyPlugin
function M.resolve(plugin)
local Plugin = require("lazy.core.plugin")
plugin._.handlers = {}
for type, handler in pairs(M.handlers) do
if plugin[type] then
plugin._.handlers[type] = handler:_values(Plugin.values(plugin, type, true), plugin)
end
end
end
---@param plugin LazyPlugin
function M:add(plugin)
for key, value in pairs(plugin._.handlers[self.type] or {}) do
for _, value in ipairs(plugin[self.type] or {}) do
local key = self:key(value)
if not self.active[key] then
self.active[key] = {}
self:_add(value)
self.managed[key] = plugin.name
end
self.active[key][plugin.name] = plugin.name
end
@ -122,10 +86,8 @@ end
---@param plugin LazyPlugin
function M:del(plugin)
if not plugin._.handlers then
return
end
for key, value in pairs(plugin._.handlers[self.type] or {}) do
for _, value in ipairs(plugin[self.type] or {}) do
local key = self:key(value)
if self.active[key] and self.active[key][plugin.name] then
self.active[key][plugin.name] = nil
if vim.tbl_isempty(self.active[key]) then

View file

@ -1,201 +1,95 @@
local Loader = require("lazy.core.loader")
local Util = require("lazy.core.util")
local Loader = require("lazy.core.loader")
---@class LazyKeysBase
---@class LazyKeys
---@field [1] string lhs
---@field [2]? string|fun() rhs
---@field desc? string
---@field mode? string|string[]
---@field noremap? boolean
---@field remap? boolean
---@field expr? boolean
---@field nowait? boolean
---@field ft? string|string[]
---@class LazyKeysSpec: LazyKeysBase
---@field [1] string lhs
---@field [2]? string|fun():string?|false rhs
---@field mode? string|string[]
---@class LazyKeys: LazyKeysBase
---@field lhs string lhs
---@field rhs? string|fun() rhs
---@field mode? string
---@field id string
---@field name string
---@class LazyKeysHandler:LazyHandler
local M = {}
local skip = { mode = true, id = true, ft = true, rhs = true, lhs = true }
---@param value string|LazyKeysSpec
---@param mode? string
---@return LazyKeys
function M.parse(value, mode)
value = type(value) == "string" and { value } or value --[[@as LazyKeysSpec]]
local ret = vim.deepcopy(value) --[[@as LazyKeys]]
ret.lhs = ret[1] or ""
ret.rhs = ret[2]
---@diagnostic disable-next-line: no-unknown
ret[1] = nil
---@diagnostic disable-next-line: no-unknown
ret[2] = nil
ret.mode = mode or "n"
ret.id = vim.api.nvim_replace_termcodes(ret.lhs, true, true, true)
if ret.ft then
local ft = type(ret.ft) == "string" and { ret.ft } or ret.ft --[[@as string[] ]]
ret.id = ret.id .. " (" .. table.concat(ft, ", ") .. ")"
function M.retrigger(keys)
local pending = ""
while true do
---@type number|string
local c = vim.fn.getchar(0)
if c == 0 then
break
end
c = type(c) == "number" and vim.fn.nr2char(c) or c
pending = pending .. c
end
local feed = vim.api.nvim_replace_termcodes(keys, true, false, true) .. pending
vim.api.nvim_feedkeys(feed, "m", false)
end
if ret.mode ~= "n" then
ret.id = ret.id .. " (" .. ret.mode .. ")"
end
---@param value string|LazyKeys
function M.parse(value)
local ret = vim.deepcopy(value)
ret = type(ret) == "string" and { ret } or ret --[[@as LazyKeys]]
ret.mode = ret.mode or "n"
return ret
end
---@param keys LazyKeys
function M.to_string(keys)
return keys.lhs .. (keys.mode == "n" and "" or " (" .. keys.mode .. ")")
end
---@param lhs string
---@param mode? string
function M:have(lhs, mode)
local keys = M.parse(lhs, mode)
return self.managed[keys.id] ~= nil
end
function M:_values(values)
return M.resolve(values)
end
---@param spec? (string|LazyKeysSpec)[]
function M.resolve(spec)
---@type LazyKeys[]
local values = {}
---@diagnostic disable-next-line: no-unknown
for _, value in ipairs(spec or {}) do
value = type(value) == "string" and { value } or value --[[@as LazyKeysSpec]]
value.mode = value.mode or "n"
local modes = (type(value.mode) == "table" and value.mode or { value.mode }) --[=[@as string[]]=]
for _, mode in ipairs(modes) do
local keys = M.parse(value, mode)
if keys.rhs == vim.NIL or keys.rhs == false then
values[keys.id] = nil
else
values[keys.id] = keys
end
end
end
return values
end
---@param keys LazyKeys
function M.opts(keys)
local opts = {} ---@type LazyKeysBase
---@diagnostic disable-next-line: no-unknown
local opts = {}
for k, v in pairs(keys) do
if type(k) ~= "number" and not skip[k] then
---@diagnostic disable-next-line: no-unknown
if type(k) ~= "number" and k ~= "mode" then
opts[k] = v
end
end
return opts
end
---@param keys LazyKeys
function M.is_nop(keys)
return type(keys.rhs) == "string" and (keys.rhs == "" or keys.rhs:lower() == "<nop>")
---@return string
function M:key(value)
if type(value) == "string" then
return value
end
local mode = value.mode or { "n" }
if type(mode) == "string" then
mode = { mode }
end
---@type string
local ret = value[1]
if #mode > 0 then
ret = table.concat(mode, ",") .. ": " .. ret
end
return ret
end
---@param keys LazyKeys
function M:_add(keys)
local lhs = keys.lhs
---@param value string|LazyKeys
function M:_add(value)
local keys = M.parse(value)
local lhs = keys[1]
local opts = M.opts(keys)
---@param buf? number
local function add(buf)
if M.is_nop(keys) then
return self:_set(keys, buf)
end
opts.noremap = true
vim.keymap.set(keys.mode, lhs, function()
local plugins = self.active[keys.id]
local key = self:key(value)
local plugins = self.active[key]
-- always delete the mapping immediately to prevent recursive mappings
self:_del(keys)
self.active[keys.id] = nil
self:_del(value)
self.active[key] = nil
if plugins then
local name = M.to_string(keys)
Util.track({ keys = name })
Loader.load(plugins, { keys = name })
Util.track({ keys = lhs })
Loader.load(plugins, { keys = lhs })
M.retrigger(lhs)
Util.track()
end, opts)
end
if keys.mode:sub(-1) == "a" then
lhs = lhs .. "<C-]>"
end
local feed = vim.api.nvim_replace_termcodes("<Ignore>" .. lhs, true, true, true)
-- insert instead of append the lhs
vim.api.nvim_feedkeys(feed, "i", false)
end, {
desc = opts.desc,
nowait = opts.nowait,
-- we do not return anything, but this is still needed to make operator pending mappings work
expr = true,
buffer = buf,
})
end
-- buffer-local mappings
if keys.ft then
vim.api.nvim_create_autocmd("FileType", {
pattern = keys.ft,
callback = function(event)
if self.active[keys.id] and not M.is_nop(keys) then
add(event.buf)
else
-- Only create the mapping if its managed by lazy
-- otherwise the plugin is supposed to manage it
self:_set(keys, event.buf)
end
end,
})
else
add()
end
end
-- Delete a mapping and create the real global/buffer-local
-- mapping when needed
---@param keys LazyKeys
function M:_del(keys)
-- bufs will be all buffers of the filetype for a buffer-local mapping
-- OR `false` for a global mapping
local bufs = { false }
if keys.ft then
local ft = type(keys.ft) == "string" and { keys.ft } or keys.ft --[[@as string[] ]]
bufs = vim.tbl_filter(function(buf)
return vim.tbl_contains(ft, vim.bo[buf].filetype)
end, vim.api.nvim_list_bufs())
end
for _, buf in ipairs(bufs) do
pcall(vim.keymap.del, keys.mode, keys.lhs, { buffer = buf or nil })
self:_set(keys, buf or nil)
end
end
-- Create a mapping if it is managed by lazy
---@param keys LazyKeys
---@param buf number?
function M:_set(keys, buf)
if keys.rhs then
local opts = M.opts(keys)
---@diagnostic disable-next-line: inject-field
opts.buffer = buf
vim.keymap.set(keys.mode, keys.lhs, keys.rhs, opts)
---@param value string|LazyKeys
function M:_del(value)
local keys = M.parse(value)
pcall(vim.keymap.del, keys.mode, keys[1])
if keys[2] then
vim.keymap.set(keys.mode, keys[1], keys[2], M.opts(keys))
end
end

View file

@ -1,10 +1,7 @@
local Cache = require("lazy.core.cache")
local Util = require("lazy.core.util")
local Config = require("lazy.core.config")
local Handler = require("lazy.core.handler")
local Plugin = require("lazy.core.plugin")
local Util = require("lazy.core.util")
---@class LazyCoreLoader
local M = {}
local DEFAULT_PRIORITY = 50
@ -16,15 +13,15 @@ M.init_done = false
M.disabled_rtp_plugins = { packer_compiled = true }
---@type table<string,string>
M.did_ftdetect = {}
M.did_handlers = false
function M.disable_rtp_plugin(plugin)
M.disabled_rtp_plugins[plugin] = true
end
function M.setup()
-- setup handlers
Util.track("handlers")
Handler.setup()
Util.track()
for _, file in ipairs(Config.options.performance.rtp.disabled_plugins) do
M.disable_rtp_plugin(file)
M.disabled_rtp_plugins[file] = true
end
vim.api.nvim_create_autocmd("ColorSchemePre", {
@ -33,65 +30,21 @@ function M.setup()
end,
})
-- load the plugins
Plugin.load()
Handler.init()
-- install missing plugins
if Config.options.install.missing then
Util.track("install")
local count = 0
while M.install_missing() do
count = count + 1
if count > 5 then
Util.error("Too many rounds of missing plugins")
break
end
end
Util.track()
end
Config.mapleader = vim.g.mapleader
Config.maplocalleader = vim.g.maplocalleader
-- report any warnings & errors
Config.spec:report()
-- setup handlers
Util.track("handlers")
Handler.setup()
M.did_handlers = true
Util.track()
end
-- this will incrementally install missing plugins
-- multiple rounds can happen when importing a spec from a missing plugin
function M.install_missing()
for _, plugin in pairs(Config.plugins) do
local installed = plugin._.installed
local has_errors = Plugin.has_errors(plugin)
if not has_errors and not (installed and not plugin._.build) then
if not plugin._.installed then
for _, colorscheme in ipairs(Config.options.install.colorscheme) do
if colorscheme == "default" then
break
end
M.colorscheme(colorscheme)
if vim.g.colors_name or pcall(vim.cmd.colorscheme, colorscheme) then
if pcall(vim.cmd.colorscheme, colorscheme) then
break
end
end
Cache.reset()
require("lazy.manage").install({ wait = true, lockfile = true, clear = false })
-- remove any installed plugins from indexed, so cache will index again
for _, p in pairs(Config.plugins) do
if p._.installed then
Cache.reset(p.dir)
require("lazy.manage").install({ wait = true })
break
end
end
-- reload plugins
Plugin.load()
return true
end
Util.track()
end
end
@ -104,7 +57,7 @@ function M.startup()
M.source(vim.env.VIMRUNTIME .. "/filetype.lua")
-- backup original rtp
local rtp = vim.opt.rtp:get() --[[@as string[] ]]
local rtp = vim.opt.rtp:get()
-- 1. run plugin init
Util.track({ start = "init" })
@ -133,9 +86,6 @@ function M.startup()
Util.track({ start = "rtp plugins" })
for _, path in ipairs(rtp) do
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] = path
M.packadd(path)
end
end
@ -143,9 +93,7 @@ function M.startup()
-- 4. load after plugins
Util.track({ start = "after" })
for _, path in
ipairs(vim.opt.rtp:get() --[[@as string[] ]])
do
for _, path in ipairs(vim.opt.rtp:get()) do
if path:find("after/?$") then
M.source_runtime(path, "plugin")
end
@ -161,7 +109,7 @@ function M.get_start_plugins()
---@type LazyPlugin[]
local start = {}
for _, plugin in pairs(Config.plugins) do
if not plugin._.loaded and (plugin._.rtp_loaded or plugin.lazy == false) then
if plugin.lazy == false and not plugin._.loaded then
start[#start + 1] = plugin
end
end
@ -176,8 +124,7 @@ end
---@class Loader
---@param plugins string|LazyPlugin|string[]|LazyPlugin[]
---@param reason {[string]:string}
---@param opts? {force:boolean} when force is true, we skip the cond check
function M.load(plugins, reason, opts)
function M.load(plugins, reason)
---@diagnostic disable-next-line: cast-local-type
plugins = (type(plugins) == "string" or plugins.name) and { plugins } or plugins
---@cast plugins (string|LazyPlugin)[]
@ -186,143 +133,29 @@ function M.load(plugins, reason, opts)
if type(plugin) == "string" then
if Config.plugins[plugin] then
plugin = Config.plugins[plugin]
elseif Config.spec.disabled[plugin] then
plugin = nil
else
Util.error("Plugin " .. plugin .. " not found")
plugin = nil
end
end
if plugin and not plugin._.loaded then
M._load(plugin, reason, opts)
end
end
end
---@param plugin LazyPlugin
function M.deactivate(plugin)
if not plugin._.loaded then
return
end
local main = M.get_main(plugin)
if main then
Util.try(function()
local mod = require(main)
if mod.deactivate then
mod.deactivate(plugin)
end
end, "Failed to deactivate plugin " .. plugin.name)
end
-- execute deactivate when needed
if plugin.deactivate then
Util.try(function()
plugin.deactivate(plugin)
end, "Failed to deactivate plugin " .. plugin.name)
end
-- disable handlers
Handler.disable(plugin)
-- clear plugin properties cache
plugin._.cache = nil
-- remove loaded lua modules
Util.walkmods(plugin.dir .. "/lua", function(modname)
package.loaded[modname] = nil
package.preload[modname] = nil
end)
-- clear vim.g.loaded_ for plugins
Util.ls(plugin.dir .. "/plugin", function(_, name, type)
if type == "file" then
vim.g["loaded_" .. name:gsub("%..*", "")] = nil
end
end)
-- set as not loaded
plugin._.loaded = nil
end
--- reload a plugin
---@param plugin LazyPlugin|string
function M.reload(plugin)
if type(plugin) == "string" then
plugin = Config.plugins[plugin]
end
if not plugin then
error("Plugin not found")
end
local load = plugin._.loaded ~= nil
M.deactivate(plugin)
-- enable handlers
Handler.enable(plugin)
-- run init
if plugin.init then
Util.try(function()
plugin.init(plugin)
end, "Failed to run `init` for **" .. plugin.name .. "**")
end
-- if this is a start plugin, load it now
if plugin.lazy == false then
load = true
end
local events = plugin._.handlers and plugin._.handlers.event and plugin._.handlers.event or {}
for _, event in pairs(events) do
if event.id:find("VimEnter") or event.id:find("UIEnter") or event.id:find("VeryLazy") then
load = true
break
end
end
-- reload any vimscript files for this plugin
local scripts = vim.fn.getscriptinfo()
local loaded_scripts = {}
for _, s in ipairs(scripts) do
---@type string
local path = s.name
if
path:sub(-4) == ".vim"
and path:find(plugin.dir, 1, true) == 1
and not path:find("/plugin/", 1, true)
and not path:find("/ftplugin/", 1, true)
then
loaded_scripts[#loaded_scripts + 1] = path
end
end
if load then
M.load(plugin, { start = "reload" })
for _, s in ipairs(loaded_scripts) do
M.source(s)
M._load(plugin, reason)
end
end
end
---@param plugin LazyPlugin
---@param reason {[string]:string}
---@param opts? {force:boolean} when force is true, we skip the cond check
function M._load(plugin, reason, opts)
function M._load(plugin, reason)
if not plugin._.installed then
return Util.error("Plugin " .. plugin.name .. " is not installed")
end
if plugin._.cond == false and not (opts and opts.force) then
if plugin.cond ~= nil then
if plugin.cond == false or (type(plugin.cond) == "function" and not plugin.cond()) then
plugin._.cond = false
return
end
if not Handler.did_setup then
Util.try(function()
Handler.enable(plugin)
end, "Failed to setup handlers for " .. plugin.name)
end
---@diagnostic disable-next-line: assign-type-mismatch
@ -341,13 +174,7 @@ function M._load(plugin, reason, opts)
Util.track({ plugin = plugin.name, start = reason.start })
Handler.disable(plugin)
if not plugin.virtual then
M.add_to_rtp(plugin)
end
if plugin._.pkg and plugin._.pkg.source == "rockspec" then
M.add_to_luapath(plugin)
end
if plugin.dependencies then
Util.try(function()
@ -355,18 +182,15 @@ function M._load(plugin, reason, opts)
end, "Failed to load deps for " .. plugin.name)
end
if not plugin.virtual then
M.packadd(plugin.dir)
end
if plugin.config or plugin.opts then
if plugin.config then
M.config(plugin)
end
plugin._.loaded.time = Util.track().time
table.remove(M.loading)
vim.schedule(function()
vim.api.nvim_exec_autocmds("User", { pattern = "LazyLoad", modeline = false, data = plugin.name })
vim.api.nvim_exec_autocmds("User", { pattern = "LazyRender", modeline = false })
vim.cmd("do User LazyRender")
end)
end
@ -376,15 +200,30 @@ function M.config(plugin)
local fn
if type(plugin.config) == "function" then
fn = function()
local opts = Plugin.values(plugin, "opts", false)
plugin.config(plugin, opts)
plugin.config(plugin)
end
else
local main = M.get_main(plugin)
if main then
local normname = Util.normname(plugin.name)
---@type table<string, string>
local mods = {}
Util.ls(plugin.dir .. "/lua", function(_, modname)
modname = modname:gsub("%.lua$", "")
mods[modname] = modname
local modnorm = Util.normname(modname)
-- if we found an exact match, then use that
if modnorm == normname then
mods = { modname }
return false
end
end)
mods = vim.tbl_values(mods)
if #mods == 1 then
fn = function()
local opts = Plugin.values(plugin, "opts", false)
require(main).setup(opts)
local opts = plugin.config
if opts == true then
opts = nil
end
require(mods[1]).setup(opts)
end
else
return Util.error(
@ -395,31 +234,6 @@ function M.config(plugin)
Util.try(fn, "Failed to run `config` for " .. plugin.name)
end
---@param plugin LazyPlugin
function M.get_main(plugin)
if plugin.main then
return plugin.main
end
if plugin.name ~= "mini.nvim" and plugin.name:match("^mini%..*$") then
return plugin.name
end
local normname = Util.normname(plugin.name)
---@type string[]
local mods = {}
for _, mod in ipairs(Cache.find("*", { all = true, rtp = false, paths = { plugin.dir } })) do
local modname = mod.modname
mods[#mods + 1] = modname
local modnorm = Util.normname(modname)
-- if we found an exact match, then use that
if modnorm == normname then
mods = { modname }
break
end
end
return #mods == 1 and mods[1] or nil
end
---@param path string
function M.packadd(path)
M.source_runtime(path, "plugin")
@ -468,8 +282,10 @@ function M.add_to_rtp(plugin)
local rtp = vim.api.nvim_get_runtime_file("", true)
local idx_dir, idx_after
local is_win = jit.os:find("Windows")
for i, path in ipairs(rtp) do
if Util.is_win then
if is_win then
path = Util.norm(path)
end
if path == Config.me then
@ -484,30 +300,17 @@ function M.add_to_rtp(plugin)
table.insert(rtp, idx_dir or (#rtp + 1), plugin.dir)
local after = plugin.dir .. "/after"
if vim.uv.fs_stat(after) then
if vim.loop.fs_stat(after) then
table.insert(rtp, idx_after or (#rtp + 1), after)
end
---@type vim.Option
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"
local cpath2 = root .. "/lib64/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") .. ";"
package.cpath = package.cpath .. ";" .. cpath2 .. "/?." .. (jit.os:find("Windows") and "dll" or "so") .. ";"
end
function M.source(path)
Util.track({ runtime = path })
Util.try(function()
vim.cmd("source " .. path)
vim.cmd("silent source " .. path)
end, "Failed to source `" .. path .. "`")
Util.track()
end
@ -520,7 +323,7 @@ function M.colorscheme(name)
if not plugin._.loaded then
for _, ext in ipairs({ "lua", "vim" }) do
local path = plugin.dir .. "/colors/" .. name .. "." .. ext
if vim.uv.fs_stat(path) then
if vim.loop.fs_stat(path) then
return M.load(plugin, { colorscheme = name })
end
end
@ -528,49 +331,4 @@ function M.colorscheme(name)
end
end
function M.auto_load(modname, modpath)
local plugin = Plugin.find(modpath, { fast = not M.did_handlers })
if plugin then
plugin._.rtp_loaded = true
-- don't load if:
-- * handlers haven't been setup yet
-- * we're loading specs
-- * the plugin is already loaded
if M.did_handlers and not (Plugin.loading or plugin._.loaded) then
if plugin.module == false then
error("Plugin " .. plugin.name .. " is not loaded and is configured with module=false")
end
M.load(plugin, { require = modname })
if plugin._.cond == false then
error("You're trying to load `" .. plugin.name .. "` for which `cond==false`")
end
end
end
end
---@param modname string
function M.loader(modname)
local paths, cached = Util.get_unloaded_rtp(modname, { cache = true })
local ret = Cache.find(modname, { rtp = false, paths = paths })[1]
if not ret and cached then
paths = Util.get_unloaded_rtp(modname)
ret = Cache.find(modname, { rtp = false, paths = paths })[1]
end
if ret then
-- explicitly set to nil to prevent loading errors
package.loaded[modname] = nil
M.auto_load(modname, ret.modpath)
local mod = package.loaded[modname]
if type(mod) == "table" then
return function()
return mod
end
end
-- selene: allow(incorrect_standard_library_use)
return loadfile(ret.modpath, nil, nil, ret.stat)
end
end
return M

View file

@ -1,360 +0,0 @@
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 last_id = self.fragments._fid
local meta, fragment = self:add(pkg.spec)
if meta and fragment then
meta._.pkg = pkg
-- tag all top-level package fragments that were added as optional
for _, fid in ipairs(meta._.frags) do
if fid > last_id then
local frag = self.fragments:get(fid)
frag.spec.optional = true
end
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 and meta.dir 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()
local frag_count = vim.tbl_count(self.fragments.dirty)
local plugin_count = vim.tbl_count(self.dirty)
if frag_count == 0 and plugin_count == 0 then
return
end
if Config.options.debug then
Util.track("rebuild plugins frags=" .. frag_count .. " plugins=" .. plugin_count)
end
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
if Config.options.debug then
Util.track()
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)
if not self.dirty[name] then
return
end
self.dirty[name] = nil
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._.top = 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
plugin._.top = plugin._.top and fragment.pid == nil
-- dependencies
for _, dep in ipairs(fragment.deps or {}) do
local dep_meta = self.frag_to_meta[dep]
if dep_meta then
table.insert(plugin.dependencies, dep_meta.name)
end
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)
elseif super.virtual then
plugin.dir = Util.norm("/dev/null/" .. plugin.name)
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 })
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
local function check(top)
for _, plugin in pairs(self.plugins) do
if (plugin._.top or false) == top then
if plugin.enabled == false or (type(plugin.enabled) == "function" and not plugin.enabled()) then
changes = changes + 1
if plugin.optional then
self:del(plugin.name)
else
self:disable(plugin)
end
self:rebuild()
end
end
end
end
-- disable top-level plugins first, since they may have non-top-level frags
-- that disable other plugins
check(true)
-- then disable non-top-level plugins
check(false)
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,231 +1,155 @@
local Config = require("lazy.core.config")
local Meta = require("lazy.core.meta")
local Pkg = require("lazy.pkg")
local Util = require("lazy.core.util")
local Handler = require("lazy.core.handler")
local Cache = require("lazy.core.cache")
---@class LazyCorePlugin
local M = {}
M.loading = false
---@class LazySpecLoader
---@field meta LazyMeta
---@field plugins table<string, LazyPlugin>
---@field disabled table<string, LazyPlugin>
---@field ignore_installed table<string, true>
---@field modules string[]
---@field notifs {msg:string, level:number, file?:string}[]
---@field importing? string
---@field optional? boolean
---@field errors string[]
local Spec = {}
M.Spec = Spec
M.LOCAL_SPEC = ".lazy.lua"
---@param spec? LazySpec
---@param opts? {optional?:boolean, pkg?:boolean}
function Spec.new(spec, opts)
local self = setmetatable({}, Spec)
self.meta = Meta.new(self)
self.disabled = {}
self.modules = {}
self.notifs = {}
self.ignore_installed = {}
self.optional = opts and opts.optional
if not (opts and opts.pkg == false) then
self.meta:load_pkgs()
end
function Spec.new(spec)
local self = setmetatable({}, { __index = Spec })
self.plugins = {}
self.errors = {}
if spec then
self:parse(spec)
self:normalize(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.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
local slash = name:reverse():find("/", 1, true) --[[@as number?]]
return slash and name:sub(#name - slash + 2) or pkg:gsub("%W+", "_")
end
function Spec:error(msg)
self:log(msg, vim.log.levels.ERROR)
---@param plugin LazyPlugin
---@param is_dep? boolean
function Spec:add(plugin, is_dep)
if not plugin.url and plugin[1] then
plugin.url = Config.options.git.url_format:format(plugin[1])
end
function Spec:warn(msg)
self:log(msg, vim.log.levels.WARN)
if plugin.dir then
plugin.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
-- dev plugins
if plugin.dev then
plugin.dir = Config.options.dev.path .. "/" .. plugin.name
else
-- remote plugin
plugin.dir = Config.options.root .. "/" .. plugin.name
end
else
self:error("Invalid plugin spec " .. vim.inspect(plugin))
end
---@param msg string
---@param level number
function Spec:log(msg, level)
self.notifs[#self.notifs + 1] = { msg = msg, level = level, file = self.importing }
plugin.event = type(plugin.event) == "string" and { plugin.event } or plugin.event
plugin.keys = type(plugin.keys) == "string" and { plugin.keys } or plugin.keys
plugin.cmd = type(plugin.cmd) == "string" and { plugin.cmd } or plugin.cmd
plugin.ft = type(plugin.ft) == "string" and { plugin.ft } or plugin.ft
plugin._ = {}
plugin._.dep = is_dep
local other = self.plugins[plugin.name]
self.plugins[plugin.name] = other and self:merge(other, plugin) or plugin
return self.plugins[plugin.name]
end
function Spec:report(level)
level = level or vim.log.levels.ERROR
local count = 0
for _, notif in ipairs(self.notifs) do
if notif.level >= level then
Util.notify(notif.msg, { level = notif.level })
count = count + 1
end
end
return count
function Spec:error(error)
self.errors[#self.errors + 1] = error
Util.error(error)
end
---@param spec LazySpec|LazySpecImport
function Spec:normalize(spec)
---@param spec LazySpec
---@param results? string[]
---@param is_dep? boolean
function Spec:normalize(spec, results, is_dep)
results = results or {}
if type(spec) == "string" then
self.meta:add({ spec })
if is_dep and not spec:find("/", 1, true) then
-- spec is a plugin name
table.insert(results, spec)
else
table.insert(results, self:add({ spec }, is_dep).name)
end
elseif #spec > 1 or Util.is_list(spec) then
---@cast spec LazySpec[]
for _, s in ipairs(spec) do
self:normalize(s)
self:normalize(s, results, is_dep)
end
elseif spec[1] or spec.dir or spec.url then
---@cast spec LazyPluginSpec
self.meta:add(spec)
---@diagnostic disable-next-line: cast-type-mismatch
---@cast spec LazySpecImport
if spec and spec.import then
self:import(spec)
end
elseif spec.import then
---@cast spec LazySpecImport
self:import(spec)
elseif spec.enabled == nil or spec.enabled == true or (type(spec.enabled) == "function" and spec.enabled()) then
local plugin
-- 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 spec._ then
plugin = spec
else
self:error("Invalid plugin spec " .. vim.inspect(spec))
---@cast spec LazyPlugin
plugin = self:add(spec, is_dep)
plugin.dependencies = plugin.dependencies and self:normalize(plugin.dependencies, {}, true) or nil
end
table.insert(results, plugin.name)
end
return results
end
---@param spec LazySpecImport
function Spec:import(spec)
if spec.import == "lazy" then
return self:error("You can't name your plugins module `lazy`.")
end
if type(spec.import) == "function" then
if not spec.name then
return self:error("Invalid import spec. Missing name: " .. vim.inspect(spec))
end
elseif type(spec.import) ~= "string" then
return self:error("Invalid import spec. `import` should be a string: " .. vim.inspect(spec))
end
---@param old LazyPlugin
---@param new LazyPlugin
---@return LazyPlugin
function Spec:merge(old, new)
local is_dep = old._.dep and new._.dep
local import_name = spec.name or spec.import
---@cast import_name string
if vim.tbl_contains(self.modules, import_name) then
return
end
if spec.cond == false or (type(spec.cond) == "function" and not spec.cond()) then
return
end
if spec.enabled == false or (type(spec.enabled) == "function" and not spec.enabled()) then
return
end
self.modules[#self.modules + 1] = import_name
local import = spec.import
local imported = 0
---@type {modname: string, load: fun():(LazyPluginSpec?, string?)}[]
local modspecs = {}
if type(import) == "string" then
Util.lsmod(import, function(modname, modpath)
modspecs[#modspecs + 1] = {
modname = modname,
load = function()
local mod, err = loadfile(modpath)
if mod then
return mod()
else
return nil, err
end
end,
}
end)
table.sort(modspecs, function(a, b)
return a.modname < b.modname
end)
else
modspecs = { { modname = import_name, load = spec.import } }
end
for _, modspec in ipairs(modspecs) do
imported = imported + 1
local modname = modspec.modname
Util.track({ import = modname })
self.importing = modname
-- unload the module so we get a clean slate
---@diagnostic disable-next-line: no-unknown
package.loaded[modname] = nil
Util.try(function()
local mod, err = modspec.load()
if err then
self:error("Failed to load `" .. modname .. "`:\n" .. err)
elseif type(mod) ~= "table" then
return self:error(
"Invalid spec module: `"
.. modname
.. "`\nExpected a `table` of specs, but a `"
.. type(mod)
.. "` was returned instead"
)
for k, v in pairs(new) do
if k == "_" then
elseif old[k] ~= nil and old[k] ~= v then
if Handler.types[k] then
local values = type(v) == "string" and { v } or v
vim.list_extend(values, type(old[k]) == "string" and { old[k] } or old[k])
---@diagnostic disable-next-line: no-unknown
old[k] = values
else
self:normalize(mod)
self:error("Merging plugins is not supported for key `" .. k .. "`\n" .. vim.inspect({ old = old, new = new }))
end
end, {
msg = "Failed to load `" .. modname .. "`",
on_error = function(msg)
self:error(msg)
end,
})
self.importing = nil
Util.track()
else
---@diagnostic disable-next-line: no-unknown
old[k] = v
end
if imported == 0 then
self:error("No specs found for module " .. vim.inspect(spec.import))
end
old._.dep = is_dep
return old
end
function M.update_state()
---@type string[]
local cloning = {}
---@type table<string,FileType>
local installed = {}
Util.ls(Config.options.root, function(_, name, type)
if type == "directory" and name ~= "readme" then
installed[name] = type
elseif type == "file" and name:sub(-8) == ".cloning" then
name = name:sub(1, -9)
cloning[#cloning + 1] = name
end
end)
for _, failed in ipairs(cloning) do
installed[failed] = nil
end
for _, plugin in pairs(Config.plugins) do
plugin._ = plugin._ or {}
if plugin.lazy == nil then
@ -237,24 +161,15 @@ function M.update_state()
or plugin.cmd
plugin.lazy = lazy and true or false
end
if plugin.virtual then
plugin._.is_local = true
plugin._.installed = true -- local plugins are managed by the user
elseif plugin.dir:find(Config.options.root, 1, true) == 1 then
if plugin.dir:find(Config.options.root, 1, true) == 1 then
plugin._.installed = installed[plugin.name] ~= nil
installed[plugin.name] = nil
else
plugin._.is_local = true
plugin._.installed = vim.fn.isdirectory(plugin.dir) == 1
plugin._.installed = true -- local plugins are managed by the user
end
end
for name in pairs(Config.spec.ignore_installed) do
installed[name] = nil
end
M.update_rocks_state()
Config.to_clean = {}
for pack, dir_type in pairs(installed) do
table.insert(Config.to_clean, {
@ -270,101 +185,49 @@ 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)
function M.spec()
local spec = Spec.new()
for _, plugin in pairs(Config.plugins) do
if plugin.build == "rockspec" or plugin.name == "hererocks" then
plugin._.build = not installed[plugin.name]
if type(Config.spec) == "string" then
-- spec is a module
local function _load(modname)
-- unload the module so we get a clean slate
---@diagnostic disable-next-line: no-unknown
package.loaded[modname] = nil
Util.try(function()
spec:normalize(Cache.require(modname))
end, "Failed to load **" .. modname .. "**")
end
Util.lsmod(Config.spec --[[@as string]], _load)
else
-- spec is a spec
spec:normalize(vim.deepcopy(Config.spec))
end
end
---@return LazySpecImport?
function M.find_local_spec()
if not Config.options.local_spec then
return
end
local path = vim.uv.cwd()
while path and path ~= "" do
local file = path .. "/" .. M.LOCAL_SPEC
if vim.fn.filereadable(file) == 1 then
return {
name = vim.fn.fnamemodify(file, ":~:."),
import = function()
local data = vim.secure.read(file)
if data then
return loadstring(data, M.LOCAL_SPEC)()
end
return {}
end,
}
end
local p = vim.fn.fnamemodify(path, ":h")
if p == path then
break
end
path = p
end
return spec
end
function M.load()
M.loading = true
-- load specs
Util.track("spec")
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()
specs[#specs + 1] = { "folke/lazy.nvim" }
Config.spec:parse(specs)
local spec = M.spec()
-- add ourselves
spec:add({ "folke/lazy.nvim" })
-- override some lazy props
local lazy = Config.spec.plugins["lazy.nvim"]
if lazy then
local lazy = spec.plugins["lazy.nvim"]
lazy.lazy = true
lazy.dir = Config.me
lazy.config = function()
error("lazy config should not be called")
end
lazy._.loaded = {}
end
-- add hererocks when enabled and needed
for _, plugin in pairs(Config.spec.plugins) do
if plugin.build == "rockspec" then
if Config.hererocks() then
Config.spec.meta:add({
"luarocks/hererocks",
build = "rockspec",
lazy = true,
})
end
break
end
end
local existing = Config.plugins
Config.plugins = Config.spec.plugins
Config.plugins = spec.plugins
-- copy state. This wont do anything during startup
for name, plugin in pairs(existing) do
if Config.plugins[name] then
local new_state = Config.plugins[name]._
Config.plugins[name]._ = plugin._
Config.plugins[name]._.dep = new_state.dep
Config.plugins[name]._.frags = new_state.frags
Config.plugins[name]._.pkg = new_state.pkg
end
end
Util.track()
@ -372,112 +235,20 @@ 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 })
require("lazy.core.cache").indexed_unloaded = false
end
-- Finds the plugin that has this path
---@param path string
---@param opts? {fast?:boolean}
function M.find(path, opts)
if not Config.spec then
return
end
opts = opts or {}
function M.find(path)
local lua = path:find("/lua/", 1, true)
if lua then
local name = path:sub(1, lua - 1)
local slash = name:reverse():find("/", 1, true)
if slash then
name = name:sub(#name - slash + 2)
if name then
if opts.fast then
return Config.spec.meta.plugins[name]
return name and Config.plugins[name] or nil
end
return Config.spec.plugins[name]
end
end
end
end
---@param plugin LazyPlugin
function M.has_errors(plugin)
for _, task in ipairs(plugin._.tasks or {}) do
if task:has_errors() then
return true
end
end
return false
end
-- Merges super values or runs the values function to override values or return new ones.
-- Values are cached for performance.
-- Used for opts, cmd, event, ft and keys
---@param plugin LazyPlugin
---@param prop string
---@param is_list? boolean
function M.values(plugin, prop, is_list)
if not plugin[prop] then
return {}
end
plugin._.cache = plugin._.cache or {}
local key = prop .. (is_list and "_list" or "")
if plugin._.cache[key] == nil then
plugin._.cache[key] = M._values(plugin, plugin, prop, is_list)
end
return plugin._.cache[key]
end
-- Merges super values or runs the values function to override values or return new ones
-- Used for opts, cmd, event, ft and keys
---@param root LazyPlugin
---@param plugin LazyPlugin
---@param prop string
---@param is_list? boolean
function M._values(root, plugin, prop, is_list)
if not plugin[prop] then
return {}
end
local super = getmetatable(plugin)
---@type table
local ret = super and M._values(root, super.__index, prop, is_list) or {}
local values = rawget(plugin, prop)
if not values then
return ret
elseif type(values) == "function" then
ret = values(root, ret) or ret
return type(ret) == "table" and ret or { ret }
end
values = type(values) == "table" and values or { values }
if is_list then
return Util.extend(ret, values)
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)
local v = Util.key_get(values, path)
if type(r) == "table" and type(v) == "table" then
lists[key] = { path = path, list = {} }
vim.list_extend(lists[key].list, r)
vim.list_extend(lists[key].list, v)
end
end
local t = Util.merge(ret, values)
for _, list in pairs(lists) do
Util.key_set(t, list.path, list.list)
end
return t
end
end

View file

@ -5,7 +5,6 @@ local M = {}
---@type LazyProfile[]
M._profiles = { { name = "lazy" } }
M.is_win = jit.os:find("Windows")
---@param data (string|{[string]:string})?
---@param time number?
@ -13,7 +12,7 @@ function M.track(data, time)
if data then
local entry = {
data = data,
time = time or vim.uv.hrtime(),
time = time or vim.loop.hrtime(),
}
table.insert(M._profiles[#M._profiles], entry)
@ -24,82 +23,48 @@ function M.track(data, time)
else
---@type LazyProfile
local entry = table.remove(M._profiles)
entry.time = vim.uv.hrtime() - entry.time
entry.time = vim.loop.hrtime() - entry.time
return entry
end
end
function M.exiting()
return vim.v.exiting ~= vim.NIL
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
---@return F
function M.trackfn(data, fn)
return function(...)
M.track(data)
local ok, ret = pcall(fn, ...)
M.track()
if not ok then
error(ret)
end
return ret
end
end
---@param name string
---@return string
function M.normname(name)
local ret = name:lower():gsub("^n?vim%-", ""):gsub("%.n?vim$", ""):gsub("[%.%-]lua", ""):gsub("[^a-z]+", "")
local ret = name:lower():gsub("^n?vim%-", ""):gsub("%.n?vim$", ""):gsub("%.lua", ""):gsub("[^a-z]+", "")
return ret
end
---@return string
function M.norm(path)
if path:sub(1, 1) == "~" then
local home = vim.uv.os_homedir()
local home = vim.loop.os_homedir()
if home:sub(-1) == "\\" or home:sub(-1) == "/" then
home = home:sub(1, -2)
end
path = home .. path:sub(2)
end
path = path:gsub("\\", "/"):gsub("/+", "/")
path = path:gsub("\\", "/")
return path:sub(-1) == "/" and path:sub(1, -2) or path
end
---@param opts? {level?: number}
function M.pretty_trace(opts)
opts = opts or {}
function M.try(fn, msg)
-- error handler
local error_handler = function(err)
local Config = require("lazy.core.config")
local trace = {}
local level = opts.level or 2
local level = 1
while true do
local info = debug.getinfo(level, "Sln")
if not info then
break
end
if info.what ~= "C" and (Config.options.debug or not info.source:find("lazy.nvim")) then
if info.what ~= "C" and not info.source:find("lazy.nvim") then
local source = info.source:sub(2)
if source:find(Config.options.root, 1, true) == 1 then
source = source:sub(#Config.options.root + 1)
end
source = vim.fn.fnamemodify(source, ":p:~:.") --[[@as string]]
source = vim.fn.fnamemodify(source, ":p:~:.")
local line = " - " .. source .. ":" .. info.currentline
if info.name then
line = line .. " _in_ **" .. info.name .. "**"
@ -108,26 +73,13 @@ function M.pretty_trace(opts)
end
level = level + 1
end
return #trace > 0 and ("\n\n# stacktrace:\n" .. table.concat(trace, "\n")) or ""
msg = msg .. "\n\n" .. err
if #trace > 0 then
msg = msg .. "\n\n# stacktrace:\n" .. table.concat(trace, "\n")
end
---@generic R
---@param fn fun():R?
---@param opts? string|{msg:string, on_error:fun(msg)}
---@return R
function M.try(fn, opts)
opts = type(opts) == "string" and { msg = opts } or opts or {}
local msg = opts.msg
-- error handler
local error_handler = function(err)
msg = (msg and (msg .. "\n\n") or "") .. err .. M.pretty_trace()
if opts.on_error then
opts.on_error(msg)
else
vim.schedule(function()
M.error(msg)
end)
end
return err
end
@ -143,7 +95,7 @@ function M.get_source()
if not info then
break
end
if info.what ~= "C" and not info.source:find("lazy.nvim", 1, true) and info.source ~= "@vim/loader.lua" then
if info.what ~= "C" and not info.source:find("lazy.nvim", 1, true) then
return info.source:sub(2)
end
f = f + 1
@ -166,15 +118,9 @@ end
function M.very_lazy()
local function _load()
vim.schedule(function()
if vim.v.exiting ~= vim.NIL then
return
end
vim.g.did_very_lazy = true
M.track({ event = "VeryLazy" })
vim.api.nvim_exec_autocmds("User", { pattern = "VeryLazy", modeline = false })
M.track()
end)
vim.defer_fn(function()
vim.cmd("do User VeryLazy")
end, 100)
end
vim.api.nvim_create_autocmd("User", {
@ -184,7 +130,7 @@ function M.very_lazy()
if vim.v.vim_did_enter == 1 then
_load()
else
vim.api.nvim_create_autocmd("UIEnter", {
vim.api.nvim_create_autocmd("VimEnter", {
once = true,
callback = function()
_load()
@ -199,19 +145,13 @@ end
---@param path string
---@param fn fun(path: string, name:string, type:FileType):boolean?
function M.ls(path, fn)
local handle = vim.uv.fs_scandir(path)
local handle = vim.loop.fs_scandir(path)
while handle do
local name, t = vim.uv.fs_scandir_next(handle)
local name, t = vim.loop.fs_scandir_next(handle)
if not name then
break
end
local fname = path .. "/" .. name
-- HACK: type is not always returned due to a bug in luv,
-- so fecth it with fs_stat instead when needed.
-- see https://github.com/folke/lazy.nvim/issues/306
if fn(fname, name, t or vim.uv.fs_stat(fname).type) == false then
if fn(path .. "/" .. name, name, t) == false then
break
end
end
@ -228,138 +168,34 @@ function M.walk(path, fn)
end)
end
---@param modname string
---@param root string
---@param fn fun(modname:string, modpath:string)
---@param modname? string
function M.walkmods(root, fn, modname)
modname = modname and (modname:gsub("%.$", "") .. ".") or ""
M.ls(root, function(path, name, type)
if name == "init.lua" then
fn(modname:gsub("%.$", ""), path)
elseif (type == "file" or type == "link") and name:sub(-4) == ".lua" then
fn(modname .. name:sub(1, -5), path)
elseif type == "directory" then
M.walkmods(path, fn, modname .. name .. ".")
end
end)
end
---@param modname string
---@return string
function M.topmod(modname)
return modname:match("^[^./]+") or modname
end
---@type table<string, string[]>
M.unloaded_cache = {}
---@param modname string
---@param opts? {cache?:boolean}
function M.get_unloaded_rtp(modname, opts)
opts = opts or {}
local topmod = M.topmod(modname)
if opts.cache and M.unloaded_cache[topmod] then
return M.unloaded_cache[topmod], true
end
local norm = M.normname(topmod)
---@type string[]
local rtp = {}
local Config = require("lazy.core.config")
if Config.spec then
for _, plugin in pairs(Config.spec.plugins) do
if not (plugin._.loaded or plugin.module == false or plugin.virtual) then
if norm == M.normname(plugin.name) then
table.insert(rtp, 1, plugin.dir)
else
table.insert(rtp, plugin.dir)
end
end
end
end
M.unloaded_cache[topmod] = rtp
return rtp, false
end
function M.find_root(modname)
local paths, cached = M.get_unloaded_rtp(modname, { cache = true })
local ret = require("lazy.core.cache").find(modname, {
rtp = true,
paths = paths,
patterns = { ".lua", "" },
})[1]
if not ret and cached then
paths = M.get_unloaded_rtp(modname)
ret = require("lazy.core.cache").find(modname, {
rtp = false,
paths = paths,
patterns = { ".lua", "" },
})[1]
end
if ret then
return ret.modpath:gsub("%.lua$", ""), ret.modpath
end
end
---@param modname string
---@param fn fun(modname:string, modpath:string)
function M.lsmod(modname, fn)
local root, match = M.find_root(modname)
if not root then
return
end
if match:sub(-4) == ".lua" then
fn(modname, match)
if not vim.uv.fs_stat(root) then
return
end
end
---@overload fun(modname:string, fn: fun(modname:string, modpath:string))
function M.lsmod(modname, root, fn)
if type(root) == "function" then
fn = root
root = vim.fn.stdpath("config") .. "/lua"
end
root = root .. "/" .. modname:gsub("%.", "/")
if vim.loop.fs_stat(root .. ".lua") then
fn(modname, root .. ".lua")
end
M.ls(root, function(path, name, type)
if (type == "file" or type == "link") and name:sub(-4) == ".lua" then
if name == "init.lua" then
fn(modname, path)
elseif (type == "file" or type == "link") and name:sub(-4) == ".lua" then
else
fn(modname .. "." .. name:sub(1, -5), path)
elseif type == "directory" and vim.uv.fs_stat(path .. "/init.lua") then
end
elseif type == "directory" and vim.loop.fs_stat(path .. "/init.lua") then
fn(modname .. "." .. name, path .. "/init.lua")
end
end)
end
---@generic T
---@param list T[]
---@param add T[]
---@return T[]
function M.extend(list, add)
local idx = {}
for _, v in ipairs(list) do
idx[v] = v
end
for _, a in ipairs(add) do
if not idx[a] then
table.insert(list, a)
end
end
return list
end
---@alias LazyNotifyOpts {lang?:string, title?:string, level?:number, once?:boolean, stacktrace?:boolean, stacklevel?:number}
---@param msg string|string[]
---@param opts? LazyNotifyOpts
function M.notify(msg, opts)
if vim.in_fast_event() then
return vim.schedule(function()
M.notify(msg, opts)
end)
end
opts = opts or {}
function M.notify(msg, level)
if type(msg) == "table" then
msg = table.concat(
vim.tbl_filter(function(line)
@ -368,149 +204,33 @@ function M.notify(msg, opts)
"\n"
)
end
if opts.stacktrace then
msg = msg .. M.pretty_trace({ level = opts.stacklevel or 2 })
end
local lang = opts.lang or "markdown"
local n = opts.once and vim.notify_once or vim.notify
n(msg, opts.level or vim.log.levels.INFO, {
ft = lang,
vim.notify(msg, level, {
on_open = function(win)
local ok = pcall(function()
vim.treesitter.language.add("markdown")
end)
if not ok then
pcall(require, "nvim-treesitter")
end
vim.wo[win].conceallevel = 3
vim.wo[win].concealcursor = ""
vim.wo[win].spell = false
local buf = vim.api.nvim_win_get_buf(win)
if not pcall(vim.treesitter.start, buf, lang) then
vim.bo[buf].filetype = lang
vim.bo[buf].syntax = lang
if not pcall(vim.treesitter.start, buf, "markdown") then
vim.bo[buf].filetype = "markdown"
end
end,
title = opts.title or "lazy.nvim",
title = "lazy.nvim",
})
end
---@param msg string|string[]
---@param opts? LazyNotifyOpts
function M.error(msg, opts)
opts = opts or {}
opts.level = vim.log.levels.ERROR
M.notify(msg, opts)
function M.error(msg)
M.notify(msg, vim.log.levels.ERROR)
end
---@param msg string|string[]
---@param opts? LazyNotifyOpts
function M.info(msg, opts)
opts = opts or {}
opts.level = vim.log.levels.INFO
M.notify(msg, opts)
function M.info(msg)
M.notify(msg, vim.log.levels.INFO)
end
---@param msg string|string[]
---@param opts? LazyNotifyOpts
function M.warn(msg, opts)
opts = opts or {}
opts.level = vim.log.levels.WARN
M.notify(msg, opts)
end
---@param msg string|table
---@param opts? LazyNotifyOpts
function M.debug(msg, opts)
if not require("lazy.core.config").options.debug then
return
end
opts = opts or {}
if opts.title then
opts.title = "lazy.nvim: " .. opts.title
end
if type(msg) == "string" then
M.notify(msg, opts)
else
opts.lang = "lua"
M.notify(vim.inspect(msg), opts)
end
end
local function can_merge(v)
return type(v) == "table" and (vim.tbl_isempty(v) or not M.is_list(v))
end
--- Merges the values similar to vim.tbl_deep_extend with the **force** behavior,
--- but the values can be any type, in which case they override the values on the left.
--- Values will me merged in-place in the first left-most table. If you want the result to be in
--- a new table, then simply pass an empty table as the first argument `vim.merge({}, ...)`
--- Supports clearing values by setting a key to `vim.NIL`
---@generic T
---@param ... T
---@return T
function M.merge(...)
local ret = select(1, ...)
if ret == vim.NIL then
ret = nil
end
for i = 2, select("#", ...) do
local value = select(i, ...)
if can_merge(ret) and can_merge(value) then
for k, v in pairs(value) do
ret[k] = M.merge(ret[k], v)
end
elseif value == vim.NIL then
ret = nil
elseif value ~= nil then
ret = value
end
end
return ret
end
function M.lazy_require(module)
local mod = nil
-- if already loaded, return the module
-- otherwise return a lazy module
return type(package.loaded[module]) == "table" and package.loaded[module]
or setmetatable({}, {
__index = function(_, key)
mod = mod or require(module)
return mod[key]
end,
})
end
---@param t table
---@param key string|string[]
---@return any
function M.key_get(t, key)
local path = type(key) == "table" and key or vim.split(key, ".", true)
local value = t
for _, k in ipairs(path) do
if type(value) ~= "table" then
return value
end
value = value[k]
end
return value
end
---@param t table
---@param key string|string[]
---@param value any
function M.key_set(t, key, value)
local path = type(key) == "table" and key or vim.split(key, ".", true)
local last = t
for i = 1, #path - 1 do
local k = path[i]
if type(last[k]) ~= "table" then
last[k] = {}
end
last = last[k]
end
last[path[#path]] = value
function M.warn(msg)
M.notify(msg, vim.log.levels.WARN)
end
return M

View file

@ -14,48 +14,36 @@ end
function M.fix_indent(str)
local lines = vim.split(str, "\n")
local first = table.remove(lines, 1)
local width = 120
for _, line in ipairs(lines) do
if not line:find("^%s*$") then
width = math.min(width, #line:match("^%s*"))
end
end
for l, line in ipairs(lines) do
lines[l] = line:sub(width + 1)
end
table.insert(lines, 1, first)
return table.concat(lines, "\n")
end
---@alias ReadmeBlock {content:string, lang?:string}
---@param contents table<string, ReadmeBlock|string>
---@param readme_file? string
function M.save(contents, readme_file)
local readme = Util.read_file(readme_file or "README.md")
for tag, block in pairs(contents) do
if type(block) == "string" then
block = { content = block, lang = "lua" }
end
---@cast block ReadmeBlock
local content = M.fix_indent(block.content)
---@param contents table<string, string>
function M.save(contents)
local readme = Util.read_file("README.md")
for tag, content in pairs(contents) do
content = M.fix_indent(content)
content = content:gsub("%%", "%%%%")
content = vim.trim(content)
local pattern = "(<%!%-%- " .. tag .. ":start %-%->).*(<%!%-%- " .. tag .. ":end %-%->)"
if not readme:find(pattern) then
error("tag " .. tag .. " not found")
end
if block.lang then
readme = readme:gsub(pattern, "%1\n\n```" .. block.lang .. "\n" .. content .. "\n```\n\n%2")
else
if tag == "commands" or tag == "colors" then
readme = readme:gsub(pattern, "%1\n\n" .. content .. "\n\n%2")
else
readme = readme:gsub(pattern, "%1\n\n```lua\n" .. content .. "\n```\n\n%2")
end
end
Util.write_file(readme_file or "README.md", readme)
vim.cmd.checktime()
Util.write_file("README.md", readme)
end
---@return string
@ -64,14 +52,13 @@ function M.extract(file, pattern)
return assert(init:match(pattern))
end
---@return ReadmeBlock
function M.commands()
local commands = require("lazy.view.commands").commands
local modes = require("lazy.view.config").commands
modes.load.opts = true
local lines = {
{ "Command", "Lua", "Description" },
{ "---", "---", "---" },
{ "---", "---", "---", "---" },
}
Util.foreach(modes, function(name, mode)
if commands[name] then
@ -96,7 +83,7 @@ function M.commands()
end
end
end)
return { content = M.table(lines) }
return M.table(lines)
end
---@param lines string[][]
@ -109,15 +96,8 @@ function M.table(lines)
return table.concat(ret, "\n")
end
---@param opts? {name?:string, path?:string, modname?:string}
---@return ReadmeBlock
function M.colors(opts)
opts = vim.tbl_extend("force", {
name = "Lazy",
path = "lua/lazy/view/colors.lua",
modname = "lazy.view.colors",
}, opts or {})
local str = M.extract(opts.path, "\nM%.colors = ({.-\n})")
function M.colors()
local str = M.extract("lua/lazy/view/colors.lua", "\nM%.colors = ({.-\n})")
---@type table<string,string>
local comments = {}
for _, line in ipairs(vim.split(str, "\n")) do
@ -130,15 +110,19 @@ function M.colors(opts)
{ "Highlight Group", "Default Group", "Description" },
{ "---", "---", "---" },
}
Util.foreach(require(opts.modname).colors, function(group, link)
link = type(link) == "table" and "`" .. vim.inspect(link):gsub("%s+", " ") .. "`" or "***" .. link .. "***"
lines[#lines + 1] = { "**" .. opts.name .. group .. "**", link, comments[group] or "" }
Util.foreach(require("lazy.view.colors").colors, function(group, link)
lines[#lines + 1] = { "**Lazy" .. group .. "**", "***" .. link .. "***", comments[group] or "" }
end)
return { content = M.table(lines) }
return M.table(lines)
end
function M.update()
local cache_config = M.extract("lua/lazy/core/cache.lua", "\nM%.config = ({.-\n})")
local config = M.extract("lua/lazy/core/config.lua", "\nM%.defaults = ({.-\n})")
config = config:gsub(
"\n%s*%-%-%-@type LazyCacheConfig.*cache = nil,",
"\n" .. M.indent("cache = " .. cache_config .. ",", 4)
)
config = config:gsub("%s*debug = false.\n", "\n")
M.save({
bootstrap = M.extract("lua/lazy/init.lua", "function M%.bootstrap%(%)\n(.-)\nend"),
@ -148,20 +132,9 @@ function M.update()
commands = M.commands(),
colors = M.colors(),
})
vim.cmd.checktime()
end
---@param plugins? LazyPlugin[]
---@return ReadmeBlock
function M.plugins(plugins)
plugins = plugins or require("lazy.core.config").plugins
---@type string[]
local lines = {}
Util.foreach(plugins, function(name, plugin)
if plugin.url then
lines[#lines + 1] = "- [" .. name .. "](" .. plugin.url:gsub("%.git$", "") .. ")"
end
end)
return { content = table.concat(lines, "\n") }
end
M.update()
return M

View file

@ -18,22 +18,30 @@ return {
"nvim-neorg/neorg",
-- lazy-load on filetype
ft = "norg",
-- options for neorg. This will automatically call `require("neorg").setup(opts)`
opts = {
load = {
["core.defaults"] = {},
-- custom config that will be executed when loading the plugin
config = function()
require("neorg").setup()
end,
},
-- the above could also be written as:
{
"nvim-neorg/neorg",
ft = "norg",
config = true, -- run require("neorg").setup()
},
-- or set a custom config:
{
"nvim-neorg/neorg",
ft = "norg",
config = { foo = "bar" }, -- run require("neorg").setup({foo = "bar"})
},
{
"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,
},
{
@ -51,20 +59,19 @@ return {
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 },
"cshuaimin/ssr.nvim",
-- init is always executed during startup, but doesn't load the plugin yet.
init = function()
vim.keymap.set({ "n", "x" }, "<leader>cR", function()
-- this require will automatically load the plugin
require("ssr").open()
end, { desc = "Structural Replace" })
end,
},
{
@ -80,8 +87,8 @@ return {
-- 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
-- local plugins can also be configure 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 },
}

View file

@ -1,217 +1,62 @@
local Util = require("lazy.util")
local Config = require("lazy.core.config")
local Process = require("lazy.manage.process")
local uv = vim.uv or vim.loop
local M = {}
-- "report_" prefix has been deprecated, use the recommended replacements if they exist.
local start = vim.health.start or vim.health.report_start
local ok = vim.health.ok or vim.health.report_ok
local warn = vim.health.warn or vim.health.report_warn
local error = vim.health.error or vim.health.report_error
local info = vim.health.info or vim.health.report_info
---@class LazyHealth
---@field error? fun(msg:string)
---@field warn? fun(msg:string)
---@field ok? fun(msg:string)
---@class LazyHealthHave : LazyHealth
---@field version? string
---@field version_pattern? string
---@field optional? boolean
---@param cmd string|string[]
---@param opts? LazyHealthHave
function M.have(cmd, opts)
opts = vim.tbl_extend("force", {
error = error,
warn = warn,
ok = ok,
version = "--version",
}, opts or {})
cmd = type(cmd) == "table" and cmd or { cmd }
---@cast cmd string[]
---@type string?
local found
for _, c in ipairs(cmd) do
if vim.fn.executable(c) == 1 then
local out, exit_code = Process.exec({ c, opts.version })
if exit_code ~= 0 then
opts.error(("failed to get version of {%s}\n%s"):format(c, table.concat(out, "\n")))
else
local version = vim.trim(out[1] or "")
version = version:gsub("^%s*" .. vim.pesc(c) .. "%s*", "")
if opts.version_pattern and not version:find(opts.version_pattern, 1, true) then
opts.warn(("`%s` version `%s` needed, but found `%s`"):format(c, opts.version_pattern, version))
else
found = ("{%s} `%s`"):format(c, version)
break
end
end
end
end
if found then
opts.ok(found)
return true
else
(opts.optional and opts.warn or opts.error)(
("{%s} %snot installed"):format(
table.concat(cmd, "} or {"),
opts.version_pattern and "version `" .. opts.version_pattern .. "` " or ""
)
)
end
end
function M.check()
start("lazy.nvim")
info("{lazy.nvim} version `" .. Config.version .. "`")
M.have("git")
local sites = vim.opt.packpath:get()
local default_site = vim.fn.stdpath("data") .. "/site"
if not vim.tbl_contains(sites, default_site) then
sites[#sites + 1] = default_site
end
vim.health.report_start("lazy.nvim")
local existing = false
for _, site in pairs(sites) do
for _, packs in ipairs(vim.fn.expand(site .. "/pack/*", false, true)) do
if not packs:find("[/\\]dist$") and uv.fs_stat(packs) then
Util.ls(vim.fn.stdpath("data") .. "/site/pack/", function(path)
existing = true
warn("found existing packages at `" .. packs .. "`")
end
end
end
vim.health.report_warn("found existing packages at `" .. path .. "`")
end)
if not existing then
ok("no existing packages found by other package managers")
end
for _, name in ipairs({ "packer", "plugged", "paq", "pckr", "mini.deps" }) do
for _, path in ipairs(vim.opt.rtp:get()) do
if path:find(name, 1, true) then
error("Found paths on the rtp from another plugin manager `" .. name .. "`")
break
end
end
vim.health.report_ok("no existing packages found by other package managers")
end
local packer_compiled = vim.fn.stdpath("config") .. "/plugin/packer_compiled.lua"
if uv.fs_stat(packer_compiled) then
error("please remove the file `" .. packer_compiled .. "`")
if vim.loop.fs_stat(packer_compiled) then
vim.health.report_error("please remove the file `" .. packer_compiled .. "`")
else
ok("packer_compiled.lua not found")
vim.health.report_ok("packer_compiled.lua not found")
end
local spec = Config.spec
if spec == nil then
error('No plugins loaded. Did you forget to run `require("lazy").setup()`?')
else
for _, plugin in pairs(spec.plugins) do
M.check_valid(plugin)
end
if #spec.notifs > 0 then
error("Issues were reported when loading your specs:")
for _, notif in ipairs(spec.notifs) do
local lines = vim.split(notif.msg, "\n")
for _, line in ipairs(lines) do
if notif.level == vim.log.levels.ERROR then
error(line)
else
warn(line)
end
end
end
end
end
start("luarocks")
if Config.options.rocks.enabled then
if Config.hererocks() then
info("checking `hererocks` installation")
else
info("checking `luarocks` installation")
end
local need_luarocks = {}
for _, plugin in pairs(spec.plugins) do
if plugin.build == "rockspec" then
table.insert(need_luarocks, plugin.name)
end
end
if #need_luarocks == 0 then
ok("no plugins require `luarocks`, so you can ignore any warnings below")
else
local lines = vim.tbl_map(function(name)
return " * `" .. name .. "`"
end, need_luarocks)
info("you have some plugins that require `luarocks`:\n" .. table.concat(lines, "\n"))
end
local ok = require("lazy.pkg.rockspec").check({
error = #need_luarocks > 0 and error or warn,
warn = warn,
ok = ok,
})
if not ok then
warn(table.concat({
"Lazy won't be able to install plugins that require `luarocks`.",
"Here's what you can do:",
" - fix your `luarocks` installation",
Config.hererocks() and " - disable *hererocks* with `opts.rocks.hererocks = false`"
or " - enable `hererocks` with `opts.rocks.hererocks = true`",
" - disable `luarocks` support completely with `opts.rocks.enabled = false`",
}, "\n"))
end
else
ok("luarocks disabled")
end
end
---@param plugin LazyPlugin
function M.check_valid(plugin)
for key in pairs(plugin) do
if not vim.tbl_contains(M.valid, key) then
if key ~= "module" or type(plugin.module) ~= "boolean" then
warn("{" .. plugin.name .. "}: unknown key <" .. key .. ">")
end
end
end
end
M.valid = {
local valid = {
1,
"_",
"branch",
"build",
"cmd",
"commit",
"cond",
"config",
"deactivate",
"dependencies",
"dev",
"dir",
"enabled",
"event",
"ft",
"import",
"init",
"keys",
"lazy",
"main",
"module",
"name",
"optional",
"opts",
"pin",
"priority",
"submodules",
"tag",
"url",
"enabled",
"lazy",
"dev",
"dependencies",
"init",
"config",
"build",
"branch",
"tag",
"commit",
"version",
"module",
"pin",
"cmd",
"event",
"keys",
"ft",
"dir",
"priority",
"cond",
"_",
}
for _, plugin in pairs(Config.plugins) do
for key in pairs(plugin) do
if not vim.tbl_contains(valid, key) then
if key ~= "module" or type(plugin.module) ~= "boolean" then
vim.health.report_warn("{" .. plugin.name .. "}: unknown key <" .. key .. ">")
end
end
end
end
end
return M

View file

@ -4,54 +4,38 @@ local Util = require("lazy.util")
local M = {}
function M.index(plugin)
if Config.options.readme.skip_if_doc_exists and vim.uv.fs_stat(plugin.dir .. "/doc") then
if Config.options.readme.skip_if_doc_exists and vim.loop.fs_stat(plugin.dir .. "/doc") then
return {}
end
local files = {}
for _, file in ipairs(Config.options.readme.files) do
vim.list_extend(files, vim.fn.expand(plugin.dir .. "/" .. file, false, true))
end
---@type table<string,{file:string, tag:string, line:string}>
local tags = {}
for _, file in ipairs(files) do
file = Util.norm(file)
if vim.uv.fs_stat(file) then
local rel_file = file:sub(#plugin.dir + 1)
local tag_filename = plugin.name .. vim.fn.fnamemodify(rel_file, ":h"):gsub("%W+", "-"):gsub("^%-$", "")
for _, file in ipairs(Config.options.readme.files) do
file = plugin.dir .. "/" .. file
if vim.loop.fs_stat(file) then
local lines = vim.split(Util.read_file(file), "\n")
for _, line in ipairs(lines) do
local title = line:match("^#+%s*(.*)")
if title then
local tag = tag_filename .. "-" .. title:lower():gsub("%W+", "-")
local tag = plugin.name .. "-" .. title:lower():gsub("%W+", "-")
tag = tag:gsub("%-+", "-"):gsub("%-$", "")
line = line:gsub("([%[%]/])", "\\%1")
tags[tag] = { tag = tag, line = line, file = tag_filename .. ".md" }
tags[tag] = { tag = tag, line = line, file = plugin.name .. ".md" }
end
end
table.insert(lines, [[<!-- vim: set ft=markdown: -->]])
Util.write_file(Config.options.readme.root .. "/doc/" .. tag_filename .. ".md", table.concat(lines, "\n"))
Util.write_file(Config.options.readme.root .. "/doc/" .. plugin.name .. ".md", table.concat(lines, "\n"))
end
end
return tags
end
function M.update()
if Config.plugins["lazy.nvim"] then
vim.cmd.helptags(Config.plugins["lazy.nvim"].dir .. "/doc")
end
if Config.options.readme.enabled == false then
return
end
local docs = Config.options.readme.root .. "/doc"
vim.fn.mkdir(docs, "p")
Util.ls(docs, function(path, name, type)
if type == "file" and name:sub(-2) == "md" then
vim.uv.fs_unlink(path)
vim.loop.fs_unlink(path)
end
end)
---@type {file:string, tag:string, line:string}[]
@ -64,7 +48,7 @@ function M.update()
local lines = { [[!_TAG_FILE_ENCODING utf-8 //]] }
Util.foreach(tags, function(_, tag)
table.insert(lines, ("%s\t%s\t/%s"):format(tag.tag, tag.file, tag.line))
end, { case_sensitive = true })
end)
Util.write_file(docs .. "/tags", table.concat(lines, "\n"))
end

View file

@ -1,43 +1,11 @@
---@class Lazy: LazyCommands
---@type LazyCommands
local M = {}
M._start = 0
vim.uv = vim.uv or vim.loop
local function profile_require()
local done = {} ---@type table<string, true>
local r = require
_G.require = function(modname)
local Util = package.loaded["lazy.core.util"]
if Util and not done[modname] then
done[modname] = true
Util.track({ require = modname })
local ok, ret = pcall(function()
return vim.F.pack_len(r(modname))
end)
Util.track()
if not ok then
error(ret, 2)
end
return vim.F.unpack_len(ret)
else
return r(modname)
end
end
end
---@overload fun(opts: LazyConfig)
---@overload fun(spec:LazySpec, opts: LazyConfig)
---@param spec LazySpec Should be a module name to load, or a plugin spec
---@param opts? LazyConfig
function M.setup(spec, opts)
if type(spec) == "table" and spec.spec then
---@cast spec LazyConfig
opts = spec
else
opts = opts or {}
opts.spec = spec
end
M._start = M._start == 0 and vim.uv.hrtime() or M._start
M._start = M._start == 0 and vim.loop.hrtime() or M._start
if vim.g.lazy_did_setup then
return vim.notify(
"Re-sourcing your config is not supported with lazy.nvim",
@ -55,54 +23,32 @@ function M.setup(spec, opts)
if not (pcall(require, "ffi") and jit and jit.version) then
return vim.notify("lazy.nvim requires Neovim built with LuaJIT", vim.log.levels.ERROR, { title = "lazy.nvim" })
end
local start = vim.uv.hrtime()
local start = vim.loop.hrtime()
-- use the Neovim cache if available
if vim.loader and vim.fn.has("nvim-0.9.1") == 1 then
package.loaded["lazy.core.cache"] = vim.loader
end
local Cache = require("lazy.core.cache")
local enable_cache = vim.tbl_get(opts, "performance", "cache", "enabled") ~= false
-- load module cache before anything else
if enable_cache then
Cache.enable()
end
if vim.tbl_get(opts, "profiling", "require") then
profile_require()
end
require("lazy.stats").track("LazyStart")
require("lazy.core.cache").setup(opts)
local Util = require("lazy.core.util")
local Config = require("lazy.core.config")
local Loader = require("lazy.core.loader")
table.insert(package.loaders, 3, Loader.loader)
if vim.tbl_get(opts, "profiling", "loader") then
if vim.loader then
vim.loader._profile({ loaders = true })
else
Cache._profile_loaders()
end
end
local Plugin = require("lazy.core.plugin")
Util.track({ plugin = "lazy.nvim" }) -- setup start
Util.track("module", vim.uv.hrtime() - start)
Util.track("module", vim.loop.hrtime() - start)
-- load config
Util.track("config")
Config.setup(opts)
Config.setup(spec, opts)
Util.track()
-- load the plugins
Plugin.load()
-- setup loader and handlers
Loader.setup()
-- correct time delta and loaded
local delta = vim.uv.hrtime() - start
local delta = vim.loop.hrtime() - start
Util.track().time = delta -- end setup
if Config.plugins["lazy.nvim"] then
Config.plugins["lazy.nvim"]._.loaded = { time = delta, source = "init.lua" }
@ -112,8 +58,7 @@ function M.setup(spec, opts)
Loader.startup()
-- all done!
vim.api.nvim_exec_autocmds("User", { pattern = "LazyDone", modeline = false })
require("lazy.stats").track("LazyDone")
vim.cmd("do User LazyDone")
end
function M.stats()
@ -122,13 +67,13 @@ end
function M.bootstrap()
local lazypath = vim.fn.stdpath("data") .. "/lazy/lazy.nvim"
if not (vim.uv or vim.loop).fs_stat(lazypath) then
if not vim.loop.fs_stat(lazypath) then
vim.fn.system({
"git",
"clone",
"--filter=blob:none",
"--single-branch",
"https://github.com/folke/lazy.nvim.git",
"--branch=stable", -- latest stable release
lazypath,
})
end

View file

@ -1,9 +1,7 @@
local Config = require("lazy.core.config")
local Git = require("lazy.manage.git")
local Manage = require("lazy.manage")
local Plugin = require("lazy.core.plugin")
local State = require("lazy.state")
local Util = require("lazy.util")
local Git = require("lazy.manage.git")
local M = {}
@ -13,74 +11,41 @@ M.reported = {}
function M.start()
M.fast_check()
if M.schedule() > 0 and not M.has_errors() then
Manage.log({
clear = false,
show = false,
check = true,
concurrency = Config.options.checker.concurrency,
})
end
end
function M.schedule()
State.read() -- update state
local next_check = State.checker.last_check + Config.options.checker.frequency - os.time()
next_check = math.max(next_check, 0)
vim.defer_fn(M.check, next_check * 1000)
return next_check
M.check()
end
---@param opts? {report:boolean} report defaults to true
function M.fast_check(opts)
opts = opts or {}
for _, plugin in pairs(Config.plugins) do
-- don't check local plugins here, since we mark them as needing updates
-- only if local is behind upstream (if the git log task gives no output)
if plugin._.installed and not (plugin.pin or plugin._.is_local) then
plugin._.updates = nil
if not plugin.pin and plugin._.installed then
plugin._.has_updates = nil
local info = Git.info(plugin.dir)
local ok, target = pcall(Git.get_target, plugin)
if ok and info and target and not Git.eq(info, target) then
plugin._.updates = { from = info, to = target }
if ok and info and target and info.commit ~= target.commit then
plugin._.has_updates = true
end
end
end
M.report(opts.report ~= false)
end
function M.has_errors()
for _, plugin in pairs(Config.plugins) do
if Plugin.has_errors(plugin) then
return true
end
end
return false
end
function M.check()
State.checker.last_check = os.time()
State.write() -- update state
if M.has_errors() then
M.schedule()
else
Manage.check({
clear = false,
show = false,
concurrency = Config.options.checker.concurrency,
}):wait(function()
M.report()
M.schedule()
vim.defer_fn(M.check, Config.options.checker.frequency * 1000)
end)
end
end
---@param notify? boolean
function M.report(notify)
local lines = {}
M.updated = {}
for _, plugin in pairs(Config.plugins) do
if plugin._.updates then
if plugin._.has_updates then
table.insert(M.updated, plugin.name)
if not vim.tbl_contains(M.reported, plugin.name) then
table.insert(lines, "- **" .. plugin.name .. "**")
@ -88,7 +53,7 @@ function M.report(notify)
end
end
end
if #lines > 0 and notify and Config.options.checker.notify and not Config.headless() then
if #lines > 0 and notify and Config.options.checker.notify then
table.insert(lines, 1, "# Plugin Updates")
Util.info(lines)
end

View file

@ -1,7 +1,7 @@
local Util = require("lazy.util")
local Semver = require("lazy.manage.semver")
local Config = require("lazy.core.config")
local Process = require("lazy.manage.process")
local Semver = require("lazy.manage.semver")
local Util = require("lazy.util")
local M = {}
@ -11,20 +11,21 @@ local M = {}
---@param details? boolean Fetching details is slow! Don't loop over a plugin to fetch all details!
---@return GitInfo?
function M.info(repo, details)
local line = M.head(repo)
local line = Util.head(repo .. "/.git/HEAD")
if line then
---@type string, string
local ref, branch = line:match("ref: refs/(heads/(.*))")
local ref, branch = line:match("ref: (refs/heads/(.*))")
local ret = ref and {
branch = branch,
commit = M.ref(repo, ref),
commit = Util.head(repo .. "/.git/" .. ref),
} or { commit = line }
if details then
for tag, tag_ref in pairs(M.get_tag_refs(repo)) do
if tag_ref == ret.commit then
ret.tag = tag
ret.version = ret.version or Semver.version(tag)
ret.version = Semver.version(tag)
break
end
end
end
@ -32,18 +33,6 @@ function M.info(repo, details)
end
end
---@param a GitInfo
---@param b GitInfo
function M.eq(a, b)
local ra = a.commit and a.commit:sub(1, 7)
local rb = b.commit and b.commit:sub(1, 7)
return ra == rb
end
function M.head(repo)
return Util.head(repo .. "/.git/HEAD")
end
---@class TaggedSemver: Semver
---@field tag string
@ -52,30 +41,15 @@ function M.get_versions(repo, spec)
local range = Semver.range(spec or "*")
---@type TaggedSemver[]
local versions = {}
for _, tag in ipairs(M.get_tags(repo)) do
local v = Semver.version(tag)
Util.ls(repo .. "/.git/refs/tags", function(_, name)
local v = Semver.version(name)
---@cast v TaggedSemver
if v and range:matches(v) then
v.tag = tag
v.tag = name
table.insert(versions, v)
end
end
return versions
end
function M.get_tags(repo)
---@type string[]
local ret = {}
Util.ls(repo .. "/.git/refs/tags", function(_, name)
ret[#ret + 1] = name
end)
for name in pairs(M.packed_refs(repo)) do
local tag = name:match("^tags/(.*)")
if tag then
ret[#ret + 1] = tag
end
end
return ret
return versions
end
---@param plugin LazyPlugin
@ -95,7 +69,7 @@ function M.get_branch(plugin)
end
-- fallback to local HEAD
main = assert(M.head(plugin.dir))
main = assert(Util.head(plugin.dir .. "/.git/HEAD"))
return main and main:match("ref: refs/heads/(.*)")
end
end
@ -116,12 +90,6 @@ end
---@param plugin LazyPlugin
---@return GitInfo?
function M.get_target(plugin)
if plugin._.is_local then
local info = M.info(plugin.dir)
local branch = assert(info and info.branch or M.get_branch(plugin))
return { branch = branch, commit = M.get_commit(plugin.dir, branch, true) }
end
local branch = assert(M.get_branch(plugin))
if plugin.commit then
@ -137,8 +105,7 @@ function M.get_target(plugin)
commit = M.ref(plugin.dir, "tags/" .. plugin.tag),
}
end
local version = (plugin.version == nil and plugin.branch == nil) and Config.options.defaults.version or plugin.version
local version = plugin.version or Config.options.defaults.version
if version then
local last = Semver.last(M.get_versions(plugin.dir, version))
if last then
@ -150,6 +117,7 @@ function M.get_target(plugin)
}
end
end
---@diagnostic disable-next-line: return-type-mismatch
return { branch = branch, commit = M.get_commit(plugin.dir, branch, true) }
end
@ -165,22 +133,7 @@ function M.ref(repo, ...)
end
-- otherwise just get the ref
return Util.head(repo .. "/.git/refs/" .. ref) or M.packed_refs(repo)[ref]
end
function M.packed_refs(repo)
local ok, refs = pcall(Util.read_file, repo .. "/.git/packed-refs")
---@type table<string,string>
local ret = {}
if ok then
for _, line in ipairs(vim.split(refs, "\n")) do
local ref, name = line:match("^(.*) refs/(.*)$")
if ref then
ret[name] = ref
end
end
end
return ret
return Util.head(repo .. "/.git/refs/" .. ref)
end
-- this is slow, so don't use on a loop over all plugins!
@ -189,12 +142,7 @@ function M.get_tag_refs(repo, tagref)
tagref = tagref or "--tags"
---@type table<string,string>
local tags = {}
local ok, lines = pcall(function()
return Process.exec({ "git", "show-ref", "-d", tagref }, { cwd = repo })
end)
if not ok then
return {}
end
local lines = Process.exec({ "git", "show-ref", "-d", tagref }, { cwd = repo })
for _, line in ipairs(lines) do
local ref, tag = line:match("^(%w+) refs/tags/([^%^]+)%^?{?}?$")
if ref then
@ -204,46 +152,4 @@ function M.get_tag_refs(repo, tagref)
return tags
end
---@param repo string
function M.get_origin(repo)
return M.get_config(repo)["remote.origin.url"]
end
---@param repo string
function M.get_config(repo)
local ok, config = pcall(Util.read_file, repo .. "/.git/config")
if not ok then
return {}
end
---@type table<string, string>
local ret = {}
---@type string
local current_section = nil
for line in config:gmatch("[^\n]+") do
-- Check if the line is a section header
local section = line:match("^%s*%[(.+)%]%s*$")
if section then
---@type string
current_section = section:gsub('%s+"', "."):gsub('"+%s*$', "")
else
-- Ignore comments and blank lines
if not line:match("^%s*[#;]") and line:match("%S") then
local key, value = line:match("^%s*(%S+)%s*=%s*(.+)%s*$")
ret[current_section .. "." .. key] = value
end
end
end
return ret
end
function M.count(repo, commit1, commit2)
local lines = Process.exec({ "git", "rev-list", "--count", commit1 .. ".." .. commit2 }, { cwd = repo })
return tonumber(lines[1] or "0") or 0
end
function M.age(repo, commit)
local lines = Process.exec({ "git", "show", "-s", "--format=%cr", "--date=short", commit }, { cwd = repo })
return lines[1] or ""
end
return M

View file

@ -1,6 +1,6 @@
local Config = require("lazy.core.config")
local Plugin = require("lazy.core.plugin")
local Runner = require("lazy.manage.runner")
local Plugin = require("lazy.core.plugin")
local M = {}
@ -11,20 +11,12 @@ local M = {}
---@field mode? string
---@field plugins? (LazyPlugin|string)[]
---@field concurrency? number
---@field lockfile? boolean
---@param ropts RunnerOpts
---@param opts? ManagerOpts
function M.run(ropts, opts)
opts = opts or {}
local mode = opts.mode
local event = mode and ("Lazy" .. mode:sub(1, 1):upper() .. mode:sub(2))
if event then
vim.api.nvim_exec_autocmds("User", { pattern = event .. "Pre", modeline = false })
end
if opts.plugins then
---@param plugin string|LazyPlugin
opts.plugins = vim.tbl_map(function(plugin)
@ -49,15 +41,16 @@ function M.run(ropts, opts)
local runner = Runner.new(ropts)
runner:start()
vim.api.nvim_exec_autocmds("User", { pattern = "LazyRender", modeline = false })
vim.cmd([[do User LazyRender]])
-- wait for post-install to finish
runner:wait(function()
vim.api.nvim_exec_autocmds("User", { pattern = "LazyRender", modeline = false })
vim.cmd([[do User LazyRender]])
Plugin.update_state()
require("lazy.manage.checker").fast_check({ report = false })
if event then
vim.api.nvim_exec_autocmds("User", { pattern = event, modeline = false })
local mode = opts.mode
if mode then
vim.cmd("do User Lazy" .. mode:sub(1, 1):upper() .. mode:sub(2))
end
end)
@ -80,23 +73,14 @@ function M.install(opts)
opts = M.opts(opts, { mode = "install" })
return M.run({
pipeline = {
"plugin.exists",
"git.clone",
{ "git.checkout", lockfile = opts.lockfile },
"git.checkout",
"plugin.docs",
{
"wait",
---@param runner Runner
sync = function(runner)
require("lazy.pkg").update()
Plugin.load()
runner:update()
end,
},
"plugin.build",
},
plugins = function(plugin)
return not (plugin._.installed and not plugin._.build)
return plugin.url and not plugin._.installed
end,
}, opts):wait(function()
require("lazy.manage.lock").update()
@ -104,27 +88,16 @@ function M.install(opts)
end)
end
---@param opts? ManagerOpts
---@param opts? ManagerOpts|{lockfile?:boolean}
function M.update(opts)
opts = M.opts(opts, { mode = "update" })
return M.run({
pipeline = {
"plugin.exists",
"git.origin",
"git.branch",
"git.fetch",
"git.status",
{ "git.checkout", lockfile = opts.lockfile },
"plugin.docs",
{
"wait",
---@param runner Runner
sync = function(runner)
require("lazy.pkg").update()
Plugin.load()
runner:update()
end,
},
"plugin.build",
{ "git.log", updated = true },
},
@ -149,10 +122,7 @@ function M.check(opts)
opts = opts or {}
return M.run({
pipeline = {
"plugin.exists",
{ "git.origin", check = true },
"git.fetch",
"git.status",
"wait",
{ "git.log", check = true },
},
@ -162,31 +132,17 @@ function M.check(opts)
}, opts)
end
---@param opts? ManagerOpts | {check?:boolean}
---@param opts? ManagerOpts
function M.log(opts)
opts = M.opts(opts, { mode = "log" })
return M.run({
pipeline = {
{ "git.origin", check = true },
{ "git.log", check = opts.check },
},
pipeline = { "git.log" },
plugins = function(plugin)
return plugin.url and plugin._.installed
end,
}, opts)
end
---@param opts? ManagerOpts
function M.build(opts)
opts = M.opts(opts, { mode = "build" })
return M.run({
pipeline = { { "plugin.build", force = true } },
plugins = function()
return false
end,
}, opts)
end
---@param opts? ManagerOpts
function M.sync(opts)
opts = M.opts(opts)
@ -200,18 +156,13 @@ function M.sync(opts)
end)
opts.show = false
end
vim.api.nvim_exec_autocmds("User", { pattern = "LazySyncPre", modeline = false })
local clean_opts = vim.deepcopy(opts)
clean_opts.plugins = nil
local clean = M.clean(clean_opts)
local clean = M.clean(opts)
local install = M.install(opts)
local update = M.update(opts)
clean:wait(function()
install:wait(function()
update:wait(function()
vim.api.nvim_exec_autocmds("User", { pattern = "LazySync", modeline = false })
vim.cmd([[do User LazySync]])
end)
end)
end)
@ -231,7 +182,7 @@ end
---@param plugins? LazyPlugin[]
function M.clear(plugins)
for _, plugin in pairs(plugins or Config.plugins) do
plugin._.updates = nil
plugin._.has_updates = nil
plugin._.updated = nil
plugin._.cloned = nil
plugin._.dirty = nil
@ -239,11 +190,11 @@ function M.clear(plugins)
if plugin._.tasks then
---@param task LazyTask
plugin._.tasks = vim.tbl_filter(function(task)
return task:running() or task:has_errors()
return task:is_running()
end, plugin._.tasks)
end
end
vim.api.nvim_exec_autocmds("User", { pattern = "LazyRender", modeline = false })
vim.cmd([[do User LazyRender]])
end
return M

View file

@ -3,53 +3,50 @@ local Git = require("lazy.manage.git")
local M = {}
---@alias LazyLockfile table<string, {commit:string, branch:string}>
---@type LazyLockfile
---@type table<string, {commit:string, branch:string}>
M.lock = {}
M._loaded = false
function M.update()
M.load()
vim.fn.mkdir(vim.fn.fnamemodify(Config.options.lockfile, ":p:h"), "p")
local f = assert(io.open(Config.options.lockfile, "wb"))
local f = assert(io.open(Config.options.lockfile, "w"))
f:write("{\n")
M.lock = {}
-- keep disabled and cond plugins
for name in pairs(M.lock) do
if not (Config.spec.disabled[name] or Config.spec.ignore_installed[name]) then
M.lock[name] = nil
end
end
for _, plugin in pairs(Config.plugins) do
if not plugin._.is_local and plugin._.installed then
local info = assert(Git.info(plugin.dir))
M.lock[plugin.name] = {
branch = info.branch or assert(Git.get_branch(plugin)),
commit = assert(info.commit, "commit is nil"),
}
end
end
---@param plugin LazyPlugin
local plugins = vim.tbl_filter(function(plugin)
return not plugin._.is_local and plugin._.installed
end, Config.plugins)
---@param plugin LazyPlugin
---@type string[]
local names = vim.tbl_keys(M.lock)
local names = vim.tbl_map(function(plugin)
return plugin.name
end, plugins)
table.sort(names)
for n, name in ipairs(names) do
local info = M.lock[name]
local plugin = Config.plugins[name]
if not plugin._.is_local and plugin._.installed then
local info = assert(Git.info(plugin.dir))
if not info.branch then
info.branch = assert(Git.get_branch(plugin))
end
info.commit = info.commit
-- f:write(([[ [%q] = { branch = %q, commit = %q },]]):format(name, info.branch, info.commit) .. "\n")
f:write(([[ %q: { "branch": %q, "commit": %q }]]):format(name, info.branch, info.commit))
if n ~= #names then
f:write(",\n")
end
---@diagnostic disable-next-line: assign-type-mismatch
M.lock[plugin.name] = info
end
f:write("\n}\n")
end
f:write("\n}")
f:close()
end
function M.load()
if M._loaded then
return
end
M.lock = {}
M._loaded = true
local f = io.open(Config.options.lockfile, "r")
@ -67,7 +64,9 @@ end
---@param plugin LazyPlugin
---@return {commit:string, branch:string}
function M.get(plugin)
if not M._loaded then
M.load()
end
return M.lock[plugin.name]
end

View file

@ -1,239 +1,134 @@
local Async = require("lazy.async")
local Config = require("lazy.core.config")
local M = {}
---@diagnostic disable-next-line: no-unknown
local uv = vim.uv
local uv = vim.loop
---@class ProcessOpts
---@field args string[]
---@field cwd? string
---@field on_line? fun(line:string)
---@field on_line? fun(string)
---@field on_exit? fun(ok:boolean, output:string)
---@field on_data? fun(data:string, is_stderr?:boolean)
---@field timeout? number
---@field env? table<string,string>
---@field env? string[]
local M = {}
---@type table<uv_process_t, LazyProcess>
M.running = setmetatable({}, { __mode = "k" })
---@class LazyProcess: Async
---@field handle? uv_process_t
---@field pid? number
---@field cmd string
---@field opts ProcessOpts
---@field timeout? uv_timer_t
---@field timedout? boolean
---@field data string
---@field check? uv_check_t
---@field code? number
---@field signal? number
local Process = setmetatable({}, { __index = Async.Async })
---@param cmd string|string[]
---@param opts? ProcessOpts
function Process.new(cmd, opts)
local self = setmetatable({}, { __index = Process })
---@async
Process.init(self, function()
self:_run()
end)
opts = opts or {}
opts.args = opts.args or {}
if type(cmd) == "table" then
self.cmd = cmd[1]
vim.list_extend(opts.args, vim.list_slice(cmd, 2))
else
self.cmd = cmd
end
opts.timeout = opts.timeout or (Config.options.git and Config.options.git.timeout * 1000)
-- make sure the cwd is valid
if not opts.cwd and type(uv.cwd()) ~= "string" then
opts.cwd = uv.os_homedir()
end
opts.on_line = opts.on_line and vim.schedule_wrap(opts.on_line) or nil
opts.on_data = opts.on_data and vim.schedule_wrap(opts.on_data) or nil
self.data = ""
self.opts = opts
self.code = 1
self.signal = 0
return self
end
---@async
function Process:_run()
self:guard()
local stdout = assert(uv.new_pipe())
local stderr = assert(uv.new_pipe())
self.handle = uv.spawn(self.cmd, {
stdio = { nil, stdout, stderr },
args = self.opts.args,
cwd = self.opts.cwd,
env = self:env(),
}, function(code, signal)
self.code = code
self.signal = signal
if self.timeout then
self.timeout:stop()
end
self.handle:close()
stdout:close()
stderr:close()
self:resume()
end)
if self.handle then
M.running[self.handle] = self
stdout:read_start(function(err, data)
self:on_data(err, data)
end)
stderr:read_start(function(err, data)
self:on_data(err, data, true)
end)
self:suspend()
while not (self.handle:is_closing() and stdout:is_closing() and stderr:is_closing()) do
Async.yield()
end
else
self.data = "Failed to spawn process " .. self.cmd .. " " .. vim.inspect(self.opts)
end
self:on_exit()
end
function Process:on_exit()
self.data = self.data:gsub("[^\r\n]+\r", "")
if self.timedout then
self.data = self.data .. "\n" .. "Process was killed because it reached the timeout"
elseif self.signal ~= 0 then
self.data = self.data .. "\n" .. "Process was killed with SIG" .. M.signals[self.signal]:upper()
end
if self.opts.on_exit then
self.opts.on_exit(self.code == 0 and self.signal == 0, self.data)
end
end
function Process:guard()
if self.opts.timeout then
self.timeout = assert(uv.new_timer())
self.timeout:start(self.opts.timeout, 0, function()
self.timedout = true
self:kill()
end)
end
end
function Process:env()
---@type table<string, string>
local env = vim.tbl_extend("force", {
GIT_SSH_COMMAND = "ssh -oBatchMode=yes",
}, uv.os_environ(), self.opts.env or {})
env.GIT_DIR = nil
env.GIT_WORK_TREE = nil
env.GIT_TERMINAL_PROMPT = "0"
env.GIT_INDEX_FILE = nil
---@type string[]
local env_flat = {}
for k, v in pairs(env) do
env_flat[#env_flat + 1] = k .. "=" .. v
end
return env_flat
end
---@param signals uv.aliases.signals|uv.aliases.signals[]|nil
function Process:kill(signals)
if not self.handle or self.handle:is_closing() then
return
end
signals = signals or { "sigterm", "sigkill" }
signals = type(signals) == "table" and signals or { signals }
---@cast signals uv.aliases.signals[]
local timer = assert(uv.new_timer())
timer:start(0, 1000, function()
if self.handle and not self.handle:is_closing() and #signals > 0 then
self.handle:kill(table.remove(signals, 1))
else
timer:stop()
end
end)
end
---@param err? string
---@param data? string
---@param is_stderr? boolean
function Process:on_data(err, data, is_stderr)
assert(not err, err)
if not data then
return
end
if self.opts.on_data then
self.opts.on_data(data, is_stderr)
end
self.data = self.data .. data:gsub("\r\n", "\n")
local lines = vim.split(vim.trim(self.data:gsub("\r$", "")):gsub("[^\n\r]+\r", ""), "\n")
if self.opts.on_line then
self.opts.on_line(lines[#lines])
end
end
M.signals = {
"hup",
"int",
"quit",
"ill",
"trap",
"abrt",
"bus",
"fpe",
"kill",
"usr1",
"segv",
"usr2",
"pipe",
"alrm",
"term",
"chld",
"cont",
"stop",
"tstp",
"ttin",
"ttou",
"urg",
"xcpu",
"xfsz",
"vtalrm",
"prof",
"winch",
"io",
"pwr",
"emt",
"sys",
"info",
}
---@param cmd string|string[]
---@param opts? ProcessOpts
function M.spawn(cmd, opts)
return Process.new(cmd, opts)
opts = opts or {}
opts.timeout = opts.timeout or (Config.options.git and Config.options.git.timeout * 1000)
local env = {
"GIT_TERMINAL_PROMPT=0",
"GIT_SSH_COMMAND=ssh -oBatchMode=yes",
}
if opts.env then
vim.list_extend(env, opts.env)
end
function M.abort()
for _, proc in pairs(M.running) do
proc:kill()
for key, value in
pairs(uv.os_environ() --[[@as string[] ]])
do
table.insert(env, key .. "=" .. value)
end
local stdout = uv.new_pipe()
local stderr = uv.new_pipe()
local output = ""
---@type vim.loop.Process
local handle = nil
local timeout
local killed = false
if opts.timeout then
timeout = uv.new_timer()
timeout:start(opts.timeout, 0, function()
if handle and not handle:is_closing() then
killed = true
uv.process_kill(handle, "sigint")
end
end)
end
handle = uv.spawn(cmd, {
stdio = { nil, stdout, stderr },
args = opts.args,
cwd = opts.cwd,
env = env,
}, function(exit_code, signal)
if timeout then
timeout:stop()
timeout:close()
end
handle:close()
stdout:close()
stderr:close()
local check = uv.new_check()
check:start(function()
if not stdout:is_closing() or not stderr:is_closing() then
return
end
check:stop()
if opts.on_exit then
output = output:gsub("[^\r\n]+\r", "")
if killed then
output = output .. "\n" .. "Process was killed because it reached the timeout"
end
vim.schedule(function()
opts.on_exit(exit_code == 0 and signal == 0, output)
end)
end
end)
end)
if not handle then
if opts.on_exit then
opts.on_exit(false, "Failed to spawn process " .. cmd .. " " .. vim.inspect(opts))
end
return
end
---@param data? string
local function on_output(err, data)
assert(not err, err)
if data then
output = output .. data:gsub("\r\n", "\n")
local lines = vim.split(vim.trim(output:gsub("\r$", "")):gsub("[^\n\r]+\r", ""), "\n")
if opts.on_line then
vim.schedule(function()
opts.on_line(lines[#lines])
end)
end
end
end
---@async
---@param cmd string|string[]
---@param opts? ProcessOpts
uv.read_start(stdout, on_output)
uv.read_start(stderr, on_output)
return handle
end
---@param cmd string[]
---@param opts? {cwd:string, env:table}
function M.exec(cmd, opts)
opts = opts or {}
local proc = M.spawn(cmd, opts)
proc:wait()
return vim.split(proc.data, "\n"), proc.code
---@type string[]
local lines
local job = vim.fn.jobstart(cmd, {
cwd = opts.cwd,
pty = false,
env = opts.env,
stdout_buffered = true,
on_stdout = function(_, _lines)
lines = _lines
end,
})
vim.fn.jobwait({ job })
return lines
end
return M

View file

@ -1,22 +1,25 @@
local Cache = require("lazy.core.cache")
local Config = require("lazy.core.config")
local Loader = require("lazy.core.loader")
local Plugin = require("lazy.core.plugin")
local Util = require("lazy.util")
local Plugin = require("lazy.core.plugin")
local Loader = require("lazy.core.loader")
local M = {}
---@type table<string, uv.aliases.fs_stat_table>
---@type table<string, CacheHash>
M.files = {}
---@type uv_timer_t
---@type vim.loop.Timer
M.timer = nil
M.root = nil
function M.enable()
if M.timer then
M.timer:stop()
end
if #Config.spec.modules > 0 then
M.timer = assert(vim.uv.new_timer())
if type(Config.spec) == "string" then
M.timer = vim.loop.new_timer()
M.root = vim.fn.stdpath("config") .. "/lua"
M.check(true)
M.timer:start(2000, 2000, M.check)
end
@ -29,12 +32,6 @@ function M.disable()
end
end
---@param h1 uv.aliases.fs_stat_table
---@param h2 uv.aliases.fs_stat_table
function M.eq(h1, h2)
return h1 and h2 and h1.size == h2.size and h1.mtime.sec == h2.mtime.sec and h1.mtime.nsec == h2.mtime.nsec
end
function M.check(start)
---@type table<string,true>
local checked = {}
@ -44,10 +41,10 @@ function M.check(start)
-- spec is a module
local function check(_, modpath)
checked[modpath] = true
local hash = vim.uv.fs_stat(modpath)
local hash = Cache.hash(modpath)
if hash then
if M.files[modpath] then
if not M.eq(M.files[modpath], hash) then
if not Cache.eq(M.files[modpath], hash) then
M.files[modpath] = hash
table.insert(changes, { file = modpath, what = "changed" })
end
@ -58,9 +55,7 @@ function M.check(start)
end
end
for _, modname in ipairs(Config.spec.modules) do
Util.lsmod(modname, check)
end
Util.lsmod(Config.spec --[[@as string]], M.root, check)
for file in pairs(M.files) do
if not checked[file] then
@ -76,22 +71,9 @@ function M.check(start)
Config.mapleader = vim.g.mapleader
end
if Loader.init_done and Config.maplocalleader ~= vim.g.maplocalleader then
vim.schedule(function()
require("lazy.core.util").warn("You need to set `vim.g.maplocalleader` **BEFORE** loading lazy")
end)
Config.maplocalleader = vim.g.maplocalleader
end
if not (start or #changes == 0) then
M.reload(changes)
end
end
---@param {file:string, what:string}[]
function M.reload(changes)
vim.schedule(function()
if Config.options.change_detection.notify and not Config.headless() then
if Config.options.change_detection.notify then
local lines = { "# Config Change Detected. Reloading...", "" }
for _, change in ipairs(changes) do
table.insert(lines, "- **" .. change.what .. "**: `" .. vim.fn.fnamemodify(change.file, ":p:~:.") .. "`")
@ -99,9 +81,10 @@ function M.reload(changes)
Util.warn(lines)
end
Plugin.load()
vim.api.nvim_exec_autocmds("User", { pattern = "LazyRender", modeline = false })
vim.api.nvim_exec_autocmds("User", { pattern = "LazyReload", modeline = false })
vim.cmd([[do User LazyRender]])
vim.cmd([[do User LazyReload]])
end)
end
end
return M

View file

@ -1,23 +1,21 @@
local Async = require("lazy.async")
local Config = require("lazy.core.config")
local Task = require("lazy.manage.task")
local Config = require("lazy.core.config")
local Util = require("lazy.util")
---@class RunnerOpts
---@field pipeline (string|{[1]:string, [string]:any})[]
---@field plugins? LazyPlugin[]|fun(plugin:LazyPlugin):any?
---@field concurrency? number
---@class RunnerTask
---@field task? LazyTask
---@field step number
---@alias PipelineStep {task:string, opts?:TaskOptions}
---@alias LazyRunnerTask {co:thread, status: {task?:LazyTask, waiting?:boolean}}
---@class Runner
---@field _plugins table<string,LazyPlugin>
---@field _plugins LazyPlugin[]
---@field _running LazyRunnerTask[]
---@field _pipeline PipelineStep[]
---@field _on_done fun()[]
---@field _opts RunnerOpts
---@field _running? Async
local Runner = {}
---@param opts RunnerOpts
@ -26,17 +24,13 @@ function Runner.new(opts)
self._opts = opts or {}
local plugins = self._opts.plugins
---@type LazyPlugin[]
local pp = {}
if type(plugins) == "function" then
pp = vim.tbl_filter(plugins, Config.plugins)
self._plugins = vim.tbl_filter(plugins, Config.plugins)
else
pp = plugins or Config.plugins
end
self._plugins = {}
for _, plugin in ipairs(pp) do
self._plugins[plugin.name] = plugin
self._plugins = plugins or Config.plugins
end
self._running = {}
self._on_done = {}
---@param step string|(TaskOptions|{[1]:string})
self._pipeline = vim.tbl_map(function(step)
@ -46,152 +40,118 @@ function Runner.new(opts)
return self
end
function Runner:plugin(name)
return self._plugins[name]
---@param entry LazyRunnerTask
function Runner:_resume(entry)
if entry.status.task and not entry.status.task:is_done() then
return true
end
local ok, status = coroutine.resume(entry.co)
if not ok then
Util.error("Could not resume a task\n" .. status)
end
entry.status = ok and status
return entry.status ~= nil
end
--- Update plugins
function Runner:update()
for name in pairs(self._plugins) do
self._plugins[name] = Config.plugins[name] or self._plugins[name]
function Runner:resume(waiting)
local running = 0
for _, entry in ipairs(self._running) do
if entry.status then
if waiting and entry.status.waiting then
entry.status.waiting = false
end
if not entry.status.waiting and self:_resume(entry) then
running = running + 1
if self._opts.concurrency and running >= self._opts.concurrency then
break
end
end
end
end
return running > 0 or (not waiting and self:resume(true))
end
function Runner:start()
---@async
self._running = Async.new(function()
self:_start()
for _, plugin in pairs(self._plugins) do
local co = coroutine.create(self.run_pipeline)
local ok, err = coroutine.resume(co, self, plugin)
if ok then
table.insert(self._running, { co = co, status = {} })
else
Util.error("Could not start tasks for " .. plugin.name .. "\n" .. err)
end
end
local check = vim.loop.new_check()
check:start(function()
if self:resume() then
return
end
check:stop()
self._running = {}
for _, cb in ipairs(self._on_done) do
vim.schedule(cb)
end
self._on_done = {}
end)
end
---@async
function Runner:_start()
---@type string[]
local names = vim.tbl_keys(self._plugins)
table.sort(names)
---@type table<string,RunnerTask>
local state = {}
local active = 1
local waiting = 0
---@type number?
local wait_step = nil
---@async
---@param resume? boolean
local function continue(resume)
active = 0
waiting = 0
wait_step = nil
local next = {} ---@type string[]
-- check running tasks
for _, name in ipairs(names) do
state[name] = state[name] or { step = 0 }
local s = state[name]
local is_running = s.task and s.task:running()
local step = self._pipeline[s.step]
if is_running then
-- still running
active = active + 1
-- selene:allow(empty_if)
elseif s.task and s.task:has_errors() then
-- don't continue tasks if there are errors
elseif step and step.task == "wait" and not resume then
-- waiting for sync
waiting = waiting + 1
wait_step = s.step
else
next[#next + 1] = name
end
end
-- schedule next tasks
for _, name in ipairs(next) do
if self._opts.concurrency and active >= self._opts.concurrency then
break
end
local s = state[name]
local plugin = self:plugin(name)
while s.step <= #self._pipeline do
if s.step == #self._pipeline then
-- done
s.task = nil
plugin._.working = false
break
elseif s.step < #self._pipeline then
-- next
s.step = s.step + 1
local step = self._pipeline[s.step]
---@param plugin LazyPlugin
function Runner:run_pipeline(plugin)
coroutine.yield()
for _, step in ipairs(self._pipeline) do
if step.task == "wait" then
plugin._.working = false
waiting = waiting + 1
wait_step = s.step
break
coroutine.yield({ waiting = true })
else
s.task = self:queue(plugin, step)
plugin._.working = true
if s.task then
active = active + 1
s.task:wake(false)
break
local task = self:queue(plugin, step.task, step.opts)
if task then
coroutine.yield({ task = task })
assert(task:is_done())
if task.error then
return
end
end
end
end
end
end
while active > 0 do
continue()
if active == 0 and waiting > 0 then
local sync = self._pipeline[wait_step]
if sync and sync.opts and type(sync.opts.sync) == "function" then
sync.opts.sync(self)
end
continue(true)
end
if active > 0 then
self._running:suspend()
end
end
end
---@param plugin LazyPlugin
---@param step PipelineStep
---@param task_name string
---@param opts? TaskOptions
---@return LazyTask?
function Runner:queue(plugin, step)
assert(self._running and self._running:running(), "Runner is not running")
local def = vim.split(step.task, ".", { plain = true })
function Runner:queue(plugin, task_name, opts)
assert(self._running)
local def = vim.split(task_name, ".", { plain = true })
---@type LazyTaskDef
local task_def = require("lazy.manage.task." .. def[1])[def[2]]
assert(task_def, "Task not found: " .. step.task)
local opts = step.opts or {}
assert(task_def)
opts = opts or {}
if not (task_def.skip and task_def.skip(plugin, opts)) then
return Task.new(plugin, def[2], task_def.run, opts)
local task = Task.new(plugin, def[2], task_def.run, opts)
task:start()
return task
end
end
function Runner:is_running()
return self._running and self._running:running()
end
-- Execute the callback async when done.
-- When no callback is specified, this will wait sync
---@param cb? fun()
function Runner:wait(cb)
if not self:is_running() then
if #self._running == 0 then
if cb then
cb()
end
return self
end
if cb then
self._running:on("done", cb)
table.insert(self._on_done, cb)
else
self._running:wait()
-- sync wait
while #self._running > 0 do
vim.wait(10)
end
end
return self
end

View file

@ -9,7 +9,6 @@ local M = {}
---@field patch number
---@field prerelease? string
---@field build? string
---@field input? string
local Semver = {}
Semver.__index = Semver
@ -91,7 +90,6 @@ function M.version(version)
patch = patch == "" and 0 or tonumber(patch),
prerelease = prerelease ~= "" and prerelease or nil,
build = build ~= "" and build or nil,
input = version,
}, Semver)
end
end

View file

@ -1,42 +1,28 @@
local Config = require("lazy.core.config")
local Util = require("lazy.util")
local Config = require("lazy.core.config")
---@type table<string, LazyTaskDef>
local M = {}
local function rm(dir)
local stat = vim.uv.fs_lstat(dir)
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)
else
vim.uv.fs_unlink(path)
end
end)
vim.uv.fs_rmdir(dir)
end
M.clean = {
skip = function(plugin)
return plugin._.is_local
end,
---@param opts? {rocks_only?:boolean}
run = function(self, opts)
opts = opts or {}
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 rock_root = Config.options.rocks.root .. "/" .. self.plugin.name
if vim.uv.fs_stat(rock_root) then
rm(rock_root)
end
local stat = vim.loop.fs_lstat(dir)
assert(stat.type == "directory", self.plugin.dir .. " should be a directory!")
if opts.rocks_only then
return
Util.walk(dir, function(path, _, type)
if type == "directory" then
vim.loop.fs_rmdir(path)
else
vim.loop.fs_unlink(path)
end
rm(dir)
end)
vim.loop.fs_rmdir(dir)
self.plugin._.installed = false
end,

View file

@ -1,53 +1,6 @@
local Async = require("lazy.async")
local Config = require("lazy.core.config")
local Git = require("lazy.manage.git")
local Lock = require("lazy.manage.lock")
local Util = require("lazy.util")
local throttle = {}
throttle.running = 0
throttle.waiting = {} ---@type Async[]
throttle.timer = vim.uv.new_timer()
function throttle.next()
throttle.running = 0
while #throttle.waiting > 0 and throttle.running < Config.options.git.throttle.rate do
---@type Async
local task = table.remove(throttle.waiting, 1)
task:resume()
throttle.running = throttle.running + 1
end
if throttle.running == 0 then
throttle.timer:stop()
end
end
function throttle.wait()
if not Config.options.git.throttle.enabled then
return
end
if not throttle.timer:is_active() then
throttle.timer:start(0, Config.options.git.throttle.duration, vim.schedule_wrap(throttle.next))
end
local running = Async.running()
if throttle.running < Config.options.git.throttle.rate then
throttle.running = throttle.running + 1
else
table.insert(throttle.waiting, running)
coroutine.yield("waiting")
running:suspend()
coroutine.yield("")
end
end
---@param plugin LazyPlugin
local function cooldown(plugin)
if not plugin._.last_check then
return false
end
local delta = (vim.uv.now() - plugin._.last_check) / 1000
return delta < Config.options.git.cooldown
end
local Config = require("lazy.core.config")
---@type table<string, LazyTaskDef>
local M = {}
@ -61,13 +14,11 @@ M.log = {
if opts.updated and not (plugin._.updated and plugin._.updated.from ~= plugin._.updated.to) then
return true
end
local stat = vim.uv.fs_stat(plugin.dir .. "/.git")
return not (stat and stat.type == "directory")
local stat = vim.loop.fs_stat(plugin.dir .. "/.git")
return stat and stat.type ~= "directory"
end,
---@async
---@param opts {args?: string[], updated?:boolean, check?:boolean}
run = function(self, opts)
-- self:spawn({ "sleep", "5" })
local args = {
"log",
"--pretty=format:%h %s (%cr)",
@ -78,32 +29,13 @@ M.log = {
"--no-show-signature",
}
local info, target
if opts.updated then
table.insert(args, self.plugin._.updated.from .. ".." .. (self.plugin._.updated.to or "HEAD"))
elseif opts.check then
info = assert(Git.info(self.plugin.dir))
target = assert(Git.get_target(self.plugin))
if not target.commit then
for k, v in pairs(target) do
error(k .. " '" .. v .. "' not found")
end
error("no target commit found")
end
local info = assert(Git.info(self.plugin.dir))
local target = assert(Git.get_target(self.plugin))
assert(target.commit, self.plugin.name .. " " .. target.branch)
if not self.plugin._.is_local then
if Git.eq(info, target) then
if Config.options.checker.check_pinned then
local last_commit = Git.get_commit(self.plugin.dir, target.branch, true)
if not Git.eq(info, { commit = last_commit }) then
self.plugin._.outdated = true
end
end
else
self.plugin._.updates = { from = info, to = target }
end
end
self.plugin._.has_updates = target.commit ~= info.commit
table.insert(args, info.commit .. ".." .. target.commit)
else
vim.list_extend(args, opts.args or Config.options.git.log)
@ -113,14 +45,6 @@ M.log = {
args = args,
cwd = self.plugin.dir,
})
-- for local plugins, mark as needing updates only if local is
-- behind upstream, i.e. if git log gave no output
if opts.check and self.plugin._.is_local then
if not vim.tbl_isempty(self:get_log()) then
self.plugin._.updates = { from = info, to = target }
end
end
end,
}
@ -128,45 +52,21 @@ M.clone = {
skip = function(plugin)
return plugin._.installed or plugin._.is_local
end,
---@async
run = function(self)
throttle.wait()
local args = {
"clone",
self.plugin.url,
"--filter=blob:none",
"--recurse-submodules",
"--single-branch",
"--progress",
}
if Config.options.git.filter then
args[#args + 1] = "--filter=blob:none"
end
if self.plugin.submodules ~= false then
args[#args + 1] = "--recurse-submodules"
end
args[#args + 1] = "--origin=origin"
-- If git config --global core.autocrlf is true on a Unix/Linux system, then the git clone
-- process will lead to files with CRLF endings. Vi / vim / neovim cannot handle this.
-- Force git to clone with core.autocrlf=false.
args[#args + 1] = "-c"
args[#args + 1] = "core.autocrlf=false"
args[#args + 1] = "--progress"
if self.plugin.branch then
vim.list_extend(args, { "-b", self.plugin.branch })
end
table.insert(args, self.plugin.dir)
if vim.fn.isdirectory(self.plugin.dir) == 1 then
require("lazy.manage.task.fs").clean.run(self, {})
end
local marker = self.plugin.dir .. ".cloning"
Util.write_file(marker, "")
self:spawn("git", {
args = args,
on_exit = function(ok)
@ -174,7 +74,6 @@ M.clone = {
self.plugin._.cloned = true
self.plugin._.installed = true
self.plugin._.dirty = true
vim.uv.fs_unlink(marker)
end
end,
})
@ -189,9 +88,8 @@ M.branch = {
return true
end
local branch = assert(Git.get_branch(plugin))
return Git.get_commit(plugin.dir, branch, true)
return Git.get_commit(plugin.dir, branch)
end,
---@async
run = function(self)
local args = {
"remote",
@ -208,99 +106,22 @@ M.branch = {
end,
}
-- check and switch origin
M.origin = {
skip = function(plugin)
if not plugin._.installed or plugin._.is_local then
return true
end
local origin = Git.get_origin(plugin.dir)
return origin == plugin.url
end,
---@async
---@param opts {check?:boolean}
run = function(self, opts)
if opts.check then
local origin = Git.get_origin(self.plugin.dir)
self:error({
"Origin has changed:",
" * old: " .. origin,
" * new: " .. self.plugin.url,
"Please run update to fix",
})
return
end
require("lazy.manage.task.fs").clean.run(self, opts)
M.clone.run(self, opts)
end,
}
M.status = {
skip = function(plugin)
return not plugin._.installed or plugin._.is_local
end,
---@async
run = function(self)
self:spawn("git", {
args = { "ls-files", "-d", "-m" },
cwd = self.plugin.dir,
on_exit = function(ok, output)
if ok then
local lines = vim.split(output, "\n")
---@type string[]
lines = vim.tbl_filter(function(line)
-- Fix doc/tags being marked as modified
if line:gsub("[\\/]", "/") == "doc/tags" then
local Process = require("lazy.manage.process")
Process.exec({ "git", "checkout", "--", "doc/tags" }, { cwd = self.plugin.dir })
return false
end
return line ~= ""
end, lines)
if #lines > 0 then
local msg = { "You have local changes in `" .. self.plugin.dir .. "`:" }
for _, line in ipairs(lines) do
msg[#msg + 1] = " * " .. line
end
msg[#msg + 1] = "Please remove them to update."
msg[#msg + 1] = "You can also press `x` to remove the plugin and then `I` to install it again."
self:error(msg)
end
end
end,
})
end,
}
-- fetches all needed origin branches
M.fetch = {
skip = function(plugin)
return not plugin._.installed or plugin._.is_local or cooldown(plugin)
return not plugin._.installed or plugin._.is_local
end,
---@async
run = function(self)
throttle.wait()
local args = {
"fetch",
"--recurse-submodules",
"--tags", -- also fetch remote tags
"--force", -- overwrite existing tags if needed
"--progress",
}
if self.plugin.submodules == false then
table.remove(args, 2)
end
self:spawn("git", {
args = args,
cwd = self.plugin.dir,
on_exit = function(ok)
if ok then
self.plugin._.last_check = vim.uv.now()
end
end,
})
end,
}
@ -312,10 +133,8 @@ M.checkout = {
return not plugin._.installed or plugin._.is_local
end,
---@async
---@param opts {lockfile?:boolean}
run = function(self, opts)
throttle.wait()
local info = assert(Git.info(self.plugin.dir))
local target = assert(Git.get_target(self.plugin))
@ -335,9 +154,9 @@ M.checkout = {
end
end
-- don't run checkout if target is already reached.
-- dont run checkout if target is already reached.
-- unless we just cloned, since then we won't have any data yet
if Git.eq(info, target) and info.branch == target.branch then
if info.commit == target.commit and info.branch == target.branch then
self.plugin._.updated = {
from = info.commit,
to = info.commit,
@ -351,10 +170,6 @@ M.checkout = {
"--recurse-submodules",
}
if self.plugin.submodules == false then
table.remove(args, 3)
end
if lock then
table.insert(args, lock.commit)
elseif target.tag then

View file

@ -1,29 +1,23 @@
local Async = require("lazy.async")
local Config = require("lazy.core.config")
local Process = require("lazy.manage.process")
local Terminal = require("lazy.terminal")
local colors = Config.options.headless.colors
---@class LazyTaskDef
---@field skip? fun(plugin:LazyPlugin, opts?:TaskOptions):any?
---@field run async fun(task:LazyTask, opts:TaskOptions)
---@field run fun(task:LazyTask, opts:TaskOptions)
---@alias LazyTaskFn async fun(task:LazyTask, opts:TaskOptions)
---@alias LazyTaskState fun():boolean?
---@class LazyMsg
---@field msg string
---@field level? number
---@class LazyTask: Async
---@class LazyTask
---@field plugin LazyPlugin
---@field name string
---@field private _log LazyMsg[]
---@field private _started number
---@field output string
---@field status string
---@field error? string
---@field private _task fun(task:LazyTask)
---@field private _running LazyPluginState[]
---@field private _started? number
---@field private _ended? number
---@field private _opts TaskOptions
---@field private _level number
local Task = setmetatable({}, { __index = Async.Async })
local Task = {}
---@class TaskOptions: {[string]:any}
---@field on_done? fun(task:LazyTask)
@ -31,210 +25,146 @@ local Task = setmetatable({}, { __index = Async.Async })
---@param plugin LazyPlugin
---@param name string
---@param opts? TaskOptions
---@param task LazyTaskFn
---@param task fun(task:LazyTask)
function Task.new(plugin, name, task, opts)
local self = setmetatable({}, { __index = Task })
---@async
Task.init(self, function()
self:_run(task)
end)
self:set_level()
local self = setmetatable({}, {
__index = Task,
})
self._opts = opts or {}
self._log = {}
self._running = {}
self._task = task
self._started = nil
self.plugin = plugin
self.name = name
self._started = vim.uv.hrtime()
self.output = ""
self.status = ""
plugin._.tasks = plugin._.tasks or {}
---@param other LazyTask
plugin._.tasks = vim.tbl_filter(function(other)
return other.name ~= name or other:running()
end, plugin._.tasks or {})
return other.name ~= name or other:is_running()
end, plugin._.tasks)
table.insert(plugin._.tasks, self)
self:render()
return self
end
---@param level? number
---@return LazyMsg[]
function Task:get_log(level)
level = level or vim.log.levels.DEBUG
return vim.tbl_filter(function(msg)
return msg.level >= level
end, self._log)
function Task:has_started()
return self._started ~= nil
end
---@param level? number
function Task:output(level)
return table.concat(
---@param m LazyMsg
vim.tbl_map(function(m)
return m.msg
end, self:get_log(level)),
"\n"
)
function Task:is_done()
return self:has_started() and not self:is_running()
end
function Task:status()
local ret = self._log[#self._log]
local msg = ret and vim.trim(ret.msg) or ""
return msg ~= "" and msg or nil
function Task:is_running()
return self:has_started() and self._ended == nil
end
function Task:has_errors()
return self._level >= vim.log.levels.ERROR
end
function Task:has_warnings()
return self._level >= vim.log.levels.WARN
end
---@param level? number
function Task:set_level(level)
self._level = level or vim.log.levels.TRACE
end
---@async
---@param task LazyTaskFn
function Task:_run(task)
if Config.headless() and Config.options.headless.task then
self:log("Running task " .. self.name, vim.log.levels.INFO)
end
self
:on("done", function()
self:_done()
end)
:on("error", function(err)
self:error(err)
end)
:on("yield", function(msg)
self:log(msg)
end)
task(self, self._opts)
end
---@param msg string|string[]|LazyMsg
---@param level? number
function Task:log(msg, level)
if type(msg) == "table" and msg.msg then
level = msg.level or level
msg = msg.msg
end
level = level or vim.log.levels.DEBUG
self._level = math.max(self._level or 0, level or 0)
msg = type(msg) == "table" and table.concat(msg, "\n") or msg
---@cast msg string
table.insert(self._log, { msg = msg, level = level })
self:render()
if Config.headless() then
self:headless()
end
end
function Task:render()
vim.schedule(function()
vim.api.nvim_exec_autocmds("User", { pattern = "LazyRender", modeline = false })
function Task:start()
if vim.in_fast_event() then
return vim.schedule(function()
self:start()
end)
end
function Task:headless()
if not Config.options.headless.log then
return
self._started = vim.loop.hrtime()
---@type boolean, string|any
local ok, err = pcall(self._task, self, self._opts)
if not ok then
self.error = err or "failed"
end
local msg = self._log[#self._log]
if not msg or msg.level == vim.log.levels.TRACE then
return
end
local map = {
[vim.log.levels.ERROR] = Terminal.red,
[vim.log.levels.WARN] = Terminal.yellow,
[vim.log.levels.INFO] = Terminal.blue,
}
local color = Config.options.headless.colors and map[msg.level]
io.write(Terminal.prefix(color and color(msg.msg) or msg.msg, self:prefix()))
io.write("\n")
end
---@param msg string|string[]
function Task:error(msg)
self:log(msg, vim.log.levels.ERROR)
end
---@param msg string|string[]
function Task:warn(msg)
self:log(msg, vim.log.levels.WARN)
self:_check()
end
---@private
function Task:_done()
if Config.headless() and Config.options.headless.task then
local ms = math.floor(self:time() + 0.5)
self:log("Finished task " .. self.name .. " in " .. ms .. "ms", vim.log.levels.INFO)
function Task:_check()
for _, state in ipairs(self._running) do
if state() then
return
end
self._ended = vim.uv.hrtime()
end
self._ended = vim.loop.hrtime()
if self._opts.on_done then
self._opts.on_done(self)
end
self:render()
vim.schedule(function()
vim.cmd("do User LazyRender")
vim.api.nvim_exec_autocmds("User", {
pattern = "LazyPlugin" .. self.name:sub(1, 1):upper() .. self.name:sub(2),
data = { plugin = self.plugin.name },
})
end)
end
function Task:time()
return ((self._ended or vim.uv.hrtime()) - self._started) / 1e6
if not self:has_started() then
return 0
end
if not self:is_done() then
return (vim.loop.hrtime() - self._started) / 1e6
end
return (self._ended - self._started) / 1e6
end
---@param fn fun()
function Task:schedule(fn)
local done = false
table.insert(self._running, function()
return not done
end)
vim.schedule(function()
---@type boolean, string|any
local ok, err = pcall(fn)
if not ok then
self.error = err or "failed"
end
done = true
self:_check()
end)
end
---@async
---@param cmd string
---@param opts? ProcessOpts
function Task:spawn(cmd, opts)
opts = opts or {}
local on_line = opts.on_line
local headless = Config.headless() and Config.options.headless.process
local on_exit = opts.on_exit
function opts.on_line(line)
if not headless then
return self:log(line, vim.log.levels.TRACE)
end
self.status = line
if on_line then
pcall(on_line, line)
end
vim.cmd("do User LazyRender")
end
if headless then
opts.on_data = function(data)
-- prefix with plugin name
io.write(Terminal.prefix(data, self:prefix()))
---@param output string
function opts.on_exit(ok, output)
self.output = self.output .. output
if not ok then
self.error = self.error and (self.error .. "\n" .. output) or output
end
if on_exit then
pcall(on_exit, ok, output)
end
self:_check()
end
local proc = Process.spawn(cmd, opts)
proc:wait()
local ok = proc.code == 0 and proc.signal == 0
if not headless then
local msg = vim.trim(proc.data)
if #msg > 0 then
self:log(vim.trim(proc.data), ok and vim.log.levels.DEBUG or vim.log.levels.ERROR)
end
table.insert(self._running, function()
return proc and not proc:is_closing()
end)
end
if opts.on_exit then
pcall(opts.on_exit, ok, proc.data)
---@param tasks (LazyTask?)[]
function Task.all_done(tasks)
for _, task in ipairs(tasks) do
if task and not task:is_done() then
return false
end
return ok
end
return true
end
function Task:prefix()
local plugin = "[" .. self.plugin.name .. "] "
local task = string.rep(" ", 20 - #(self.name .. self.plugin.name)) .. self.name
return colors and Terminal.magenta(plugin) .. Terminal.cyan(task) .. Terminal.bright_black(" | ")
or plugin .. " " .. task .. " | "
function Task:wait()
while self:is_running() do
vim.wait(10)
end
end
return Task

View file

@ -1,84 +1,34 @@
local Loader = require("lazy.core.loader")
local Rocks = require("lazy.pkg.rockspec")
local Util = require("lazy.util")
local Loader = require("lazy.core.loader")
---@type table<string, LazyTaskDef>
local M = {}
---@param plugin LazyPlugin
local function get_build_file(plugin)
for _, path in ipairs({ "build.lua", "build/init.lua" }) do
if Util.file_exists(plugin.dir .. "/" .. path) then
return path
end
end
end
local B = {}
---@param task LazyTask
---@param build string
function B.cmd(task, build)
if task.plugin.build ~= "rockspec" then
Loader.load(task.plugin, { task = "build" })
end
local cmd = vim.api.nvim_parse_cmd(build:sub(2), {}) --[[@as vim.api.keyset.cmd]]
task:log(vim.api.nvim_cmd(cmd, { output = true }))
end
---@async
---@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 or plugin._.build) and (plugin.build or get_build_file(plugin)))
skip = function(plugin)
return not (plugin._.dirty and plugin.build)
end,
---@async
run = function(self)
vim.cmd([[silent! runtime plugin/rplugin.vim]])
Loader.load(self.plugin, { task = "build" })
local builders = self.plugin.build
-- Skip if `build` is set to `false`
if builders == false then
return
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) == "function" then
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
build(self.plugin)
elseif build == "rockspec" then
Rocks.build(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
chunk()
else
B.shell(self, build)
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,
})
end
end
end
@ -87,23 +37,12 @@ M.build = {
M.docs = {
skip = function(plugin)
return not plugin._.is_local and not plugin._.dirty
return not plugin._.dirty
end,
run = function(self)
local docs = self.plugin.dir .. "/doc"
local docs = self.plugin.dir .. "/doc/"
if Util.file_exists(docs) then
self:log(vim.api.nvim_cmd({ cmd = "helptags", args = { docs } }, { output = true }))
end
end,
}
M.exists = {
skip = function(plugin)
return not plugin._.is_local or plugin.virtual
end,
run = function(self)
if not Util.file_exists(self.plugin.dir) then
self:error("Local plugin does not exist at `" .. self.plugin.dir .. "`")
self.output = vim.api.nvim_cmd({ cmd = "helptags", args = { docs } }, { output = true })
end
end,
}

View file

@ -1,214 +0,0 @@
---@diagnostic disable: inject-field
local islist = vim.islist or vim.tbl_islist
local M = {}
---@param opts LazyConfig
---@return LazySpec[]
local function get_spec(opts)
local ret = opts.spec or {}
return ret and type(ret) == "table" and islist(ret) and ret or { ret }
end
---@param defaults LazyConfig
---@param opts LazyConfig
function M.extend(defaults, opts)
local spec = {}
vim.list_extend(spec, get_spec(defaults))
vim.list_extend(spec, get_spec(opts))
return vim.tbl_deep_extend("force", defaults, opts, { spec = spec })
end
---@param opts LazyConfig
function M.setup(opts)
opts = M.extend({
local_spec = false,
change_detection = { enabled = false },
dev = {
patterns = vim.env.LAZY_DEV and vim.split(vim.env.LAZY_DEV, ",") or nil,
},
}, opts)
local args = {}
local is_busted = false
local is_minitest = false
for _, a in ipairs(_G.arg) do
if a == "--busted" then
is_busted = true
elseif a == "--minitest" then
is_minitest = true
else
table.insert(args, a)
end
end
_G.arg = args
if is_busted then
opts = M.busted.setup(opts)
elseif is_minitest then
opts = M.minitest.setup(opts)
end
-- set stdpaths to use .tests
if vim.env.LAZY_STDPATH then
local root = vim.fn.fnamemodify(vim.env.LAZY_STDPATH, ":p")
for _, name in ipairs({ "config", "data", "state", "cache" }) do
vim.env[("XDG_%s_HOME"):format(name:upper())] = root .. "/" .. name
end
end
vim.o.loadplugins = true
require("lazy").setup(opts)
if vim.g.colors_name == nil then
vim.cmd("colorscheme habamax")
end
require("lazy").update():wait()
if vim.bo.filetype == "lazy" then
local errors = false
for _, plugin in pairs(require("lazy.core.config").spec.plugins) do
errors = errors or require("lazy.core.plugin").has_errors(plugin)
end
if not errors then
vim.cmd.close()
end
end
if is_busted then
M.busted.run()
elseif is_minitest then
M.minitest.run()
end
end
function M.repro(opts)
opts = M.extend({
spec = {
{
"folke/tokyonight.nvim",
priority = 1000,
lazy = false,
config = function()
require("tokyonight").setup({ style = "moon" })
require("tokyonight").load()
end,
},
},
install = { colorscheme = { "tokyonight" } },
}, opts)
M.setup(opts)
end
M.minitest = {}
function M.minitest.run()
local Config = require("lazy.core.config")
-- disable termnial output for the tests
Config.options.headless = {}
if not require("lazy.core.config").headless() then
return vim.notify("busted can only run in headless mode. Please run with `nvim -l`", vim.log.levels.WARN)
end
package.path = package.path .. ";" .. vim.uv.cwd() .. "/tests/?.lua"
local Test = require("mini.test")
local expect = Test.expect
local _assert = assert
local Assert = {
__call = function(_, ...)
return _assert(...)
end,
same = expect.equality,
equal = expect.equality,
are = {
equal = expect.equality,
},
is_not = {
same = expect.no_equality,
},
is_not_nil = function(a)
return expect.no_equality(nil, a)
end,
is_true = function(a)
return expect.equality(true, a)
end,
is_false = function(a)
return expect.equality(false, a)
end,
}
Assert.__index = Assert
assert = setmetatable({}, Assert)
assert = require("luassert")
require("mini.test").run()
end
---@param opts LazyConfig
function M.minitest.setup(opts)
return M.extend({
spec = {
"lunarmodules/luassert",
{
"echasnovski/mini.test",
opts = {
collect = {
find_files = function()
return vim.fn.globpath("tests", "**/*_spec.lua", true, true)
end,
},
-- script_path = "tests/minit.lua",
},
},
{ dir = vim.uv.cwd() },
},
rocks = { hererocks = true },
}, opts)
end
M.busted = {}
function M.busted.run()
local Config = require("lazy.core.config")
-- disable termnial output for the tests
Config.options.headless = {}
if not require("lazy.core.config").headless() then
return vim.notify("busted can only run in headless mode. Please run with `nvim -l`", vim.log.levels.WARN)
end
package.path = package.path .. ";" .. vim.uv.cwd() .. "/tests/?.lua"
-- run busted
return pcall(require("busted.runner"), {
standalone = false,
}) or os.exit(1)
end
---@param opts LazyConfig
function M.busted.setup(opts)
local args = table.concat(_G.arg, " ")
local json = args:find("--output[ =]json")
return M.extend({
spec = {
"lunarmodules/busted",
{ dir = vim.uv.cwd() },
},
headless = {
process = not json,
log = not json,
task = not json,
},
rocks = { hererocks = true },
}, opts)
end
---@param opts LazyConfig
function M.busted.init(opts)
opts = M.busted.setup(opts)
M.setup(opts)
M.busted.run()
end
setmetatable(M.busted, {
__call = function(_, opts)
M.busted.init(opts)
end,
})
return M

View file

@ -1,137 +0,0 @@
local Config = require("lazy.core.config")
local Util = require("lazy.core.util")
local M = {}
M.VERSION = 12
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 source? 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
if s ~= "rockspec" or Config.options.rocks.enabled then
sources[#sources + 1] = {
name = s,
get = require("lazy.pkg." .. s).get,
}
end
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 = spec.source or 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 U = require("lazy.util")
local code = "return " .. U.dump(ret)
vim.fn.mkdir(vim.fn.fnamemodify(Config.options.pkg.cache, ":h"), "p")
U.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,
})

View file

@ -1,29 +0,0 @@
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

View file

@ -1,54 +0,0 @@
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

View file

@ -1,343 +0,0 @@
--# selene:allow(incorrect_standard_library_use)
local Community = require("lazy.community")
local Config = require("lazy.core.config")
local Health = require("lazy.health")
local Util = require("lazy.util")
---@class RockSpec
---@field rockspec_format string
---@field package string
---@field version string
---@field dependencies string[]
---@field build? {type?: string, modules?: any[]}
---@field source? {url?: string}
---@class RockManifest
---@field repository table<string, table<string,any>>
local M = {}
M.skip = { "lua" }
M.rewrites = {
["plenary.nvim"] = { "nvim-lua/plenary.nvim", lazy = true },
}
M.python = { "python3", "python" }
---@class HereRocks
M.hererocks = {}
---@param task LazyTask
function M.hererocks.build(task)
local root = Config.options.rocks.root .. "/hererocks"
---@param p string
local python = vim.tbl_filter(function(p)
return vim.fn.executable(p) == 1
end, M.python)[1]
task:spawn(python, {
args = {
"hererocks.py",
"--verbose",
"-l",
"5.1",
"-r",
"latest",
root,
},
cwd = task.plugin.dir,
})
end
---@param bin string
function M.hererocks.bin(bin)
local hererocks = Config.options.rocks.root .. "/hererocks/bin"
return Util.norm(hererocks .. "/" .. bin)
end
-- check if hererocks is building
---@return boolean?
function M.hererocks.building()
return vim.tbl_get(Config.plugins.hererocks or {}, "_", "build")
end
---@param opts? LazyHealth
function M.check(opts)
opts = vim.tbl_extend("force", {
error = Util.error,
warn = Util.warn,
ok = function() end,
}, opts or {})
local ok = false
if Config.hererocks() then
if M.hererocks.building() then
ok = true
else
ok = Health.have(M.python, opts)
ok = Health.have(M.hererocks.bin("luarocks")) and ok
Health.have(
M.hererocks.bin("lua"),
vim.tbl_extend("force", opts, {
version = "-v",
version_pattern = "5.1",
})
)
end
else
ok = Health.have("luarocks", opts)
Health.have(
{ "lua5.1", "lua", "lua-5.1" },
vim.tbl_extend("force", opts, {
version = "-v",
version_pattern = "5.1",
})
)
end
return ok
end
---@async
---@param task LazyTask
function M.build(task)
M.check({
error = function(msg)
task:error(msg:gsub("[{}]", "`"))
end,
warn = function(msg)
task:warn(msg)
end,
ok = function(msg) end,
})
if task:has_warnings() then
task:log({
"",
"This plugin requires `luarocks`. Try one of the following:",
" - fix your `luarocks` installation",
Config.hererocks() and " - disable *hererocks* with `opts.rocks.hererocks = false`"
or " - enable `hererocks` with `opts.rocks.hererocks = true`",
" - disable `luarocks` support completely with `opts.rocks.enabled = false`",
})
task:warn("\nWill try building anyway, but will likely fail...")
task:warn("\n" .. string.rep("-", 80) .. "\n")
task:set_level(vim.log.levels.WARN)
end
if task.plugin.name == "hererocks" then
return M.hererocks.build(task)
end
local env = {}
local luarocks = "luarocks"
if Config.hererocks() then
-- hererocks is still building, so skip for now
-- a new build will happen in the next round
if M.hererocks.building() then
return
end
local sep = Util.is_win and ";" or ":"
local hererocks = Config.options.rocks.root .. "/hererocks/bin"
if Util.is_win then
hererocks = hererocks:gsub("/", "\\")
end
local path = vim.split(vim.env.PATH, sep)
table.insert(path, 1, hererocks)
env = {
PATH = table.concat(path, sep),
}
if Util.is_win then
luarocks = luarocks .. ".bat"
end
end
local pkg = task.plugin._.pkg
assert(pkg, "missing rockspec pkg for " .. task.plugin.name .. "\nThis shouldn't happen, please report.")
local rockspec = M.rockspec(task.plugin.dir .. "/" .. pkg.file) or {}
assert(
rockspec.package,
"missing rockspec package name for " .. task.plugin.name .. "\nThis shouldn't happen, please report."
)
local root = Config.options.rocks.root .. "/" .. task.plugin.name
local ok = task:spawn(luarocks, {
args = {
"--tree",
root,
"--server",
Config.options.rocks.server,
"--lua-version",
"5.1",
"install", -- use install so that we can make use of pre-built rocks
"--force-fast",
"--deps-mode",
"one",
rockspec.package,
},
cwd = task.plugin.dir,
env = env,
})
if ok then
return
end
task:warn("Failed installing " .. rockspec.package .. " with `luarocks`.")
task:warn("\n" .. string.rep("-", 80) .. "\n")
task:warn("Trying to build from source.")
-- install failed, so try building from source
task:set_level() -- reset level
ok = task:spawn(luarocks, {
args = {
"--tree",
root,
"--dev",
"--lua-version",
"5.1",
"make",
"--force-fast",
"--deps-mode",
"one",
},
cwd = task.plugin.dir,
env = env,
})
if not ok then
require("lazy.manage.task.fs").clean.run(task, { rocks_only = true })
end
end
---@param rockspec RockSpec
function M.is_simple_build(rockspec)
local type = vim.tbl_get(rockspec, "build", "type")
return type == nil or type == "none" or (type == "builtin" and not rockspec.build.modules)
end
---@param file string
---@return table?
function M.parse(file)
local ret = {}
local ok = pcall(function()
loadfile(file, nil, ret)()
end) and ret or nil
return ok and ret or nil
end
---@param plugin LazyPlugin
function M.deps(plugin)
local root = Config.options.rocks.root .. "/" .. plugin.name
---@type RockManifest?
local manifest = M.parse(root .. "/lib/luarocks/rocks-5.1/manifest")
return manifest and vim.tbl_keys(manifest.repository or {})
end
---@param file string
---@return RockSpec?
function M.rockspec(file)
return M.parse(file)
end
---@param plugin LazyPlugin
function M.find_rockspec(plugin)
local rockspec_file ---@type string?
Util.ls(plugin.dir, function(path, name, t)
if t == "file" then
for _, suffix in ipairs({ "scm", "git", "dev" }) do
suffix = suffix .. "-1.rockspec"
if name:sub(-#suffix) == suffix then
rockspec_file = path
return false
end
end
end
end)
return rockspec_file
end
---@param plugin LazyPlugin
---@return LazyPkgSpec?
function M.get(plugin)
if Community.get_spec(plugin.name) then
return {
file = "community",
source = "lazy",
spec = Community.get_spec(plugin.name),
}
end
local rockspec_file = M.find_rockspec(plugin)
local rockspec = rockspec_file and M.rockspec(rockspec_file)
if not rockspec then
return
end
local has_lua = not not vim.uv.fs_stat(plugin.dir .. "/lua")
---@type LazyPluginSpec
local specs = {}
---@param dep string
local rocks = vim.tbl_filter(function(dep)
local name = dep:gsub("%s.*", "")
local url = Community.get_url(name)
local spec = Community.get_spec(name)
if spec then
-- community spec
table.insert(specs, spec)
return false
elseif url then
-- Neovim plugin rock
table.insert(specs, { url })
return false
end
return not vim.tbl_contains(M.skip, name)
end, rockspec.dependencies or {})
local use =
-- package without a /lua directory
not has_lua
-- has dependencies that are not skipped,
-- not in community specs,
-- and don't have a rockspec mapping
or #rocks > 0
-- has a complex build process
or not M.is_simple_build(rockspec)
if not use then
-- community specs only
return #specs > 0
and {
file = vim.fn.fnamemodify(rockspec_file, ":t"),
spec = {
plugin.name,
specs = specs,
build = false,
},
}
or nil
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,
},
}
end
return M

View file

@ -1,45 +0,0 @@
local Config = require("lazy.core.config")
local Util = require("lazy.util")
---@type LazyState
local M = {}
---@class LazyState
local defaults = {
checker = {
last_check = 0,
},
}
---@type LazyState
local data = nil
function M.read()
pcall(function()
---@diagnostic disable-next-line: cast-local-type
data = vim.json.decode(Util.read_file(Config.options.state))
end)
data = vim.tbl_deep_extend("force", {}, defaults, data or {})
end
function M.write()
vim.fn.mkdir(vim.fn.fnamemodify(Config.options.state, ":p:h"), "p")
Util.write_file(Config.options.state, vim.json.encode(data))
end
function M.__index(_, key)
if not data then
M.read()
end
return data[key]
end
function M.__setindex(_, key, value)
if not data then
M.read()
end
---@diagnostic disable-next-line: no-unknown
data[key] = value
end
return setmetatable(M, M)

View file

@ -1,72 +1,44 @@
local ffi = require("ffi")
local M = {}
---@class LazyStats
M._stats = {
-- startuptime in milliseconds till UIEnter
startuptime = 0,
-- when true, startuptime is the accurate cputime for the Neovim process. (Linux & macOS)
-- 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,
startuptime_cputime = false,
count = 0, -- total number of plugins
loaded = 0, -- number of loaded plugins
---@type table<string, number>
times = {},
}
---@type ffi.namespace*
M.C = nil
function M.on_ui_enter()
M._stats.startuptime = M.track("UIEnter")
require("lazy.core.util").track({ start = "startuptime" }, M._stats.startuptime * 1e6)
vim.api.nvim_exec_autocmds("User", { pattern = "LazyVimStarted", modeline = false })
if not M.C then
pcall(function() end)
end
function M.track(event)
local time = M.cputime()
M._stats.times[event] = time
return time
end
function M.cputime()
if M.C == nil then
pcall(function()
local ok = pcall(function()
local ffi = require("ffi")
ffi.cdef([[
typedef long time_t;
typedef int clockid_t;
typedef struct timespec {
time_t tv_sec; /* seconds */
long tv_nsec; /* nanoseconds */
} nanotime;
int clock_gettime(clockid_t clk_id, struct timespec *tp);
]])
M.C = ffi.C
end)
end
local function real()
local pnano = assert(ffi.new("nanotime[?]", 1))
local CLOCK_PROCESS_CPUTIME_ID = jit.os == "OSX" and 12 or 2
ffi.C.clock_gettime(CLOCK_PROCESS_CPUTIME_ID, pnano)
return tonumber(pnano[0].tv_sec) * 1e3 + tonumber(pnano[0].tv_nsec) / 1e6
end
local function fallback()
return (vim.uv.hrtime() - require("lazy")._start) / 1e6
end
local ok, ret = pcall(real)
if ok then
M.cputime = real
M._stats.real_cputime = true
return ret
else
M.cputime = fallback
return fallback()
M._stats.startuptime = tonumber(pnano[0].tv_sec) / 1e6 + tonumber(pnano[0].tv_nsec) / 1e6
M._stats.startuptime_cputime = true
end)
if not ok then
M._stats.startuptime = (vim.loop.hrtime() - require("lazy")._start) / 1e6
end
vim.cmd([[do User LazyVimStarted]])
end
function M.stats()

View file

@ -1,71 +0,0 @@
---@class Ansi: table<string, fun(string):string>
local M = {}
M.colors = {
reset = "\27[0m",
black = "\27[30m",
red = "\27[31m",
green = "\27[32m",
yellow = "\27[33m",
blue = "\27[34m",
magenta = "\27[35m",
cyan = "\27[36m",
white = "\27[37m",
bright_black = "\27[90m",
bright_red = "\27[91m",
bright_green = "\27[92m",
bright_yellow = "\27[93m",
bright_blue = "\27[94m",
bright_magenta = "\27[95m",
bright_cyan = "\27[96m",
bright_white = "\27[97m",
}
function M.color(text, color)
return M.colors[color] .. text .. M.colors.reset
end
-- stylua: ignore start
function M.black(text) return M.color(text, "black") end
function M.red(text) return M.color(text, "red") end
function M.green(text) return M.color(text, "green") end
function M.yellow(text) return M.color(text, "yellow") end
function M.blue(text) return M.color(text, "blue") end
function M.magenta(text) return M.color(text, "magenta") end
function M.cyan(text) return M.color(text, "cyan") end
function M.white(text) return M.color(text, "white") end
function M.bright_black(text) return M.color(text, "bright_black") end
function M.bright_red(text) return M.color(text, "bright_red") end
function M.bright_green(text) return M.color(text, "bright_green") end
function M.bright_yellow(text) return M.color(text, "bright_yellow") end
function M.bright_blue(text) return M.color(text, "bright_blue") end
function M.bright_magenta(text) return M.color(text, "bright_magenta") end
function M.bright_cyan(text) return M.color(text, "bright_cyan") end
function M.bright_white(text) return M.color(text, "bright_white") end
-- stylua: ignore end
---@param data string
---@param prefix string
function M.prefix(data, prefix)
-- Normalize Windows-style newlines to simple newlines
data = data:gsub("\r\n", "\n")
-- Handle prefix for the first line, if data starts immediately
data = prefix .. data
-- Prefix new lines ensuring not to double prefix if a line starts with \r
data = data:gsub("(\n)([^\r])", "%1" .. prefix .. "%2")
-- Handle carriage returns properly to avoid double prefixing
-- Replace any \r not followed by \n with \r, then add a prefix only if the following character isn't the start of our prefix
data = data:gsub("\r([^\n])", function(nextChar)
if nextChar:sub(1, #prefix) == prefix then
return "\r" .. nextChar
else
return "\r" .. prefix .. nextChar
end
end)
return data
end
return M

View file

@ -1,102 +1,61 @@
---@alias LazyPluginKind "normal"|"clean"|"disabled"
---@alias LazyPluginKind "normal"|"clean"
---@class LazyPluginState
---@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 top? boolean
---@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 installed boolean
---@field tasks? LazyTask[]
---@field dirty? boolean
---@field updated? {from:string, to:string}
---@field updates? {from:GitInfo, to:GitInfo}
---@field last_check? number
---@field working? boolean
---@field pkg? LazyPkg
---@alias PluginOpts table|fun(self:LazyPlugin, opts:table):table?
---@field is_local boolean
---@field has_updates? boolean
---@field cloned? boolean
---@field kind? LazyPluginKind
---@field dep? boolean True if this plugin is only in the spec as a dependency
---@field cond? boolean
---@class LazyPluginHooks
---@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? false|string|async fun(self:LazyPlugin)|(string|async fun(self:LazyPlugin))[]
---@field opts? PluginOpts
---@field init? fun(LazyPlugin) Will always be run
---@field config? fun(LazyPlugin)|true|table Will be executed when loading the plugin
---@field build? string|fun(LazyPlugin)|(string|fun(LazyPlugin))[]
---@class LazyPluginHandlers
---@field event? table<string,LazyEvent>
---@field ft? table<string,LazyEvent>
---@field keys? table<string,LazyKeys>
---@field cmd? table<string,string>
---@field event? string[]
---@field cmd? string[]
---@field ft? string[]
---@field keys? string[]
---@field module? false
---@class LazyPluginRef
---@field branch? string
---@field tag? string
---@field commit? string
---@field version? string|boolean
---@field version? string
---@field pin? boolean
---@field submodules? boolean Defaults to true
---@class LazyPluginBase
---@field [1] string?
---@field name string display name and name used for plugin config files
---@field main? string Entry module that has setup & deactivate
---@field url string?
---@field dir string
---@field enabled? boolean|(fun():boolean)
---@field cond? boolean|(fun():boolean)
---@field optional? boolean If set, then this plugin will not be added unless it is added somewhere else
---@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[]
---@field virtual? boolean virtual plugins won't be installed or added to the rtp.
---@class LazyPlugin: LazyPluginBase,LazyPluginHandlers,LazyPluginHooks,LazyPluginRef
---@field dependencies? string[]
---@field specs? string|string[]|LazyPluginSpec[]
---@field _ LazyPluginState
---@class LazyPluginSpecHandlers
---@field event? string[]|string|LazyEventSpec[]|fun(self:LazyPlugin, event:string[]):string[]
---@field cmd? string[]|string|fun(self:LazyPlugin, cmd:string[]):string[]
---@field ft? string[]|string|fun(self:LazyPlugin, ft:string[]):string[]
---@field keys? string|string[]|LazyKeysSpec[]|fun(self:LazyPlugin, keys:string[]):((string|LazyKeys)[])
---@field event? string[]|string
---@field cmd? string[]|string
---@field ft? string[]|string
---@field keys? string|string[]|LazyKeys[]
---@field module? false
---@class LazyPluginSpec: LazyPluginBase,LazyPluginSpecHandlers,LazyPluginHooks,LazyPluginRef
---@field name? string display name and name used for plugin config files
---@field dir? string
---@field dependencies? string|string[]|LazyPluginSpec[]
---@field specs? string|string[]|LazyPluginSpec[]
---@alias LazySpec string|LazyPluginSpec|LazySpecImport|LazySpec[]
---@class LazySpecImport
---@field import string|(fun():LazyPluginSpec) spec module to import
---@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
---@alias LazySpec string|string[]|LazyPluginSpec[]|LazyPluginSpec[][]

View file

@ -2,45 +2,30 @@
local M = setmetatable({}, { __index = require("lazy.core.util") })
function M.file_exists(file)
return vim.uv.fs_stat(file) ~= nil
return vim.loop.fs_stat(file) ~= nil
end
---@param opts? LazyFloatOptions
---@return LazyFloat
---@param opts? LazyViewOptions
function M.float(opts)
opts = vim.tbl_deep_extend("force", {
win_opts = { zindex = 60, border = "none" },
margin = { top = 3, left = 2, right = 2 },
}, opts or {})
return require("lazy.view.float")(opts)
end
function M.wo(win, k, v)
if vim.api.nvim_set_option_value then
vim.api.nvim_set_option_value(k, v, { scope = "local", win = win })
else
vim.wo[win][k] = v
function M.open(uri)
if M.file_exists(uri) then
return M.float({ win_opts = { style = "" }, file = uri })
end
end
---@param opts? {system?:boolean}
function M.open(uri, opts)
opts = opts or {}
if not opts.system and M.file_exists(uri) then
return M.float({ style = "", file = uri })
end
local Config = require("lazy.core.config")
local cmd
if not opts.system and Config.options.ui.browser then
cmd = { Config.options.ui.browser, uri }
elseif vim.fn.has("win32") == 1 then
if vim.fn.has("win32") == 1 then
cmd = { "explorer", uri }
-- cmd = { 'cmd.exe', '/c', 'start', '""', uri }
elseif vim.fn.has("macunix") == 1 then
cmd = { "open", uri }
else
if vim.fn.executable("xdg-open") == 1 then
cmd = { "xdg-open", uri }
elseif vim.fn.executable("wslview") == 1 then
cmd = { "wslview", uri }
else
cmd = { "open", uri }
end
end
local ret = vim.fn.jobstart(cmd, { detach = true })
@ -73,118 +58,61 @@ end
---@param fn F
---@return F
function M.throttle(ms, fn)
---@type Async
local async
local pending = false
local timer = vim.loop.new_timer()
local running = false
local first = true
return function()
if async and async:running() then
pending = true
return
return function(...)
local args = { ... }
local wrapped = function()
fn(unpack(args))
end
if not running then
if first then
wrapped()
first = false
end
---@async
async = require("lazy.async").new(function()
repeat
pending = false
fn()
async:sleep(ms)
until not pending
timer:start(ms, 0, function()
running = false
vim.schedule(wrapped)
end)
running = true
end
end
end
--- Creates a weak reference to an object.
--- Calling the returned function will return the object if it has not been garbage collected.
---@generic T: table
---@param obj T
---@return T|fun():T?
function M.weak(obj)
local weak = { _obj = obj }
---@return table<any, any>
local function get()
local ret = rawget(weak, "_obj")
return ret == nil and error("Object has been garbage collected", 2) or ret
end
local mt = {
__mode = "v",
__call = function(t)
return rawget(t, "_obj")
end,
__index = function(_, k)
return get()[k]
end,
__newindex = function(_, k, v)
get()[k] = v
end,
__pairs = function()
return pairs(get())
end,
}
return setmetatable(weak, mt)
end
---@class LazyCmdOptions: LazyFloatOptions
---@field cwd? string
---@field env? table<string,string>
---@field float? LazyFloatOptions
-- Opens a floating terminal (interactive by default)
---@param cmd? string[]|string
---@param opts? LazyCmdOptions|{interactive?:boolean}
function M.float_term(cmd, opts)
cmd = cmd or {}
if type(cmd) == "string" then
cmd = { cmd }
end
if #cmd == 0 then
cmd = { vim.o.shell }
end
---@param cmd string[]
---@param opts? {cwd:string, filetype:string, terminal?:boolean, close_on_exit?:boolean, enter?:boolean}
function M.open_cmd(cmd, opts)
opts = opts or {}
local float = M.float(opts)
vim.fn.termopen(cmd, vim.tbl_isempty(opts) and vim.empty_dict() or opts)
if opts.interactive ~= false then
local float = M.float()
if opts.filetype then
vim.bo[float.buf].filetype = opts.filetype
end
if opts.terminal then
opts.terminal = nil
vim.fn.termopen(cmd, opts)
if opts.enter then
vim.cmd.startinsert()
end
if opts.close_on_exit then
vim.api.nvim_create_autocmd("TermClose", {
once = true,
buffer = float.buf,
callback = function()
float:close({ wipe = true })
vim.cmd.checktime()
float:close()
end,
})
end
return float
end
--- Runs the command and shows it in a floating window
---@param cmd string[]
---@param opts? LazyCmdOptions|{filetype?:string}
function M.float_cmd(cmd, opts)
opts = opts or {}
else
local Process = require("lazy.manage.process")
local lines, code = Process.exec(cmd, { cwd = opts.cwd })
if code ~= 0 then
M.error({
"`" .. table.concat(cmd, " ") .. "`",
"",
"## Error",
table.concat(lines, "\n"),
}, { title = "Command Failed (" .. code .. ")" })
return
end
local float = M.float(opts)
if opts.filetype then
vim.bo[float.buf].filetype = opts.filetype
end
local lines = Process.exec(cmd, { cwd = opts.cwd })
vim.api.nvim_buf_set_lines(float.buf, 0, -1, false, lines)
vim.bo[float.buf].modifiable = false
return float
end
---@deprecated use float_term or float_cmd instead
function M.open_cmd()
M.warn([[`require("lazy.util").open_cmd()` is deprecated. Please use `float_term` instead. Check the docs]])
end
---@return string?
@ -226,9 +154,9 @@ function M.markdown(msg, opts)
vim.tbl_deep_extend("force", {
title = "lazy.nvim",
on_open = function(win)
M.wo(win, "conceallevel", 3)
M.wo(win, "concealcursor", "n")
M.wo(win, "spell", false)
vim.wo[win].conceallevel = 3
vim.wo[win].concealcursor = "n"
vim.wo[win].spell = false
vim.treesitter.start(vim.api.nvim_win_get_buf(win), "markdown")
end,
@ -242,25 +170,20 @@ function M._dump(value, result)
table.insert(result, tostring(value))
elseif t == "string" then
table.insert(result, ("%q"):format(value))
elseif t == "table" and value._raw then
table.insert(result, value._raw)
elseif t == "table" then
table.insert(result, "{")
for _, v in ipairs(value) do
M._dump(v, result)
table.insert(result, ",")
end
local i = 1
---@diagnostic disable-next-line: no-unknown
for k, v in pairs(value) do
if type(k) == "string" then
if k:match("^[a-zA-Z]+$") then
table.insert(result, ("%s="):format(k))
else
if k == i then
elseif type(k) == "string" then
table.insert(result, ("[%q]="):format(k))
else
table.insert(result, k .. "=")
end
M._dump(v, result)
table.insert(result, ",")
end
i = i + 1
end
table.insert(result, "}")
else
@ -277,16 +200,10 @@ end
---@generic V
---@param t table<string, V>
---@param fn fun(key:string, value:V)
---@param opts? {case_sensitive?:boolean}
function M.foreach(t, fn, opts)
function M.foreach(t, fn)
---@type string[]
local keys = vim.tbl_keys(t)
pcall(table.sort, keys, function(a, b)
if opts and opts.case_sensitive then
return a < b
end
return a:lower() < b:lower()
end)
pcall(table.sort, keys)
for _, key in ipairs(keys) do
fn(key, t[key])
end

View file

@ -9,11 +9,9 @@ M.colors = {
CommitIssue = "Number",
CommitType = "Title", -- conventional commit type
CommitScope = "Italic", -- conventional commit scope
Dimmed = "Conceal", -- property
Prop = "Conceal", -- property
Value = "@string", -- value of a property
NoCond = "DiagnosticWarn", -- unloaded icon for a plugin where `cond()` was false
Local = "Constant",
ProgressDone = "Constant", -- progress bar done
ProgressTodo = "LineNr", -- progress bar todo
Special = "@punctuation.special",
@ -21,31 +19,26 @@ M.colors = {
ReasonPlugin = "Special",
ReasonEvent = "Constant",
ReasonKeys = "Statement",
ReasonStart = "@variable.member",
ReasonStart = "@field",
ReasonSource = "Character",
ReasonFt = "Character",
ReasonCmd = "Operator",
ReasonImport = "Identifier",
ReasonRequire = "@variable.parameter",
Button = "CursorLine",
ButtonActive = "Visual",
TaskOutput = "MsgArea", -- task output
Error = "DiagnosticError", -- task errors
Warning = "DiagnosticWarn", -- task errors
Info = "DiagnosticInfo", -- task errors
Dir = "@markup.link", -- directory
Url = "@markup.link", -- url
Bold = { bold = true },
Italic = { italic = true },
TaskError = "ErrorMsg", -- task errors
Dir = "@text.reference", -- directory
Url = "@text.reference", -- url
}
M.did_setup = false
function M.set_hl()
for hl_group, link in pairs(M.colors) do
local hl = type(link) == "table" and link or { link = link }
hl.default = true
vim.api.nvim_set_hl(0, "Lazy" .. hl_group, hl)
vim.api.nvim_set_hl(0, "Lazy" .. hl_group, {
link = link,
default = true,
})
end
end
@ -57,11 +50,6 @@ function M.setup()
M.did_setup = true
M.set_hl()
vim.api.nvim_create_autocmd("VimEnter", {
callback = function()
M.set_hl()
end,
})
vim.api.nvim_create_autocmd("ColorScheme", {
callback = function()
M.set_hl()

View file

@ -1,8 +1,7 @@
local require = require("lazy.core.util").lazy_require
local Config = require("lazy.core.config")
local View = require("lazy.view")
local Manage = require("lazy.manage")
local Util = require("lazy.util")
local View = require("lazy.view")
local Config = require("lazy.core.config")
local ViewConfig = require("lazy.view.config")
local M = {}
@ -14,12 +13,6 @@ function M.cmd(cmd, opts)
local command = M.commands[cmd] --[[@as fun(opts)]]
if command == nil then
Util.error("Invalid lazy command '" .. cmd .. "'")
elseif
ViewConfig.commands[cmd]
and ViewConfig.commands[cmd].plugins_required
and not (opts and vim.tbl_count(opts.plugins or {}) > 0)
then
return Util.error("`Lazy " .. cmd .. "` requires at least one plugin")
else
command(opts)
end
@ -31,24 +24,6 @@ M.commands = {
Manage.clear()
View.show()
end,
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,
@ -66,20 +41,12 @@ M.commands = {
end,
---@param opts ManagerOpts
load = function(opts)
-- when a command is executed with a bang, wait will be set
require("lazy.core.loader").load(opts.plugins, { cmd = "Lazy load" }, { force = opts.wait })
end,
reload = function(opts)
for _, plugin in pairs(opts.plugins) do
if type(plugin) == "string" then
plugin = Config.plugins[plugin]
end
Util.warn("Reloading **" .. plugin.name .. "**")
require("lazy.core.loader").reload(plugin)
if not (opts and opts.plugins and #opts.plugins > 0) then
return Util.error("`Lazy load` requires at least one plugin name to load")
end
require("lazy.core.loader").load(opts.plugins, { cmd = "LazyLoad" })
end,
log = Manage.log,
build = Manage.build,
clean = Manage.clean,
install = Manage.install,
sync = Manage.sync,
@ -89,14 +56,11 @@ M.commands = {
}
function M.complete(cmd, prefix)
if not (ViewConfig.commands[cmd] or {}).plugins and cmd ~= "pkg" then
if not (ViewConfig.commands[cmd] or {}).plugins then
return
end
---@type string[]
local plugins = {}
if cmd == "load" then
plugins[#plugins + 1] = "all"
end
for name, plugin in pairs(Config.plugins) do
if cmd ~= "load" or not plugin._.loaded then
plugins[#plugins + 1] = name
@ -105,7 +69,7 @@ function M.complete(cmd, prefix)
table.sort(plugins)
---@param key string
return vim.tbl_filter(function(key)
return key:find(prefix, 1, true) == 1
return key:find(prefix) == 1
end, plugins)
end
@ -114,9 +78,6 @@ function M.setup()
---@type ManagerOpts
local opts = { wait = cmd.bang == true }
local prefix, args = M.parse(cmd.args)
if #args == 1 and args[1] == "all" then
args = vim.tbl_keys(Config.plugins)
end
if #args > 0 then
---@param plugin string
opts.plugins = vim.tbl_map(function(plugin)
@ -125,7 +86,6 @@ function M.setup()
end
M.cmd(prefix, opts)
end, {
bar = true,
bang = true,
nargs = "?",
desc = "Lazy",
@ -137,7 +97,7 @@ function M.setup()
---@param key string
return vim.tbl_filter(function(key)
return key:find(prefix, 1, true) == 1
return key:find(prefix) == 1
end, vim.tbl_keys(M.commands))
end,
})
@ -146,7 +106,7 @@ end
---@return string, string[]
function M.parse(args)
local parts = vim.split(vim.trim(args), "%s+")
if vim.startswith("Lazy", parts[1]) then
if parts[1]:find("Lazy") then
table.remove(parts, 1)
end
if args:sub(-1) == " " then

View file

@ -24,8 +24,6 @@ function M.get_commands()
return ret
end
M.dimmed_commits = { "bot", "build", "ci", "chore", "doc", "style", "test" }
M.keys = {
hover = "K",
diff = "d",
@ -33,9 +31,6 @@ M.keys = {
details = "<cr>",
profile_sort = "<C-s>",
profile_filter = "<C-f>",
abort = "<C-c>",
next = "]]",
prev = "[[",
}
---@type table<string,LazyViewCommand>
@ -101,8 +96,8 @@ M.commands = {
},
restore = {
button = true,
desc = "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",
desc_plugin = "Restore a plugin to the state in the lockfile or to a given commit under the cursor",
desc = "Updates all plugins to the state in the lockfile",
desc_plugin = "Restore a plugin to the state in the lockfile",
id = 8,
key = "R",
key_plugin = "r",
@ -134,28 +129,11 @@ M.commands = {
id = 12,
},
load = {
desc = "Load a plugin that has not been loaded yet. Similar to `:packadd`. Like `:Lazy load foo.nvim`. Use `:Lazy! load` to skip `cond` checks.",
desc = "Load a plugin that has not been loaded yet. Similar to `:packadd`. Like `:Lazy load foo.nvim`",
id = 13,
plugins = true,
plugins_required = true,
},
health = {
desc = "Run `:checkhealth lazy`",
id = 14,
},
build = {
desc = "Rebuild a plugin",
id = 15,
plugins = true,
plugins_required = true,
key_plugin = "gb",
},
reload = {
desc = "Reload a plugin (experimental!!)",
plugins = true,
plugins_required = true,
id = 16,
},
}
return M

View file

@ -23,13 +23,11 @@ M.handlers = {
---@type LazyDiffFun
["diffview.nvim"] = function(plugin, diff)
local args
if diff.commit then
args = ("-C=%s"):format(plugin.dir) .. " " .. diff.commit .. "^!"
vim.cmd.DiffviewOpen(("-C=%s"):format(plugin.dir) .. " " .. diff.commit)
else
args = ("-C=%s"):format(plugin.dir) .. " " .. diff.from .. ".." .. diff.to
vim.cmd.DiffviewOpen(("-C=%s"):format(plugin.dir) .. " " .. diff.from .. ".." .. diff.to)
end
vim.cmd("DiffviewOpen " .. args)
end,
---@type LazyDiffFun
@ -43,7 +41,7 @@ M.handlers = {
cmd[#cmd + 1] = diff.from
cmd[#cmd + 1] = diff.to
end
Util.float_cmd(cmd, { cwd = plugin.dir, filetype = "git" })
Util.open_cmd(cmd, { cwd = plugin.dir, filetype = "git" })
end,
---@type LazyDiffFun
@ -57,7 +55,7 @@ M.handlers = {
cmd[#cmd + 1] = diff.from
cmd[#cmd + 1] = diff.to
end
Util.float_term(cmd, { cwd = plugin.dir, interactive = false, env = { PAGER = "cat" } })
Util.open_cmd(cmd, { cwd = plugin.dir, terminal = true, env = { PAGER = "cat" } })
end,
}

View file

@ -1,31 +1,20 @@
local Config = require("lazy.core.config")
local Util = require("lazy.util")
local ViewConfig = require("lazy.view.config")
---@class LazyFloatOptions
---@class LazyViewOptions
---@field buf? number
---@field file? string
---@field margin? {top?:number, right?:number, bottom?:number, left?:number}
---@field size? {width:number, height:number}
---@field zindex? number
---@field style? "" | "minimal"
---@field border? "none" | "single" | "double" | "rounded" | "solid" | "shadow"
---@field title? string
---@field title_pos? "center" | "left" | "right"
---@field persistent? boolean
---@field ft? string
---@field noautocmd? boolean
---@field backdrop? float
---@field win_opts LazyViewWinOpts
local defaults = {
win_opts = {},
}
---@class LazyFloat
---@field buf number
---@field win number
---@field opts LazyFloatOptions
---@field win_opts LazyWinOpts
---@field backdrop_buf number
---@field backdrop_win number
---@field id number
---@overload fun(opts?:LazyFloatOptions):LazyFloat
---@field opts LazyViewOptions
---@overload fun(opts?:LazyViewOptions):LazyFloat
local M = {}
setmetatable(M, {
@ -34,52 +23,18 @@ setmetatable(M, {
end,
})
local _id = 0
local function next_id()
_id = _id + 1
return _id
end
---@param opts? LazyFloatOptions
---@param opts? LazyViewOptions
function M.new(opts)
local self = setmetatable({}, { __index = M })
return self:init(opts)
end
---@param opts? LazyFloatOptions
---@param opts? LazyViewOptions
function M:init(opts)
require("lazy.view.colors").setup()
self.id = next_id()
self.opts = vim.tbl_deep_extend("force", {
size = Config.options.ui.size,
style = "minimal",
border = Config.options.ui.border or "none",
backdrop = Config.options.ui.backdrop or 60,
zindex = 50,
}, opts or {})
---@class LazyWinOpts
---@field width number
---@field height number
---@field row number
---@field col number
self.win_opts = {
relative = "editor",
style = self.opts.style ~= "" and self.opts.style or nil,
border = self.opts.border,
zindex = self.opts.zindex,
noautocmd = self.opts.noautocmd,
title = self.opts.title,
title_pos = self.opts.title and self.opts.title_pos or nil,
}
self.opts = vim.tbl_deep_extend("force", defaults, opts or {})
self:mount()
self:on("VimEnter", function()
vim.schedule(function()
if not self:win_valid() then
self:close()
end
end)
end, { buffer = false })
self:on_key(ViewConfig.keys.close, self.close)
self:on({ "BufDelete", "BufLeave", "BufHidden" }, self.close, { once = true })
return self
end
@ -87,173 +42,116 @@ function M:layout()
local function size(max, value)
return value > 1 and math.min(value, max) or math.floor(max * value)
end
self.win_opts.width = size(vim.o.columns, self.opts.size.width)
self.win_opts.height = size(vim.o.lines, self.opts.size.height)
self.win_opts.row = math.floor((vim.o.lines - self.win_opts.height) / 2)
self.win_opts.col = math.floor((vim.o.columns - self.win_opts.width) / 2)
if self.opts.border ~= "none" then
self.win_opts.row = self.win_opts.row - 1
self.win_opts.col = self.win_opts.col - 1
end
self.opts.win_opts.width = size(vim.o.columns, Config.options.ui.size.width)
self.opts.win_opts.height = size(vim.o.lines, Config.options.ui.size.height)
self.opts.win_opts.row = math.floor((vim.o.lines - self.opts.win_opts.height) / 2)
self.opts.win_opts.col = math.floor((vim.o.columns - self.opts.win_opts.width) / 2)
if self.opts.margin then
if self.opts.margin.top then
self.win_opts.height = self.win_opts.height - self.opts.margin.top
self.win_opts.row = self.win_opts.row + self.opts.margin.top
self.opts.win_opts.height = self.opts.win_opts.height - self.opts.margin.top
self.opts.win_opts.row = self.opts.win_opts.row + self.opts.margin.top
end
if self.opts.margin.right then
self.win_opts.width = self.win_opts.width - self.opts.margin.right
self.opts.win_opts.width = self.opts.win_opts.width - self.opts.margin.right
end
if self.opts.margin.bottom then
self.win_opts.height = self.win_opts.height - self.opts.margin.bottom
self.opts.win_opts.height = self.opts.win_opts.height - self.opts.margin.bottom
end
if self.opts.margin.left then
self.win_opts.width = self.win_opts.width - self.opts.margin.left
self.win_opts.col = self.win_opts.col + self.opts.margin.left
self.opts.win_opts.width = self.opts.win_opts.width - self.opts.margin.left
self.opts.win_opts.col = self.opts.win_opts.col + self.opts.margin.left
end
end
end
function M:mount()
if self:buf_valid() then
-- keep existing buffer
self.buf = self.buf
elseif self.opts.file then
if self.opts.file then
self.buf = vim.fn.bufadd(self.opts.file)
vim.bo[self.buf].readonly = true
vim.bo[self.buf].swapfile = false
vim.fn.bufload(self.buf)
vim.bo[self.buf].modifiable = false
elseif self.opts.buf then
self.buf = self.opts.buf
else
self.buf = vim.api.nvim_create_buf(false, true)
self.buf = vim.api.nvim_create_buf(false, false)
end
local normal, has_bg
if vim.fn.has("nvim-0.9.0") == 0 then
normal = vim.api.nvim_get_hl_by_name("Normal", true)
has_bg = normal and normal.background ~= nil
else
normal = vim.api.nvim_get_hl(0, { name = "Normal" })
has_bg = normal and normal.bg ~= nil
end
if has_bg and self.opts.backdrop and self.opts.backdrop < 100 and vim.o.termguicolors then
self.backdrop_buf = vim.api.nvim_create_buf(false, true)
self.backdrop_win = vim.api.nvim_open_win(self.backdrop_buf, false, {
---@class LazyViewWinOpts
---@field width number
---@field height number
---@field row number
---@field col number
local win_opts = {
relative = "editor",
width = vim.o.columns,
height = vim.o.lines,
row = 0,
col = 0,
style = "minimal",
focusable = false,
zindex = self.opts.zindex - 1,
})
vim.api.nvim_set_hl(0, "LazyBackdrop", { bg = "#000000", default = true })
Util.wo(self.backdrop_win, "winhighlight", "Normal:LazyBackdrop")
Util.wo(self.backdrop_win, "winblend", self.opts.backdrop)
vim.bo[self.backdrop_buf].buftype = "nofile"
vim.bo[self.backdrop_buf].filetype = "lazy_backdrop"
border = Config.options.ui.border,
noautocmd = true,
zindex = 50,
}
self.opts.win_opts = vim.tbl_extend("force", win_opts, self.opts.win_opts)
if self.opts.win_opts.style == "" then
self.opts.win_opts.style = nil
end
self:layout()
self.win = vim.api.nvim_open_win(self.buf, true, self.win_opts)
self:on("WinClosed", function()
self:close()
self:augroup(true)
end, { win = true })
self.win = vim.api.nvim_open_win(self.buf, true, self.opts.win_opts)
self:focus()
self:on_key(ViewConfig.keys.close, self.close, "Close")
self:on({ "BufDelete", "BufHidden" }, self.close)
if vim.bo[self.buf].buftype == "" then
vim.bo[self.buf].buftype = "nofile"
end
if vim.bo[self.buf].filetype == "" then
vim.bo[self.buf].filetype = self.opts.ft or "lazy"
vim.bo[self.buf].filetype = "lazy"
end
local function opts()
vim.bo[self.buf].bufhidden = self.opts.persistent and "hide" or "wipe"
Util.wo(self.win, "conceallevel", 3)
Util.wo(self.win, "foldenable", false)
Util.wo(self.win, "spell", false)
Util.wo(self.win, "wrap", true)
Util.wo(self.win, "winhighlight", "Normal:LazyNormal")
Util.wo(self.win, "colorcolumn", "")
end
opts()
vim.bo[self.buf].bufhidden = "wipe"
vim.wo[self.win].conceallevel = 3
vim.wo[self.win].spell = false
vim.wo[self.win].wrap = true
vim.wo[self.win].winhighlight = "Normal:LazyNormal"
vim.api.nvim_create_autocmd("VimResized", {
callback = function()
if not (self.win and vim.api.nvim_win_is_valid(self.win)) then
if not self.win then
return true
end
self:layout()
local config = {}
for _, key in ipairs({ "relative", "width", "height", "col", "row" }) do
---@diagnostic disable-next-line: no-unknown
config[key] = self.win_opts[key]
config[key] = self.opts.win_opts[key]
end
config.style = self.opts.style ~= "" and self.opts.style or nil
vim.api.nvim_win_set_config(self.win, config)
if self.backdrop_win and vim.api.nvim_win_is_valid(self.backdrop_win) then
vim.api.nvim_win_set_config(self.backdrop_win, {
width = vim.o.columns,
height = vim.o.lines,
})
end
opts()
vim.api.nvim_exec_autocmds("User", { pattern = "LazyFloatResized", modeline = false })
vim.cmd([[do User LazyFloatResized]])
end,
})
end
---@param clear? boolean
function M:augroup(clear)
return vim.api.nvim_create_augroup("trouble.window." .. self.id, { clear = clear == true })
end
---@param events string|string[]
---@param fn fun(self:LazyFloat, event:{buf:number}):boolean?
---@param opts? vim.api.keyset.create_autocmd | {buffer: false, win?:boolean}
---@param fn fun(self?):boolean?
---@param opts? table
function M:on(events, fn, opts)
opts = opts or {}
if opts.win then
opts.pattern = self.win .. ""
opts.win = nil
elseif opts.buffer == nil then
opts.buffer = self.buf
elseif opts.buffer == false then
opts.buffer = nil
if type(events) == "string" then
events = { events }
end
if opts.pattern then
opts.buffer = nil
for _, e in ipairs(events) do
local event, pattern = e:match("(%w+) (%w+)")
event = event or e
vim.api.nvim_create_autocmd(
event,
vim.tbl_extend("force", {
pattern = pattern,
buffer = (not pattern) and self.buf or nil,
callback = function()
return fn(self)
end,
}, opts or {})
)
end
local _self = Util.weak(self)
opts.callback = function(e)
local this = _self()
if not this then
-- delete the autocmd
return true
end
return fn(this, e)
end
opts.group = self:augroup()
vim.api.nvim_create_autocmd(events, opts)
end
---@param key string
---@param fn fun(self?)
---@param desc? string
---@param mode? string[]
function M:on_key(key, fn, desc, mode)
vim.keymap.set(mode or "n", key, function()
function M:on_key(key, fn, desc)
vim.keymap.set("n", key, function()
fn(self)
end, {
nowait = true,
@ -262,77 +160,22 @@ function M:on_key(key, fn, desc, mode)
})
end
---@param opts? {wipe:boolean}
function M:close(opts)
self:augroup(true)
function M:close()
local buf = self.buf
local win = self.win
local wipe = opts and opts.wipe
if wipe == nil then
wipe = not self.opts.persistent
end
self.win = nil
if wipe then
self.buf = nil
end
local backdrop_buf = self.backdrop_buf
local backdrop_win = self.backdrop_win
self.backdrop_buf = nil
self.backdrop_win = nil
vim.diagnostic.reset(Config.ns, buf)
vim.schedule(function()
if backdrop_win and vim.api.nvim_win_is_valid(backdrop_win) then
vim.api.nvim_win_close(backdrop_win, true)
end
if backdrop_buf and vim.api.nvim_buf_is_valid(backdrop_buf) then
vim.api.nvim_buf_delete(backdrop_buf, { force = true })
end
if win and vim.api.nvim_win_is_valid(win) then
vim.api.nvim_win_close(win, true)
end
if wipe and buf and vim.api.nvim_buf_is_valid(buf) then
vim.diagnostic.reset(Config.ns, buf)
if buf and vim.api.nvim_buf_is_valid(buf) then
vim.api.nvim_buf_delete(buf, { force = true })
end
vim.cmd.redraw()
end)
end
function M:win_valid()
return self.win and vim.api.nvim_win_is_valid(self.win)
end
function M:buf_valid()
return self.buf and vim.api.nvim_buf_is_valid(self.buf)
end
function M:hide()
if self:win_valid() then
self:close({ wipe = false })
end
end
function M:toggle()
if self:win_valid() then
self:hide()
return false
else
self:show()
return true
end
end
function M:show()
if self:win_valid() then
self:focus()
elseif self:buf_valid() then
self:mount()
else
error("LazyFloat: buffer closed")
end
end
function M:focus()
vim.api.nvim_set_current_win(self.win)

View file

@ -1,10 +1,10 @@
local Util = require("lazy.util")
local Render = require("lazy.view.render")
local Config = require("lazy.core.config")
local ViewConfig = require("lazy.view.config")
local Git = require("lazy.manage.git")
local Diff = require("lazy.view.diff")
local Float = require("lazy.view.float")
local Git = require("lazy.manage.git")
local Render = require("lazy.view.render")
local Util = require("lazy.util")
local ViewConfig = require("lazy.view.config")
---@class LazyViewState
---@field mode string
@ -25,17 +25,13 @@ local M = {}
---@type LazyView
M.view = nil
function M.visible()
return M.view and M.view.win and vim.api.nvim_win_is_valid(M.view.win)
end
---@param mode? string
function M.show(mode)
if Config.headless() then
if Config.headless then
return
end
M.view = M.visible() and M.view or M.create()
M.view = (M.view and M.view.win) and M.view or M.create()
if mode then
M.view.state.mode = mode
end
@ -50,44 +46,21 @@ end
function M.create()
local self = setmetatable({}, { __index = setmetatable(M, { __index = Float }) })
---@cast self LazyView
Float.init(self, {
title = Config.options.ui.title,
title_pos = Config.options.ui.title_pos,
noautocmd = false,
})
Float.init(self)
if Config.options.ui.wrap then
Util.wo(self.win, "wrap", true)
Util.wo(self.win, "linebreak", true)
Util.wo(self.win, "breakindent", true)
else
Util.wo(self.win, "wrap", false)
end
require("lazy.view.colors").setup()
self.state = vim.deepcopy(default_state)
self.render = Render.new(self)
local update = self.update
self.update = Util.throttle(Config.options.ui.throttle, function()
update(self)
end)
self.update = Util.throttle(Config.options.ui.throttle, self.update)
for _, pattern in ipairs({ "LazyRender", "LazyFloatResized" }) do
self:on({ "User" }, function()
self:on({ "User LazyRender", "User LazyFloatResized" }, function()
if not (self.buf and vim.api.nvim_buf_is_valid(self.buf)) then
return true
end
self:update()
end, { pattern = pattern })
end
vim.keymap.set("n", ViewConfig.keys.abort, function()
require("lazy.manage.process").abort()
require("lazy.async").abort()
return ViewConfig.keys.abort
end, { silent = true, buffer = self.buf, expr = true, desc = "Abort" })
vim.keymap.set("n", "gx", "K", { buffer = self.buf, remap = true })
end)
-- plugin details
self:on_key(ViewConfig.keys.details, function()
@ -97,49 +70,17 @@ function M.create()
name = plugin.name,
kind = plugin._.kind,
}
local open = not vim.deep_equal(self.state.plugin, selected)
if not open then
local row = self.render:get_row(selected)
if row then
vim.api.nvim_win_set_cursor(self.view.win, { row, 8 })
end
end
self.state.plugin = open and selected or nil
self.state.plugin = not vim.deep_equal(self.state.plugin, selected) and selected or nil
self:update()
end
end, "Details")
self:on_key(ViewConfig.keys.next, function()
local cursor = vim.api.nvim_win_get_cursor(self.view.win)
for l = 1, #self.render.locations, 1 do
local loc = self.render.locations[l]
if loc.from > cursor[1] then
vim.api.nvim_win_set_cursor(self.view.win, { loc.from, 8 })
return
end
end
end, "Next Plugin")
self:on_key(ViewConfig.keys.prev, function()
local cursor = vim.api.nvim_win_get_cursor(self.view.win)
for l = #self.render.locations, 1, -1 do
local loc = self.render.locations[l]
if loc.from < cursor[1] then
vim.api.nvim_win_set_cursor(self.view.win, { loc.from, 8 })
return
end
end
end, "Prev Plugin")
end)
self:on_key(ViewConfig.keys.profile_sort, function()
if self.state.mode == "profile" then
self.state.profile.sort_time_taken = not self.state.profile.sort_time_taken
self:update()
end
end, "Sort Profile")
end)
self:on_key(ViewConfig.keys.profile_filter, function()
if self.state.mode == "profile" then
@ -159,18 +100,16 @@ function M.create()
end
end)
end
end, "Filter Profile")
end)
for lhs, rhs in pairs(Config.options.ui.custom_keys) do
if rhs then
local handler = type(rhs) == "table" and rhs[1] or rhs
local desc = type(rhs) == "table" and rhs.desc or nil
self:on_key(lhs, function()
for key, handler in pairs(Config.options.ui.custom_keys) do
if handler then
self:on_key(key, function()
local plugin = self.render:get_plugin()
if plugin then
handler(plugin)
end
end, desc)
end)
end
end
@ -181,7 +120,9 @@ end
function M:update()
if self.buf and vim.api.nvim_buf_is_valid(self.buf) then
vim.bo[self.buf].modifiable = true
self.render:update()
vim.bo[self.buf].modifiable = false
vim.cmd.redraw()
end
end
@ -199,7 +140,7 @@ function M:open_url(path)
end
function M:setup_patterns()
local commit_pattern = "%f[%w](" .. string.rep("[a-f0-9]", 7) .. ")%f[%W]"
local commit_pattern = "%f[%w](" .. string.rep("%w", 7) .. ")%f[%W]"
self:on_pattern(ViewConfig.keys.hover, {
[commit_pattern] = function(hash)
self:diff({ commit = hash, browser = true })
@ -213,48 +154,26 @@ function M:setup_patterns()
Util.open(plugin.dir .. "/README.md")
end
end,
["|(%S-)|"] = function(h)
vim.cmd.help(h)
self:close()
end,
["|(%S-)|"] = vim.cmd.help, -- vim help links
["(https?://%S+)"] = function(url)
Util.open(url)
end,
}, self.hover, "Hover")
}, self.hover)
self:on_pattern(ViewConfig.keys.diff, {
[commit_pattern] = function(hash)
self:diff({ commit = hash })
end,
}, self.diff, "Diff")
self:on_pattern(ViewConfig.commands.restore.key_plugin, {
[commit_pattern] = function(hash)
self:restore({ commit = hash })
end,
}, self.restore, "Restore")
end
---@param opts? {commit:string}
function M:restore(opts)
opts = opts or {}
local Lockfile = require("lazy.manage.lock")
local Commands = require("lazy.view.commands")
local plugin = self.render:get_plugin()
if plugin then
if opts.commit then
Lockfile.get(plugin).commit = opts.commit
end
Commands.cmd("restore", { plugins = { plugin } })
end
}, self.diff)
end
function M:hover()
if self:diff({ browser = true, hover = true }) then
if self:diff({ browser = true }) then
return
end
self:open_url("")
end
---@param opts? {commit?:string, browser:boolean, hover:boolean}
---@param opts? {commit?:string, browser:boolean}
function M:diff(opts)
opts = opts or {}
local plugin = self.render:get_plugin()
@ -268,9 +187,6 @@ function M:diff(opts)
local info = assert(Git.info(plugin.dir))
local target = assert(Git.get_target(plugin))
diff = { from = info.commit, to = target.commit }
if opts.hover and diff.from == diff.to then
return
end
end
if not diff then
@ -286,8 +202,6 @@ function M:diff(opts)
else
Diff.handlers[Config.options.diff.cmd](plugin, diff)
end
return true
end
end
@ -295,8 +209,7 @@ end
---@param key string
---@param patterns table<string, fun(str:string)>
---@param fallback? fun(self)
---@param desc? string
function M:on_pattern(key, patterns, fallback, desc)
function M:on_pattern(key, patterns, fallback)
self:on_key(key, function()
local line = vim.api.nvim_get_current_line()
local pos = vim.api.nvim_win_get_cursor(0)
@ -318,7 +231,7 @@ function M:on_pattern(key, patterns, fallback, desc)
if fallback then
fallback(self)
end
end, desc)
end)
end
function M:setup_modes()
@ -333,30 +246,13 @@ function M:setup_modes()
Commands.cmd(name)
end, m.desc)
end
if m.key_plugin and name ~= "restore" then
if m.key_plugin then
self:on_key(m.key_plugin, function()
local esc = vim.api.nvim_replace_termcodes("<esc>", true, true, true)
vim.api.nvim_feedkeys(esc, "n", false)
local plugins = {}
if vim.api.nvim_get_mode().mode:lower() == "v" then
local f, t = vim.fn.line("."), vim.fn.line("v")
if f > t then
f, t = t, f
end
for i = f, t do
local plugin = self.render:get_plugin(i)
local plugin = self.render:get_plugin()
if plugin then
plugins[plugin.name] = plugin
Commands.cmd(name, { plugins = { plugin } })
end
end
plugins = vim.tbl_values(plugins)
else
plugins[1] = self.render:get_plugin()
end
if #plugins > 0 then
Commands.cmd(name, { plugins = plugins })
end
end, m.desc_plugin, { "n", "x" })
end, m.desc_plugin)
end
end
end

View file

@ -1,11 +1,11 @@
local Config = require("lazy.core.config")
local Git = require("lazy.manage.git")
local Handler = require("lazy.core.handler")
local Keys = require("lazy.core.handler.keys")
local Plugin = require("lazy.core.plugin")
local Sections = require("lazy.view.sections")
local Util = require("lazy.util")
local Sections = require("lazy.view.sections")
local Handler = require("lazy.core.handler")
local Git = require("lazy.manage.git")
local Plugin = require("lazy.core.plugin")
local ViewConfig = require("lazy.view.config")
local Cache = require("lazy.core.cache")
local Text = require("lazy.view.text")
@ -26,7 +26,7 @@ function M.new(view)
local self = setmetatable({}, { __index = setmetatable(M, { __index = Text }) })
self.view = view
self.padding = 2
self.wrap = view.win_opts.width
self.wrap = view.opts.win_opts.width
return self
end
@ -37,7 +37,6 @@ function M:update()
self.plugins = vim.tbl_values(Config.plugins)
vim.list_extend(self.plugins, vim.tbl_values(Config.to_clean))
vim.list_extend(self.plugins, vim.tbl_values(Config.spec.disabled))
table.sort(self.plugins, function(a, b)
return a.name < b.name
end)
@ -51,7 +50,7 @@ function M:update()
if plugin._.tasks then
for _, task in ipairs(plugin._.tasks) do
self.progress.total = self.progress.total + 1
if not task:running() then
if not task:is_running() then
self.progress.done = self.progress.done + 1
end
end
@ -74,17 +73,7 @@ function M:update()
end
self:trim()
vim.bo[self.view.buf].modifiable = true
local view = vim.api.nvim_win_call(self.view.win, vim.fn.winsaveview)
self:render(self.view.buf)
vim.api.nvim_win_call(self.view.win, function()
vim.fn.winrestview(view)
end)
vim.bo[self.view.buf].modifiable = false
vim.diagnostic.set(
Config.ns,
self.view.buf,
@ -94,16 +83,13 @@ function M:update()
diag.lnum = diag.row - 1
return diag
end, self._diagnostics),
{ signs = false, virtual_text = true, underline = false, virtual_lines = false }
{ signs = false }
)
end
---@param row? number
---@return LazyPlugin?
function M:get_plugin(row)
if not (self.view.win and vim.api.nvim_win_is_valid(self.view.win)) then
return
end
row = row or vim.api.nvim_win_get_cursor(self.view.win)[1]
for _, loc in ipairs(self.locations) do
if row >= loc.from and row <= loc.to then
@ -113,8 +99,6 @@ function M:get_plugin(row)
return plugin
end
end
elseif loc.kind == "disabled" then
return Config.spec.disabled[loc.name]
else
return Config.plugins[loc.name]
end
@ -122,49 +106,34 @@ function M:get_plugin(row)
end
end
---@param selected {name:string, kind?: LazyPluginKind}
function M:get_row(selected)
for _, loc in ipairs(self.locations) do
if loc.kind == selected.kind and loc.name == selected.name then
return loc.from
end
end
end
function M:title()
self:nl()
local modes = vim.tbl_filter(function(c)
return c.button
end, ViewConfig.get_commands())
if Config.options.ui.pills then
self:nl()
for c, mode in ipairs(modes) do
self:nl():nl()
for _, mode in ipairs(ViewConfig.get_commands()) do
if mode.button then
local title = " " .. mode.name:sub(1, 1):upper() .. mode.name:sub(2) .. " (" .. mode.key .. ") "
if mode.name == "home" then
if self.view.state.mode == "home" then
title = " lazy.nvim " .. Config.options.ui.icons.lazy
else
title = " lazy.nvim (H) "
end
end
if self.view.state.mode == mode.name then
if mode.name == "home" then
self:append(title, "LazyH1", { wrap = true })
self:append(title, "LazyH1")
else
self:append(title, "LazyButtonActive", { wrap = true })
self:append(title, "LazyButtonActive")
self:highlight({ ["%(.%)"] = "LazySpecial" })
end
else
self:append(title, "LazyButton", { wrap = true })
self:append(title, "LazyButton")
self:highlight({ ["%(.%)"] = "LazySpecial" })
end
if c == #modes then
break
end
self:append(" ")
end
self:nl()
end
self:nl()
if self.progress.done < self.progress.total then
self:progressbar()
end
@ -185,9 +154,7 @@ end
function M:help()
self:append("Help", "LazyH2"):nl():nl()
self:append("Use "):append(ViewConfig.keys.abort, "LazySpecial"):append(" to abort all running tasks."):nl():nl()
self:append("You can press "):append("<CR>", "LazySpecial"):append(" on a plugin to show its details."):nl():nl()
self:append("You can press "):append("<CR>", "LazySpecial"):append(" on a plugin to show its details."):nl()
self:append("Most properties can be hovered with ")
self:append("<K>", "LazySpecial")
@ -197,17 +164,8 @@ function M:help()
:append("<K>", "LazySpecial")
:append(" on a plugin anywhere else, a diff will be opened if there are updates")
:nl()
self:append("or the plugin was just updated. Otherwise the plugin webpage will open."):nl():nl()
self:append("Use "):append("<d>", "LazySpecial"):append(" on a commit or plugin to open the diff view"):nl():nl()
self
:append("Use ")
:append("<]]>", "LazySpecial")
:append(" and ")
:append("<[[>", "LazySpecial")
:append(" to navigate between plugins")
:nl()
:nl()
self:append("or the plugin was just updated. Otherwise the plugin webpage will open."):nl()
self:append("Use "):append("<d>", "LazySpecial"):append(" on a commit or plugin to open the diff view"):nl()
self:nl()
self:append("Keyboard Shortcuts", "LazyH2"):nl()
@ -235,14 +193,6 @@ function M:help()
self:append(" " .. (mode.desc_plugin or mode.desc)):nl()
end
end
for lhs, rhs in pairs(Config.options.ui.custom_keys) do
if type(rhs) == "table" and rhs.desc then
self:append("- ", "LazySpecial", { indent = 2 })
self:append("Custom key ", "Title")
self:append(lhs, "LazyProp")
self:append(" " .. rhs.desc):nl()
end
end
end
function M:progressbar()
@ -275,9 +225,6 @@ function M:section(section)
end, self.plugins)
local count = #section_plugins
table.sort(section_plugins, function(a, b)
return a.name:lower() < b.name:lower()
end)
if count > 0 then
self:append(section.title, "LazyH2"):append(" (" .. count .. ")", "LazyComment"):nl()
for _, plugin in ipairs(section_plugins) do
@ -353,17 +300,23 @@ function M:reason(reason, opts)
end
for _, key in ipairs(keys) do
local value = reason[key]
local skip = type(key) == "number" or key == "time"
if not skip then
if type(key) == "number" then
elseif key == "require" then
elseif key ~= "time" then
if first then
first = false
else
self:append(" ")
end
if key == "event" then
value = value:match("User (.*)") or value
end
if key == "keys" then
value = type(value) == "string" and value or value[1]
end
local hl = "LazyReason" .. key:sub(1, 1):upper() .. key:sub(2)
local icon = Config.options.ui.icons[key]
if icon then
icon = icon:gsub("%s*$", "")
self:append(icon .. " ", hl)
self:append(value, hl)
else
@ -379,91 +332,48 @@ end
---@param plugin LazyPlugin
function M:diagnostics(plugin)
local skip = false
for _, task in ipairs(plugin._.tasks or {}) do
if task:running() then
self:diagnostic({
severity = vim.diagnostic.severity.WARN,
message = task.name .. (task:status() and (": " .. task:status()) or ""),
})
skip = true
elseif task:has_errors() then
self:diagnostic({
message = task.name .. " failed",
severity = vim.diagnostic.severity.ERROR,
})
skip = true
elseif task:has_warnings() then
self:diagnostic({
message = task.name .. " warning",
severity = vim.diagnostic.severity.WARN,
})
skip = true
end
end
if skip then
return
end
if plugin._.build then
self:diagnostic({
message = "needs build",
severity = vim.diagnostic.severity.WARN,
})
elseif plugin._.updated then
if plugin._.updated then
if plugin._.updated.from == plugin._.updated.to then
self:diagnostic({
message = "already up to date",
})
else
local version = Git.info(plugin.dir, true).version
if version then
self:diagnostic({
message = "updated to " .. tostring(version),
})
else
self:diagnostic({
message = "updated from " .. plugin._.updated.from:sub(1, 7) .. " to " .. plugin._.updated.to:sub(1, 7),
})
end
end
elseif plugin._.updates then
local version = plugin._.updates.to.version
if version then
self:diagnostic({
message = "version " .. tostring(version) .. " is available",
})
else
elseif plugin._.has_updates then
self:diagnostic({
message = "updates available",
})
end
for _, task in ipairs(plugin._.tasks or {}) do
if task:is_running() then
self:diagnostic({
severity = vim.diagnostic.severity.WARN,
message = task.name .. (task.status == "" and "" or (": " .. task.status)),
})
elseif task.error then
self:diagnostic({
message = task.name .. " failed",
severity = vim.diagnostic.severity.ERROR,
})
end
end
end
---@param plugin LazyPlugin
function M:plugin(plugin)
local hl = plugin._.is_local and "LazyLocal" or "LazySpecial"
if plugin._.loaded then
self:append(" " .. Config.options.ui.icons.loaded .. " ", hl):append(plugin.name)
self:append(" " .. Config.options.ui.icons.loaded .. " ", "LazySpecial"):append(plugin.name)
elseif plugin._.cond == false then
self:append(" " .. Config.options.ui.icons.not_loaded .. " ", "LazyNoCond"):append(plugin.name)
else
self:append(" " .. Config.options.ui.icons.not_loaded .. " ", hl):append(plugin.name)
self:append(" " .. Config.options.ui.icons.not_loaded .. " ", "LazySpecial"):append(plugin.name)
end
local plugin_start = self:row()
if plugin._.loaded then
-- When the plugin is loaded, only show the loading reason
self:reason(plugin._.loaded)
else
-- otherwise show all lazy handlers
self:append(" ")
self:handlers(plugin)
for _, other in pairs(Config.plugins) do
if vim.tbl_contains(other.dependencies or {}, plugin.name) then
self:reason({ plugin = other.name })
self:append(" ")
end
end
end
self:diagnostics(plugin)
self:nl()
@ -476,22 +386,6 @@ function M:plugin(plugin)
{ name = plugin.name, from = plugin_start, to = self:row() - 1, kind = plugin._.kind }
end
---@param str string
---@param hl? string|Extmark
---@param opts? {indent?: number, prefix?: string, wrap?: boolean}
function M:markdown(str, hl, opts)
local lines = vim.split(str, "\n")
for _, line in ipairs(lines) do
self:append(line, hl, opts):highlight({
["`.-`"] = "@markup.raw.markdown_inline",
["%*.-%*"] = "LazyItalic",
["%*%*.-%*%*"] = "LazyBold",
["^%s*-"] = "Special",
})
self:nl()
end
end
---@param plugin LazyPlugin
function M:tasks(plugin)
for _, task in ipairs(plugin._.tasks or {}) do
@ -500,56 +394,40 @@ function M:tasks(plugin)
self:append(" " .. math.floor((task:time()) * 100) / 100 .. "ms", "Bold")
self:nl()
end
if not task:has_warnings() and task.name == "log" then
if task.error then
self:append(vim.trim(task.error), "LazyTaskError", { indent = 6 })
self:nl()
elseif task.name == "log" then
self:log(task)
else
local hls = {
[vim.log.levels.ERROR] = "LazyError",
[vim.log.levels.WARN] = "LazyWarning",
[vim.log.levels.INFO] = "LazyInfo",
}
for _, msg in ipairs(task:get_log()) do
if task:has_warnings() or self.view:is_selected(plugin) then
self:markdown(msg.msg, hls[msg.level] or "LazyTaskOutput", { indent = 6 })
end
end
elseif self.view:is_selected(plugin) and task.output ~= "" and task.output ~= task.error then
self:append(vim.trim(task.output), "LazyTaskOutput", { indent = 6 })
self:nl()
end
end
end
---@param task LazyTask
function M:log(task)
local log = vim.trim(task:output())
local log = vim.trim(task.output)
if log ~= "" then
local lines = vim.split(log, "\n")
for _, line in ipairs(lines) do
local ref, msg, time = line:match("^(%w+) (.*) (%(.*%))$")
if msg then
if msg:find("^%S+!:") then
self:diagnostic({ message = "Breaking Changes", severity = vim.diagnostic.severity.WARN })
end
self:append(ref:sub(1, 7) .. " ", "LazyCommit", { indent = 6 })
local dimmed = false
for _, dim in ipairs(ViewConfig.dimmed_commits) do
if msg:find("^" .. dim) then
dimmed = true
end
end
self:append(vim.trim(msg), dimmed and "LazyDimmed" or nil):highlight({
self:append(vim.trim(msg)):highlight({
["#%d+"] = "LazyCommitIssue",
["^%S+:"] = dimmed and "Bold" or "LazyCommitType",
["^%S+(%(.*%))!?:"] = "LazyCommitScope",
["`.-`"] = "@markup.raw.markdown_inline",
["^%S+:"] = "LazyCommitType",
["^%S+(%(.*%)):"] = "LazyCommitScope",
["`.-`"] = "@text.literal.markdown_inline",
["%*.-%*"] = "Italic",
["%*%*.-%*%*"] = "Bold",
})
-- string.gsub
self:append(" " .. time, "LazyComment")
self:nl()
-- else
-- self:append(line, "LazyTaskOutput", { indent = 6 }):nl()
end
end
self:nl()
end
@ -575,15 +453,8 @@ function M:details(plugin)
if git.branch then
table.insert(props, { "branch", git.branch })
end
if git.commit then
table.insert(props, { "commit", git.commit:sub(1, 7), "LazyCommit" })
end
end
local rocks = require("lazy.pkg.rockspec").deps(plugin)
if rocks then
table.insert(props, { "rocks", vim.inspect(rocks) })
end
if Util.file_exists(plugin.dir .. "/README.md") then
table.insert(props, { "readme", "README.md" })
end
@ -597,36 +468,24 @@ function M:details(plugin)
end
end)
for handler in pairs(plugin._.handlers or {}) do
for handler in pairs(Handler.types) do
if plugin[handler] then
table.insert(props, {
handler,
function()
self:handlers(plugin, handler)
for _, value in ipairs(plugin[handler]) do
self:reason({ [handler] = value })
self:append(" ")
end
end,
})
end
end
self:props(props, { indent = 6 })
self:nl()
end
---@param plugin LazyPlugin
---@param types? LazyHandlerTypes[]|LazyHandlerTypes
function M:handlers(plugin, types)
if not plugin._.handlers then
return
end
types = type(types) == "string" and { types } or types
types = types and types or vim.tbl_keys(Handler.types)
for _, t in ipairs(types) do
for id, value in pairs(plugin._.handlers[t] or {}) do
value = t == "keys" and Keys.to_string(value) or id
self:reason({ [t] = value })
self:append(" ")
end
end
end
---@alias LazyProps {[1]:string, [2]:string|fun(), [3]?:string}[]
---@param props LazyProps
---@param opts? {indent: number}
@ -651,11 +510,11 @@ function M:profile()
local stats = require("lazy.stats").stats()
local ms = (math.floor(stats.startuptime * 100 + 0.5) / 100)
self:append("Startuptime: ", "LazyH2"):append(ms .. "ms", "Number"):nl():nl()
if stats.real_cputime then
if stats.startuptime_cputime then
self:append("Based on the actual CPU time of the Neovim process till "):append("UIEnter", "LazySpecial")
self:append("."):nl()
self:append("This is more accurate than ")
self:append("`nvim --startuptime`", "@markup.raw.markdown_inline")
self:append("`nvim --startuptime`", "@text.literal.markdown_inline")
self:append(".")
else
self:append("An accurate startuptime based on the actual CPU time of the Neovim process is not available."):nl()
@ -664,33 +523,17 @@ function M:profile()
:append("UIEnter", "LazySpecial")
self:append(".")
end
self:nl()
local times = {}
for event, time in pairs(require("lazy.stats").stats().times) do
times[#times + 1] = { event, self:ms(time * 1e6), "Bold", time = time }
end
table.sort(times, function(a, b)
return a.time < b.time
end)
for p, prop in ipairs(times) do
if p > 1 then
prop[2] = prop[2] .. " (+" .. self:ms((prop.time - times[p - 1].time) * 1e6) .. ")"
end
end
self:props(times, { indent = 2 })
self:nl()
self:nl():nl()
self:append("Profile", "LazyH2"):nl():nl()
self
:append("You can press ")
:append(ViewConfig.keys.profile_sort, "LazySpecial")
:append("<C-s>", "LazySpecial")
:append(" to change sorting between chronological order & time taken.")
:nl()
self
:append("Press ")
:append(ViewConfig.keys.profile_filter, "LazySpecial")
:append("<C-f>", "LazySpecial")
:append(" to filter profiling entries that took more time than a given threshold")
:nl()
@ -755,19 +598,12 @@ function M:debug()
Util.foreach(require("lazy.core.handler").handlers, function(handler_type, handler)
Util.foreach(handler.active, function(value, plugins)
value = type(value) == "table" and value[1] or value
if not vim.tbl_isempty(plugins) then
---@type string[]
plugins = vim.tbl_values(plugins)
table.sort(plugins)
self:append(Config.options.ui.icons.debug, "LazySpecial", { indent = 2 })
if handler_type == "keys" then
for k, v in pairs(Config.plugins[plugins[1]]._.handlers.keys) do
if k == value then
value = Keys.to_string(v)
break
end
end
end
self:append("", "LazySpecial", { indent = 2 })
self:reason({ [handler_type] = value })
for _, plugin in pairs(plugins) do
self:append(" ")
@ -779,19 +615,33 @@ function M:debug()
end)
self:nl()
Util.foreach(require("lazy.core.cache")._inspect(), function(name, stats)
self:append(name, "LazyH2"):nl()
local props = {
{ "total", stats.total or 0, "Number" },
{ "time", self:ms(stats.time or 0, 3), "Bold" },
{ "avg time", self:ms((stats.time or 0) / (stats.total or 0), 3), "Bold" },
}
for k, v in pairs(stats) do
if k ~= "total" and k ~= "time" then
props[#props + 1] = { k, v, "Number" }
self:append("Cache.find()", "LazyH2"):nl()
self:props({
{ "total", Cache.stats.find.total, "Number" },
{ "time", self:ms(Cache.stats.find.time, 3), "Bold" },
{ "avg time", self:ms(Cache.stats.find.time / Cache.stats.find.total, 3), "Bold" },
{ "index", Cache.stats.find.index, "Number" },
{ "fs_stat", Cache.stats.find.stat, "Number" },
{ "not found", Cache.stats.find.not_found, "Number" },
}, { indent = 2 })
self:nl()
self:append("Cache.autoload()", "LazyH2"):nl()
self:props({
{ "total", Cache.stats.autoload.total, "Number" },
{ "time", self:ms(Cache.stats.autoload.time, 3), "Bold" },
{ "avg time", self:ms(Cache.stats.autoload.time / Cache.stats.autoload.total, 3), "Bold" },
}, { indent = 2 })
self:nl()
self:append("Cache", "LazyH2"):nl()
local Cache = require("lazy.core.cache")
Util.foreach(Cache.cache, function(modname, entry)
local kb = math.floor(#entry.chunk / 10.24) / 100
self:append("", "LazySpecial", { indent = 2 }):append(modname):append(" " .. kb .. "Kb", "Bold")
if entry.modpath ~= modname then
self:append(" " .. vim.fn.fnamemodify(entry.modpath, ":p:~:."), "LazyComment")
end
end
self:props(props, { indent = 2 })
self:nl()
end)
end

View file

@ -17,35 +17,27 @@ return {
{
filter = function(plugin)
return has_task(plugin, function(task)
return task:has_errors()
return task.error ~= nil
end)
end,
title = "Failed",
},
{
filter = function(plugin)
if plugin._.working then
return true
end
return has_task(plugin, function(task)
return task:running()
return task:is_running()
end)
end,
title = "Working",
},
{
filter = function(plugin)
return plugin._.build
end,
title = "Build",
},
{
filter = function(plugin)
return has_task(plugin, function(task)
if task.name ~= "log" then
return
end
for _, line in ipairs(vim.split(task:output(), "\n")) do
local lines = vim.split(task.output, "\n")
for _, line in ipairs(lines) do
if line:find("^%w+ %S+!:") then
return true
end
@ -55,12 +47,14 @@ return {
title = "Breaking Changes",
},
{
---@param plugin LazyPlugin
filter = function(plugin)
return plugin._.updated and plugin._.updated.from ~= plugin._.updated.to
end,
title = "Updated",
},
{
---@param plugin LazyPlugin
filter = function(plugin)
return plugin._.cloned
end,
@ -69,39 +63,33 @@ return {
{
---@param plugin LazyPlugin
filter = function(plugin)
return plugin._.updates ~= nil
return plugin._.has_updates
end,
title = "Updates",
},
{
filter = function(plugin)
return has_task(plugin, function(task)
return task.name == "log" and vim.trim(task:output()) ~= ""
return task.name == "log" and vim.trim(task.output) ~= ""
end)
end,
title = "Log",
},
{
filter = function(plugin)
return plugin._.kind == "clean" and plugin._.installed
return plugin._.installed and not plugin.url
end,
title = "Clean",
},
{
filter = function(plugin)
return not plugin._.installed and plugin._.kind ~= "disabled"
return not plugin._.installed
end,
title = "Not Installed",
},
{
filter = function(plugin)
return plugin._.outdated
end,
title = "Outdated",
},
{
filter = function(plugin)
return plugin._.loaded ~= nil
return plugin._.loaded
end,
title = "Loaded",
},
@ -111,10 +99,4 @@ return {
end,
title = "Not Loaded",
},
{
filter = function(plugin)
return plugin._.kind == "disabled"
end,
title = "Disabled",
},
}

View file

@ -11,7 +11,9 @@ local Util = require("lazy.util")
local Text = {}
function Text.new()
local self = setmetatable({}, { __index = Text })
local self = setmetatable({}, {
__index = Text,
})
self._lines = {}
return self
@ -19,7 +21,7 @@ end
---@param str string
---@param hl? string|Extmark
---@param opts? {indent?: number, prefix?: string, wrap?: boolean}
---@param opts? {indent?: number, prefix?: string}
function Text:append(str, hl, opts)
opts = opts or {}
if #self._lines == 0 then
@ -37,13 +39,7 @@ function Text:append(str, hl, opts)
if l > 1 then
self:nl()
end
if
Config.options.ui.wrap
and opts.wrap
and str ~= ""
and self:col() > 0
and self:col() + vim.fn.strwidth(line) + self.padding > self.wrap
then
if str ~= "" and self:col() > 0 and self:col() + vim.fn.strwidth(line) + self.padding > self.wrap then
self:nl()
end
table.insert(self._lines[#self._lines], {
@ -65,18 +61,11 @@ function Text:render(buf)
for _, line in ipairs(self._lines) do
local str = (" "):rep(self.padding)
local has_extmark = false
for _, segment in ipairs(line) do
str = str .. segment.str
if type(segment.hl) == "table" then
has_extmark = true
end
end
if str:match("^%s*$") and not has_extmark then
str = ""
end
table.insert(lines, str)
end
@ -84,7 +73,6 @@ function Text:render(buf)
vim.api.nvim_buf_clear_namespace(buf, Config.ns, 0, -1)
for l, line in ipairs(self._lines) do
if lines[l] ~= "" then
local col = self.padding
for _, segment in ipairs(line) do
@ -99,11 +87,11 @@ function Text:render(buf)
local extmark_col = extmark.col or col
extmark.col = nil
local ok, err = pcall(vim.api.nvim_buf_set_extmark, buf, Config.ns, l - 1, extmark_col, extmark)
local ok = pcall(vim.api.nvim_buf_set_extmark, buf, Config.ns, l - 1, extmark_col, extmark)
if not ok then
Util.error(
"Failed to set extmark. Please report a bug with this info:\n"
.. vim.inspect({ segment = segment, line = line, error = err })
.. vim.inspect({ segment = segment, line = line })
)
end
end
@ -112,7 +100,6 @@ function Text:render(buf)
end
end
end
end
---@param patterns table<string,string>
function Text:highlight(patterns)

116407
manifest

File diff suppressed because it is too large Load diff

View file

@ -1,3 +0,0 @@
#!/bin/env bash
nvim -l tests/minit.lua --minitest

View file

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

View file

@ -1,6 +1,3 @@
indent_type = "Spaces"
indent_width = 2
column_width = 120
[sort_requires]
enabled = true

35
tests/core/e2e_spec.lua Normal file
View file

@ -0,0 +1,35 @@
describe("lazy", function()
before_each(function()
vim.g.lazy_did_setup = false
vim.go.loadplugins = true
for modname in pairs(package.loaded) do
if modname:find("lazy") == 1 then
package.loaded[modname] = nil
end
end
end)
local root = ".tests/data/nvim/lazy"
it("installs plugins", function()
local Lazy = require("lazy")
local Config = require("lazy.core.config")
local neodev = false
Lazy.setup({
{
"folke/neodev.nvim",
config = function()
neodev = true
end,
},
"folke/paint.nvim",
}, { install_missing = true, defaults = { lazy = true } })
assert(3 == vim.tbl_count(Config.plugins))
assert(vim.loop.fs_stat(root .. "/paint.nvim/README.md"))
assert(vim.loop.fs_stat(root .. "/neodev.nvim/README.md"))
assert(not neodev)
assert(Config.plugins["neodev.nvim"]._.installed)
assert(not Config.plugins["neodev.nvim"]._.is_local)
end)
end)

View file

@ -1,10 +1,7 @@
local Util = require("lazy.core.util")
describe("init", function()
it("has correct environment for tests", function()
for _, name in ipairs({ "config", "data", "cache", "state" }) do
local path = Util.norm(vim.fn.stdpath(name) --[[@as string]])
assert(path:find(".tests/" .. name, 1, true), path .. " not in .tests")
for _, path in ipairs({ "config", "data", "cache", "state" }) do
assert(vim.fn.stdpath(path):find(".tests/" .. path))
end
end)
end)

View file

@ -1,52 +1,18 @@
local Config = require("lazy.core.config")
local Handler = require("lazy.core.handler")
local Plugin = require("lazy.core.plugin")
local function inspect(obj)
return vim.inspect(obj):gsub("%s+", " ")
end
local assert = require("luassert")
---@param plugin LazyPlugin
local function resolve(plugin)
local meta = getmetatable(plugin)
local ret = meta and type(meta.__index) == "table" and resolve(meta.__index) or {}
for k, v in pairs(plugin) do
ret[k] = v
end
return ret
end
---@param plugins LazyPlugin[]
local function clean(plugins)
return vim.tbl_map(function(plugin)
plugin = resolve(plugin)
plugin[1] = nil
plugin._.frags = nil
if plugin._.dep == false then
plugin._.dep = nil
end
plugin._.top = nil
return plugin
end, plugins)
end
Config.setup()
describe("plugin spec url/name", function()
local tests = {
{ { dir = "~/foo" }, { name = "foo", dir = vim.fn.fnamemodify("~/foo", ":p") } },
{ { dir = "/tmp/foo" }, { dir = "/tmp/foo", name = "foo" } },
{ { "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 = "bar", url = "123" } },
{ { "foo/bar", url = "123" }, { [1] = "foo/bar", name = "123", 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" } },
@ -54,98 +20,19 @@ describe("plugin spec url/name", function()
for _, test in ipairs(tests) do
test[2]._ = {}
it("parses " .. inspect(test[1]), function()
it("parses " .. vim.inspect(test[1]):gsub("%s+", " "), 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 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
local plugins = vim.tbl_values(spec.plugins)
assert(#spec.errors == 0)
assert.equal(1, #plugins)
assert.same(test[2], plugins[1])
end)
end
end)
describe("plugin spec dir", function()
local tests = {
{
"~/projects/gitsigns.nvim",
{ "lewis6991/gitsigns.nvim", opts = {}, dev = true },
{ "lewis6991/gitsigns.nvim" },
},
{
"~/projects/gitsigns.nvim",
{ "lewis6991/gitsigns.nvim", opts = {}, dev = true },
{ "gitsigns.nvim" },
},
{
"~/projects/gitsigns.nvim",
{ "lewis6991/gitsigns.nvim", opts = {} },
{ "lewis6991/gitsigns.nvim", dev = true },
},
{
"~/projects/gitsigns.nvim",
{ "lewis6991/gitsigns.nvim", opts = {} },
{ "gitsigns.nvim", dev = true },
},
}
for _, test in ipairs(tests) do
local dir = vim.fn.expand(test[1])
local input = vim.list_slice(test, 2)
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)
assert.equal(1, #plugins)
assert.same(dir, plugins[1].dir)
end)
end
end)
describe("plugin spec opt", function()
it("handles dependencies", function()
Config.options.defaults.lazy = false
@ -156,9 +43,8 @@ describe("plugin spec opt", function()
}
for _, test in ipairs(tests) do
local spec = Plugin.Spec.new(vim.deepcopy(test))
assert(#spec.notifs == 0)
assert(#spec.errors == 0)
Config.plugins = spec.plugins
Config.spec = spec
Plugin.update_state()
assert(vim.tbl_count(spec.plugins) == 3)
assert(#spec.plugins.bar.dependencies == 2)
@ -172,14 +58,16 @@ describe("plugin spec opt", function()
for _, plugin in pairs(spec.plugins) do
plugin.dir = nil
end
assert.same({
assert.same(spec.plugins, {
bar = {
"foo/bar",
_ = {},
dependencies = { "dep1", "dep2" },
name = "bar",
url = "https://github.com/foo/bar.git",
},
dep1 = {
"foo/dep1",
_ = {
dep = true,
},
@ -187,108 +75,63 @@ describe("plugin spec opt", function()
url = "https://github.com/foo/dep1.git",
},
dep2 = {
"foo/dep2",
_ = {
dep = true,
},
name = "dep2",
url = "https://github.com/foo/dep2.git",
},
}, clean(spec.plugins))
})
end
end)
describe("deps", function()
before_each(function()
Handler.init()
end)
it("handles dep names", function()
Config.options.defaults.lazy = false
local tests = {
{ { "foo/bar", dependencies = { { "dep1" }, "foo/dep2" } }, "foo/dep1" },
{ "foo/dep1", { "foo/bar", dependencies = { { "dep1" }, "foo/dep2" } } },
}
for _, test in ipairs(tests) do
it("handles dep names " .. inspect(test), function()
local spec = Plugin.Spec.new(vim.deepcopy(test))
assert(#spec.notifs == 0)
assert(#spec.errors == 0)
Config.plugins = spec.plugins
Plugin.update_state()
spec = Plugin.Spec.new(test)
for _, plugin in pairs(spec.plugins) do
plugin.dir = nil
end
assert.same({
assert.same(spec.plugins, {
bar = {
"foo/bar",
_ = {},
dependencies = { "dep1", "dep2" },
name = "bar",
url = "https://github.com/foo/bar.git",
},
dep1 = {
"foo/dep1",
_ = {},
name = "dep1",
url = "https://github.com/foo/dep1.git",
},
dep2 = {
"foo/dep2",
_ = {
dep = true,
},
name = "dep2",
url = "https://github.com/foo/dep2.git",
},
}, clean(spec.plugins))
})
end
end)
end
Config.options.defaults.lazy = false
local tests = {
{
{ "foo/baz", name = "bar" },
{ "foo/fee", dependencies = { "foo/baz" } },
},
{
{ "foo/fee", dependencies = { "foo/baz" } },
{ "foo/baz", name = "bar" },
},
-- {
-- { "foo/baz", name = "bar" },
-- { "foo/fee", dependencies = { "baz" } },
-- },
{
{ "foo/baz", name = "bar" },
{ "foo/fee", dependencies = { "bar" } },
},
}
for _, test in ipairs(tests) do
it("handles dep names " .. inspect(test), function()
local spec = Plugin.Spec.new(vim.deepcopy(test))
assert(#spec.notifs == 0)
Config.plugins = spec.plugins
Plugin.update_state()
spec = Plugin.Spec.new(test)
spec.meta:rebuild()
for _, plugin in pairs(spec.plugins) do
plugin.dir = nil
end
assert.same({
bar = {
_ = {},
name = "bar",
url = "https://github.com/foo/baz.git",
},
fee = {
_ = {},
name = "fee",
url = "https://github.com/foo/fee.git",
dependencies = { "bar" },
},
}, clean(spec.plugins))
end)
end
it("handles opt from dep", function()
Config.options.defaults.lazy = false
local spec = Plugin.Spec.new({ "foo/dep1", { "foo/bar", dependencies = { "foo/dep1", "foo/dep2" } } })
assert(#spec.notifs == 0)
assert(#spec.errors == 0)
Config.plugins = spec.plugins
Plugin.update_state()
assert.same(3, vim.tbl_count(spec.plugins))
@ -304,7 +147,7 @@ describe("plugin spec opt", function()
do
Config.options.defaults.lazy = true
local spec = Plugin.Spec.new({ "foo/bar" })
assert(#spec.notifs == 0)
assert(#spec.errors == 0)
Config.plugins = spec.plugins
Plugin.update_state()
assert(spec.plugins.bar.lazy == true)
@ -321,7 +164,7 @@ describe("plugin spec opt", function()
it("handles opt from dep", function()
Config.options.defaults.lazy = false
local spec = Plugin.Spec.new({ "foo/bar", event = "foo" })
assert(#spec.notifs == 0)
assert(#spec.errors == 0)
Config.plugins = spec.plugins
Plugin.update_state()
assert.same(1, vim.tbl_count(spec.plugins))
@ -337,190 +180,118 @@ describe("plugin spec opt", function()
}
for _, test in ipairs(tests) do
local spec = Plugin.Spec.new(test)
assert(#spec.notifs == 0)
assert(#spec.errors == 0)
assert(vim.tbl_count(spec.plugins) == 1)
Handler.resolve(spec.plugins.bar)
-- vim.print(spec.plugins.bar._.handlers)
local events = vim.tbl_keys(spec.plugins.bar._.handlers.event or {})
assert(type(events) == "table")
assert(#events == 2)
assert(vim.tbl_contains(events, "mod1"))
assert(vim.tbl_contains(events, "mod2"))
end
end)
end)
it("handles opt from dep", function()
Config.options.defaults.lazy = false
local spec = Plugin.Spec.new({ "foo/dep1", { "foo/bar", dependencies = { "foo/dep1", "foo/dep2" } } })
assert(#spec.notifs == 0)
Config.plugins = spec.plugins
Plugin.update_state()
assert.same(3, vim.tbl_count(spec.plugins))
assert(spec.plugins.bar._.dep ~= true)
assert(spec.plugins.bar.lazy == false)
assert(spec.plugins.dep2._.dep == true)
assert(spec.plugins.dep2.lazy == true)
assert(spec.plugins.dep1._.dep ~= true)
assert(spec.plugins.dep1.lazy == false)
end)
it("handles defaults opt", function()
do
Config.options.defaults.lazy = true
local spec = Plugin.Spec.new({ "foo/bar" })
assert(#spec.notifs == 0)
Config.plugins = spec.plugins
Plugin.update_state()
assert(spec.plugins.bar.lazy == true)
end
do
Config.options.defaults.lazy = false
local spec = Plugin.Spec.new({ "foo/bar" })
Config.plugins = spec.plugins
Plugin.update_state()
assert(spec.plugins.bar.lazy == false)
assert(type(spec.plugins.bar.event) == "table")
assert(#spec.plugins.bar.event == 2)
assert(vim.tbl_contains(spec.plugins.bar.event, "mod1"))
assert(vim.tbl_contains(spec.plugins.bar.event, "mod2"))
end
end)
it("handles opt from dep", function()
Config.options.defaults.lazy = false
local spec = Plugin.Spec.new({ "foo/bar", event = "foo" })
assert(#spec.notifs == 0)
Config.plugins = spec.plugins
Plugin.update_state()
assert.same(1, vim.tbl_count(spec.plugins))
assert(spec.plugins.bar._.dep ~= true)
assert(spec.plugins.bar.lazy == true)
end)
it("merges lazy loaders", function()
local tests = {
{ { "foo/bar", event = "mod1" }, { "foo/bar", event = "mod2" } },
{ { "foo/bar", event = { "mod1" } }, { "foo/bar", event = { "mod2" } } },
{ { "foo/bar", event = "mod1" }, { "foo/bar", event = { "mod2" } } },
}
for _, test in ipairs(tests) do
Handler.init()
local spec = Plugin.Spec.new(test)
assert(#spec.notifs == 0)
assert(vim.tbl_count(spec.plugins) == 1)
Handler.resolve(spec.plugins.bar)
local events = spec.plugins.bar._.handlers.event
assert(type(events) == "table")
assert(vim.tbl_count(events) == 2)
assert(events["mod1"])
assert(events["mod2"])
end
end)
it("handles disabled", function()
local tests = {
[{ { "foo/bar" }, { "foo/bar", enabled = false } }] = false,
[{ { "foo/bar", enabled = false }, { "foo/bar" } }] = false,
[{ { "foo/bar", enabled = false }, { "foo/bar", enabled = true } }] = true,
[{ { "foo/bar" }, { "foo/bar", enabled = true } }] = true,
}
for test, ret in pairs(tests) do
local spec = Plugin.Spec.new(test)
assert(#spec.notifs == 0)
if ret then
assert(spec.plugins.bar)
assert(not spec.disabled.bar)
else
assert(not spec.plugins.bar)
assert(spec.disabled.bar)
end
end
end)
it("handles the optional keyword", function()
local tests = {
[{ { "foo/bax" }, { "foo/bar", optional = true, dependencies = "foo/dep1" } }] = false,
[{ { "foo/bax", dependencies = "foo/dep1" }, { "foo/bar", optional = true, dependencies = "foo/dep1" } }] = true,
}
for test, ret in pairs(tests) do
local spec = Plugin.Spec.new(test)
assert(#spec.notifs == 0)
assert(spec.plugins.bax)
assert(not spec.plugins.bar)
assert(#spec.disabled == 0)
if ret then
assert(spec.plugins.dep1)
else
assert(not spec.plugins.opt1)
end
end
end)
end)
describe("plugin opts", function()
---@type {spec:LazySpec, opts:table}[]
local tests = {
{
spec = { { "foo/foo", opts = { a = 1, b = 1 } }, { "foo/foo", opts = { a = 2 } } },
opts = { a = 2, b = 1 },
},
{
spec = { { "foo/foo", config = { a = 1, b = 1 } }, { "foo/foo", opts = { a = 2 } } },
opts = { a = 2, b = 1 },
},
{
spec = { { "foo/foo", opts = { a = 1, b = 1 } }, { "foo/foo", config = { a = 2 } } },
opts = { a = 2, b = 1 },
},
{
spec = { { "foo/foo", config = { a = 1, b = 1 } }, { "foo/foo", config = { a = 2 } } },
opts = { a = 2, b = 1 },
},
{
spec = { { "foo/foo", config = { a = 1, b = 1 } }, { "foo/foo", config = { a = vim.NIL } } },
opts = { b = 1 },
},
{
spec = { { "foo/foo", config = { a = 1, b = 1 } }, { "foo/foo" } },
opts = { a = 1, b = 1 },
},
{
spec = { { "foo/foo" }, { "foo/foo" } },
opts = {},
},
}
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)
describe("plugin spec", function()
it("only includes fragments from enabled plugins", function()
local tests = {
{
spec = {
{ "foo/disabled", enabled = false, dependencies = { "foo/bar", opts = { key_disabled = true } } },
{ "foo/disabled", dependencies = { "foo/bar", opts = { key_disabled_two = true } } },
{ "foo/conditional", cond = false, dependencies = { "foo/bar", opts = { key_cond = true } } },
{ "foo/optional", optional = true, dependencies = { "foo/bar", opts = { key_optional = true } } },
{ "foo/active", dependencies = { "foo/bar", opts = { key_active = true } } },
it("refuses to merge", function()
local spec = Plugin.Spec.new({
{ "foo/dep1", config = 1 },
{
"foo/bar",
opts = { key = true },
dependencies = { { "foo/dep1", config = 2 }, "foo/dep2" },
},
},
expected_opts = { key = true, key_active = true },
}, -- for now, one test...
}
for _, test in ipairs(tests) do
local spec = Plugin.Spec.new(test.spec)
assert(#spec.notifs == 0)
assert(vim.tbl_count(spec.plugins) == 2)
assert(spec.plugins.active)
assert(spec.plugins.bar)
assert.same(test.expected_opts, Plugin.values(spec.plugins.bar, "opts"))
})
assert(#spec.errors > 0)
end)
-- it("recursive deps", function()
-- local spec = Plugin.Spec.new({
-- "nvim-telescope/telescope.nvim",
-- dependencies = {
-- "nvim-lua/plenary.nvim",
-- { "nvim-telescope/telescope-fzf-native.nvim", build = "make" },
-- { "nvim-telescope/telescope-frecency.nvim", dependencies = "kkharji/sqlite.lua" },
-- },
-- })
-- end)
end)
it("handles opt from dep", function()
Config.options.defaults.lazy = false
local spec = Plugin.Spec.new({ "foo/dep1", { "foo/bar", dependencies = { "foo/dep1", "foo/dep2" } } })
assert(#spec.errors == 0)
Config.plugins = spec.plugins
Plugin.update_state()
assert.same(3, vim.tbl_count(spec.plugins))
assert(spec.plugins.bar._.dep ~= true)
assert(spec.plugins.bar.lazy == false)
assert(spec.plugins.dep2._.dep == true)
assert(spec.plugins.dep2.lazy == true)
assert(spec.plugins.dep1._.dep ~= true)
assert(spec.plugins.dep1.lazy == false)
end)
it("handles defaults opt", function()
do
Config.options.defaults.lazy = true
local spec = Plugin.Spec.new({ "foo/bar" })
assert(#spec.errors == 0)
Config.plugins = spec.plugins
Plugin.update_state()
assert(spec.plugins.bar.lazy == true)
end
do
Config.options.defaults.lazy = false
local spec = Plugin.Spec.new({ "foo/bar" })
Config.plugins = spec.plugins
Plugin.update_state()
assert(spec.plugins.bar.lazy == false)
end
end)
it("handles opt from dep", function()
Config.options.defaults.lazy = false
local spec = Plugin.Spec.new({ "foo/bar", event = "foo" })
assert(#spec.errors == 0)
Config.plugins = spec.plugins
Plugin.update_state()
assert.same(1, vim.tbl_count(spec.plugins))
assert(spec.plugins.bar._.dep ~= true)
assert(spec.plugins.bar.lazy == true)
end)
it("merges lazy loaders", function()
local tests = {
{ { "foo/bar", event = "mod1" }, { "foo/bar", event = "mod2" } },
{ { "foo/bar", event = { "mod1" } }, { "foo/bar", event = { "mod2" } } },
{ { "foo/bar", event = "mod1" }, { "foo/bar", event = { "mod2" } } },
}
for _, test in ipairs(tests) do
local spec = Plugin.Spec.new(test)
assert(#spec.errors == 0)
assert(vim.tbl_count(spec.plugins) == 1)
assert(type(spec.plugins.bar.event) == "table")
assert(#spec.plugins.bar.event == 2)
assert(vim.tbl_contains(spec.plugins.bar.event, "mod1"))
assert(vim.tbl_contains(spec.plugins.bar.event, "mod2"))
end
end)
it("refuses to merge", function()
local spec = Plugin.Spec.new({
{ "foo/dep1", config = 1 },
{
"foo/bar",
dependencies = { { "foo/dep1", config = 2 }, "foo/dep2" },
},
})
assert(#spec.errors > 0)
end)
-- it("recursive deps", function()
-- local spec = Plugin.Spec.new({
-- "nvim-telescope/telescope.nvim",
-- dependencies = {
-- "nvim-lua/plenary.nvim",
-- { "nvim-telescope/telescope-fzf-native.nvim", build = "make" },
-- { "nvim-telescope/telescope-frecency.nvim", dependencies = "kkharji/sqlite.lua" },
-- },
-- })
-- end)
end)

View file

@ -1,147 +0,0 @@
local Cache = require("lazy.core.cache")
local Helpers = require("tests.helpers")
local Util = require("lazy.util")
describe("util", function()
local rtp = vim.opt.rtp:get()
before_each(function()
---@type vim.Option
vim.opt.rtp = rtp
for k, v in pairs(package.loaded) do
if k:find("^foobar") then
package.loaded[k] = nil
end
end
Helpers.fs_rm("")
assert(not vim.uv.fs_stat(Helpers.path("")), "fs root should be deleted")
end)
it("lsmod lists all mods in dir", function()
vim.opt.rtp:append(Helpers.path(""))
local tests = {
{
root = "lua/foo",
mod = "foo",
files = { "lua/foo/one.lua", "lua/foo/two.lua", "lua/foo/init.lua" },
mods = { "foo.one", "foo.two", "foo" },
},
{
root = "lua/foo",
mod = "foo",
files = { "lua/foo/one.lua", "lua/foo/two.lua", "lua/foo.lua" },
mods = { "foo.one", "foo.two", "foo" },
},
{
root = "lua/foo",
mod = "foo",
files = { "lua/foo/one.lua", "lua/foo/two.lua" },
mods = { "foo.one", "foo.two" },
},
{
root = "lua/load-plugins",
mod = "load-plugins",
files = { "lua/load-plugins.lua" },
mods = { "load-plugins" },
},
}
for t, test in ipairs(tests) do
local expected = vim.deepcopy(test.mods)
table.sort(expected)
Helpers.fs_rm("")
local files = Helpers.fs_create(test.files)
-- test with empty cache
Cache.reset()
local root = Util.find_root(test.mod)
assert(root, "no root found for " .. test.mod .. " (test " .. t .. ")")
assert.same(Helpers.path(test.root), root)
local mods = {}
Util.lsmod(test.mod, function(modname, modpath)
mods[#mods + 1] = modname
end)
table.sort(mods)
assert.same(expected, mods)
-- fill the cache
Cache.reset()
root = Util.find_root(test.mod)
assert(root, "no root found for " .. test.mod .. " (test " .. t .. ")")
assert.same(Helpers.path(test.root), root)
mods = {}
Util.lsmod(test.mod, function(modname, modpath)
mods[#mods + 1] = modname
end)
table.sort(mods)
assert.same(expected, mods)
end
end)
it("find the correct root with dels", function()
Cache.reset()
vim.opt.rtp:append(Helpers.path("old"))
Helpers.fs_create({ "old/lua/foobar/init.lua" })
local root = Util.find_root("foobar")
assert(root, "foobar root not found")
assert.same(Helpers.path("old/lua/foobar"), root)
Helpers.fs_rm("old")
assert(not vim.uv.fs_stat(Helpers.path("old/lua/foobar")), "old/lua/foobar should not exist")
-- vim.opt.rtp = rtp
vim.opt.rtp:append(Helpers.path("new"))
Helpers.fs_create({ "new/lua/foobar/init.lua" })
root = Util.find_root("foobar")
assert(root, "foobar root not found")
assert.same(Helpers.path("new/lua/foobar"), root)
end)
it("merges correctly", function()
local tests = {
{
input = { { a = 1 }, { b = 2 } },
output = { a = 1, b = 2 },
},
{
input = { nil, { a = 1 }, { b = 2 } },
output = { a = 1, b = 2 },
},
{
input = { { a = 1 }, { b = 2 }, nil },
output = { a = 1, b = 2 },
},
{
input = { { a = 1 }, nil, { b = 2 } },
output = { a = 1, b = 2 },
},
{
input = { nil, { a = 1 }, nil, { b = 2 }, nil },
output = { a = 1, b = 2 },
},
{
input = { { a = 1 }, { a = 2 } },
output = { a = 2 },
},
{
input = { { a = { 1, 2 } }, { a = { 3 } } },
output = { a = { 3 } },
},
{
input = { { b = { 1, 2 } }, { a = { 3 }, b = vim.NIL } },
output = { a = { 3 } },
},
{
input = { { a = 1 }, { b = 2, a = vim.NIL } },
output = { b = 2 },
},
}
for _, test in ipairs(tests) do
local n = 0
for i in pairs(test.input) do
n = math.max(n, i)
end
assert.same(test.output, Util.merge(unpack(test.input, 1, n)))
end
end)
end)

View file

@ -1,18 +0,0 @@
local Keys = require("lazy.core.handler.keys")
describe("keys", function()
it("parses ids correctly", function()
local tests = {
{ "<C-/>", "<c-/>", true },
{ "<C-h>", "<c-H>", true },
{ "<C-h>k", "<c-H>K", false },
}
for _, test in ipairs(tests) do
if test[3] then
assert.same(Keys.parse(test[1]).id, Keys.parse(test[2]).id)
else
assert.is_not.same(Keys.parse(test[1]).id, Keys.parse(test[2]).id)
end
end
end)
end)

View file

@ -1,37 +0,0 @@
local Util = require("lazy.util")
local M = {}
M.fs_root = vim.fn.fnamemodify("./.tests/fs", ":p")
function M.path(path)
return Util.norm(M.fs_root .. "/" .. path)
end
---@param files string[]
function M.fs_create(files)
---@type string[]
local ret = {}
for _, file in ipairs(files) do
ret[#ret + 1] = Util.norm(M.fs_root .. "/" .. file)
local parent = vim.fn.fnamemodify(ret[#ret], ":h:p")
vim.fn.mkdir(parent, "p")
Util.write_file(ret[#ret], "")
end
return ret
end
function M.fs_rm(dir)
dir = Util.norm(M.fs_root .. "/" .. dir)
Util.walk(dir, function(path, _, type)
if type == "directory" then
vim.uv.fs_rmdir(path)
else
vim.uv.fs_unlink(path)
end
end)
vim.uv.fs_rmdir(dir)
end
return M

36
tests/init.lua Normal file
View file

@ -0,0 +1,36 @@
local M = {}
function M.root(root)
local f = debug.getinfo(1, "S").source:sub(2)
return vim.fn.fnamemodify(f, ":p:h:h") .. "/" .. (root or "")
end
---@param plugin string
function M.load(plugin)
local name = plugin:match(".*/(.*)")
local package_root = M.root(".tests/site/pack/deps/start/")
if not vim.loop.fs_stat(package_root .. name) then
print("Installing " .. plugin)
vim.fn.mkdir(package_root, "p")
vim.fn.system({
"git",
"clone",
"--depth=1",
"https://github.com/" .. plugin .. ".git",
package_root .. "/" .. name,
})
end
end
function M.setup()
vim.cmd([[set runtimepath=$VIMRUNTIME]])
vim.opt.runtimepath:append(M.root())
vim.opt.packpath = { M.root(".tests/site") }
M.load("nvim-lua/plenary.nvim")
vim.env.XDG_CONFIG_HOME = M.root(".tests/config")
vim.env.XDG_DATA_HOME = M.root(".tests/data")
vim.env.XDG_STATE_HOME = M.root(".tests/state")
vim.env.XDG_CACHE_HOME = M.root(".tests/cache")
end
M.setup()

View file

@ -1,19 +0,0 @@
local Async = require("lazy.async")
local Process = require("lazy.manage.process")
describe("process", function()
it("runs sync", function()
local lines = Process.exec({ "echo", "-n", "hello" })
assert.are.same({ "hello" }, lines)
end)
it("runs sync from async context", function()
local lines ---@type string[]
local async = Async.new(function()
lines = Process.exec({ "echo", "-n", "hello" })
end)
async:wait()
assert.are.same({ "hello" }, lines)
end)
end)

View file

@ -1,4 +1,3 @@
local Async = require("lazy.async")
local Runner = require("lazy.manage.runner")
describe("runner", function()
@ -31,11 +30,11 @@ describe("runner", function()
end,
}
package.loaded["lazy.manage.task.test"]["async" .. i] = {
---@async
---@param task LazyTask
run = function(task)
Async.yield()
task:schedule(function()
table.insert(runs, { plugin = task.plugin.name, task = task.name })
end)
end,
}
end
@ -65,7 +64,7 @@ describe("runner", function()
local runner = Runner.new({ plugins = plugins, pipeline = { "test.test1", "test.skip", "test.test2" } })
runner:start()
runner:wait()
assert.equal(4, #runs, runs)
assert.equal(4, #runs)
end)
it("handles opts", function()

View file

@ -14,7 +14,6 @@ describe("semver version", function()
["1.2.3+build"] = { major = 1, minor = 2, patch = 3, build = "build" },
}
for input, output in pairs(tests) do
output.input = input
it("correctly parses " .. input, function()
assert.same(output, v(input))
end)

View file

@ -1,82 +1,95 @@
--# selene:allow(incorrect_standard_library_use)
local Async = require("lazy.async")
local Task = require("lazy.manage.task")
describe("task", function()
local plugin = { name = "test", _ = {} }
---@type {done?:boolean, error:string?}
local task_result = {}
local done = false
local error = nil
local opts = {
---@param task LazyTask
on_done = function(task)
task_result = { done = true, error = task.error }
done = true
error = task.error
end,
}
before_each(function()
task_result = {}
done = false
error = nil
end)
it("simple function", function()
local task = Task.new(plugin, "test", function() end, opts)
assert(task:running())
task:wait()
assert(not task:running())
assert(task_result.done)
assert(not task:has_started())
assert(not task:is_running())
task:start()
assert(not task:is_running())
assert(task:is_done())
assert(done)
end)
it("detects errors", function()
local task = Task.new(plugin, "test", function()
error("test")
end, opts)
assert(task:running())
task:wait()
assert(not task:running())
assert(task_result.done)
assert(task_result.error)
assert(task:has_errors() and task:output(vim.log.levels.ERROR):find("test"))
assert(not task:has_started())
assert(not task:is_running())
task:start()
assert(task:is_done())
assert(not task:is_running())
assert(done)
assert(error)
assert(task.error and task.error:find("test"))
end)
it("async", function()
local running = true
---@async
local task = Task.new(plugin, "test", function()
Async.yield()
it("schedule", function()
local running = false
local task = Task.new(plugin, "test", function(task)
running = true
task:schedule(function()
running = false
end)
end, opts)
assert(task:running())
assert(not task:is_running())
assert(not task:has_started())
task:start()
assert(running)
assert(task:running())
assert(#task._running == 1)
assert(task:is_running())
assert(not task:is_done())
task:wait()
assert(not running)
assert(not task:running())
assert(task_result.done)
assert(not task:has_errors())
assert(task:is_done())
assert(not task:is_running())
assert(done)
assert(not task.error)
end)
it("spawn errors", function()
local task = Task.new(plugin, "spawn_errors", function(task)
local task = Task.new(plugin, "test", function(task)
task:spawn("foobar")
end, opts)
assert(task:running())
task:wait()
assert(not task:running())
assert(task_result.done)
assert(task:has_errors() and task:output(vim.log.levels.ERROR):find("Failed to spawn"), task:output())
assert(not task:is_running())
task:start()
assert(not task:is_running())
assert(done)
assert(task.error and task.error:find("Failed to spawn"))
end)
it("spawn", function()
local task = Task.new(plugin, "test", function(task)
task:spawn("echo", { args = { "foo" } })
end, opts)
assert(task:running())
assert(task:running())
assert(not task:is_running())
assert(not task:has_started())
task:start()
assert(task:has_started())
assert(task:is_running())
task:wait()
assert.same(task:output(), "foo")
assert(task_result.done)
assert(not task:has_errors())
assert(task:is_done())
assert.same(task.output, "foo\n")
assert(done)
assert(not task.error)
end)
it("spawn 2x", function()
@ -84,11 +97,12 @@ describe("task", function()
task:spawn("echo", { args = { "foo" } })
task:spawn("echo", { args = { "bar" } })
end, opts)
assert(task:running())
assert(task:running())
assert(not task:is_running())
task:start()
assert(task:is_running())
task:wait()
assert(task:output() == "foo\nbar" or task:output() == "bar\nfoo", task:output())
assert(task_result.done)
assert(not task:has_errors())
assert.same(task.output, "foo\nbar\n")
assert(done)
assert(not task.error)
end)
end)

View file

@ -1,12 +0,0 @@
#!/usr/bin/env -S nvim -l
vim.env.LAZY_STDPATH = ".tests"
vim.opt.rtp:prepend(".")
-- Setup lazy.nvim
require("lazy.minit").setup({
spec = {
{ dir = vim.uv.cwd() },
},
})

3
tests/run Executable file
View file

@ -0,0 +1,3 @@
#!/bin/sh
nvim --headless -u tests/init.lua -c "PlenaryBustedDirectory tests {minimal_init = 'tests//init.lua', sequential = true}"

View file

@ -8,14 +8,42 @@ any = true
[jit]
any = true
[assert]
[[describe.args]]
type = "string"
[[describe.args]]
type = "function"
[[it.args]]
type = "string"
[[it.args]]
type = "function"
[[before_each.args]]
type = "function"
[[after_each.args]]
type = "function"
[assert.is_not]
any = true
[describe]
any = true
[[assert.equals.args]]
type = "any"
[[assert.equals.args]]
type = "any"
[[assert.equals.args]]
type = "any"
required = false
[it]
any = true
[[assert.same.args]]
type = "any"
[[assert.same.args]]
type = "any"
[before_each.args]
any = true
[[assert.truthy.args]]
type = "any"
[[assert.spy.args]]
type = "any"
[[assert.stub.args]]
type = "any"