Compare commits

..

No commits in common. "main" and "v8.1.0" have entirely different histories.

95 changed files with 3457 additions and 128040 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,10 +6,7 @@ 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?
@ -17,11 +14,9 @@ body:
options:
- label: I have read all the lazy.nvim docs
required: true
- label: I have updated the plugin to the latest version before submitting this issue
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 plugins related to this issue
required: true
- type: input
attributes:
@ -62,14 +57,32 @@ 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", "https://github.com/folke/lazy.nvim.git", lazypath, })
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",
})
render: lua
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

@ -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,68 @@
name: CI
on:
push:
branches: [main, master]
pull_request:
jobs:
ci:
uses: folke/github/.github/workflows/ci.yml@main
secrets: inherit
with:
plugin: lazy.nvim
repo: folke/lazy.nvim
docs: false
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: |
wget -q https://github.com/neovim/neovim/releases/download/nightly/nvim-linux64.deb -O /tmp/nvim.deb
sudo dpkg -i /tmp/nvim.deb
- name: Run Tests
run: |
nvim --version
[ ! -d tests ] && exit 0
nvim --headless -u tests/init.lua -c "PlenaryBustedDirectory tests/ {minimal_init = 'tests/init.lua', sequential = true}"
docs:
runs-on: ubuntu-latest
needs: tests
if: ${{ github.ref == 'refs/heads/main' }}
steps:
- uses: actions/checkout@v3
- name: panvimdoc
uses: kdheepak/panvimdoc@main
with:
vimdoc: lazy.nvim
version: "Neovim >= 0.8.0"
demojify: true
treesitter: true
- name: Push changes
uses: stefanzweifel/git-auto-commit-action@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
id: release
with:
release-type: simple
package-name: lazy.nvim
- uses: actions/checkout@v2
- name: tag stable versions
if: ${{ steps.release.outputs.release_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

15
.gitignore vendored
View file

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

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

769
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,732 @@
- 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",
"https://github.com/folke/lazy.nvim.git",
"--branch=stable", -- latest stable release
lazypath,
})
end
vim.opt.rtp:prepend(lazypath)
```
<!-- bootstrap:end -->
Next step is to add **lazy.nvim** 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
},
-- leave nil when passing the spec as the first argument to setup()
spec = nil, ---@type LazySpec
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 = {
cmd = " ",
config = "",
event = "",
ft = " ",
init = " ",
import = " ",
keys = " ",
lazy = "鈴 ",
loaded = "●",
not_loaded = "○",
plugin = " ",
runtime = " ",
source = " ",
start = "",
task = "✔ ",
list = {
"●",
"➜",
"★",
"",
},
},
-- leave nil, to automatically select a browser depending on your OS.
-- If you want to use a specific browser, you can define it here
browser = nil, ---@type string?
throttle = 20, -- how frequently should the ui process render events
custom_keys = {
-- you can define custom key maps here.
-- To disable one of the defaults, set it to false
-- open lazygit log
["<localleader>l"] = function(plugin)
require("lazy.util").float_term({ "lazygit", "log" }, {
cwd = plugin.dir,
})
end,
-- open a terminal for the plugin dir
["<localleader>t"] = function(plugin)
require("lazy.util").float_term(nil, {
cwd = plugin.dir,
})
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 = { "UIEnter", "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", "lua/**/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 = "📌",
lazy = "💤 ",
},
},
}
```
</details>
## 🚀 Usage
Plugins are managed with the `:Lazy` command.
Open the help with `<?>` to see all the key mappings.
You can press `<CR>` on a plugin to show its details. Most properties
can be hovered with `<K>` to open links, help files, readmes,
git commits and git issues.
Lazy can automatically check for updates in the background. This feature
can be enabled with `config.checker.enabled = true`.
Any operation can be started from the UI, with a sub command or an API function:
<!-- commands:start -->
| Command | Lua | Description |
| ------------------------- | -------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
| `:Lazy build {plugins}` | `require("lazy").build(opts)` | Rebuild a plugin |
| `:Lazy check [plugins]` | `require("lazy").check(opts?)` | Check for updates and show the log (git fetch) |
| `:Lazy clean [plugins]` | `require("lazy").clean(opts?)` | Clean plugins that are no longer needed |
| `:Lazy clear` | `require("lazy").clear()` | Clear finished tasks |
| `:Lazy debug` | `require("lazy").debug()` | Show debug information |
| `:Lazy health` | `require("lazy").health()` | Run `:checkhealth lazy` |
| `:Lazy help` | `require("lazy").help()` | Toggle this help page |
| `:Lazy home` | `require("lazy").home()` | Go back to plugin list |
| `:Lazy install [plugins]` | `require("lazy").install(opts?)` | Install missing plugins |
| `:Lazy load {plugins}` | `require("lazy").load(opts)` | Load a plugin that has not been loaded yet. Similar to `:packadd`. Like `:Lazy load foo.nvim`. Use `:Lazy! load` to skip `cond` checks. |
| `:Lazy log [plugins]` | `require("lazy").log(opts?)` | Show recent updates |
| `:Lazy profile` | `require("lazy").profile()` | Show detailed profiling |
| `:Lazy restore [plugins]` | `require("lazy").restore(opts?)` | Updates all plugins to the state in the lockfile. For a single plugin: restore it to the state in the lockfile or to a given commit under the cursor |
| `:Lazy sync [plugins]` | `require("lazy").sync(opts?)` | Run install, clean and update |
| `:Lazy update [plugins]` | `require("lazy").update(opts?)` | Update plugins. This will also update the lockfile |
<!-- commands:end -->
Any command can have a **bang** to make the command wait till it finished. For example,
if you want to sync lazy from the cmdline, you can use:
```shell
$ nvim --headless "+Lazy! sync" +qa
```
`opts` is a table with the following key-values:
- **wait**: when true, then the call will wait till the operation completed
- **show**: when false, the UI will not be shown
- **plugins**: a list of plugin names to run the operation on
- **concurrency**: limit the `number` of concurrently running tasks
Stats API (`require("lazy").stats()`):
<!-- stats:start -->
```lua
{
-- startuptime in milliseconds till UIEnter
startuptime = 0,
-- when true, startuptime is the accurate cputime for the Neovim process. (Linux & Macos)
-- this is more accurate than `nvim --startuptime`, and as such will be slightly higher
-- when false, startuptime is calculated based on a delta with a timestamp when lazy started.
real_cputime = false,
count = 0, -- total number of plugins
loaded = 0, -- number of loaded plugins
---@type table<string, number>
times = {},
}
```
<!-- stats:end -->
**lazy.nvim** provides a statusline component that you can use to show the number of pending updates.
Make sure to enable `config.checker.enabled = true` to make this work.
<details>
<summary>Example of configuring <a href="https://github.com/nvim-lualine/lualine.nvim">lualine.nvim</a></summary>
```lua
require("lualine").setup({
sections = {
lualine_x = {
{
require("lazy.status").updates,
cond = require("lazy.status").has_updates,
color = { fg = "#ff9e64" },
},
},
},
})
```
</details>
### 📆 User Events
The following user events will be triggered:
- **LazyDone**: when lazy has finished starting up and loaded your config
- **LazySync**: after running sync
- **LazyInstall**: after an install
- **LazyUpdate**: after an update
- **LazyClean**: after a clean
- **LazyCheck**: after checking for updates
- **LazyLog**: after running log
- **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 [LazyVim](https://github.com/LazyVim/LazyVim) and more specifically:
- [lazyvim.plugins](https://github.com/LazyVim/LazyVim/tree/main/lua/lazyvim/plugins) contains all the plugin specs that will be loaded
## 📦 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)
- `rtp` can be accomplished with:
```lua
config = function(plugin)
vim.opt.rtp:append(plugin.dir .. "/custom-rtp")
end
```
With packer `wants`, `requires` and `after` can be used to manage dependencies.
With lazy, this isn't needed for most of the lua dependencies. They can be installed just like normal plugins
(even with `lazy=true`) and will be loaded when other plugins need them.
The `dependencies` key can be used to group those required plugins with the one that requires them.
The plugins which are added as `dependencies` will always be lazy-loaded and loaded when the plugin is loaded.
### [paq-nvim](https://github.com/savq/paq-nvim)
- `as` ➡️ `name`
- `opt` ➡️ `lazy`
- `run` ➡️ `build`
## ❌ Uninstalling
To uninstall **lazy.nvim**, you need to remove the following files and directories:
- **data**: `~/.local/share/nvim/lazy`
- **state**: `~/.local/state/nvim/lazy`
- **lockfile**: `~/.config/nvim/lazy-lock.json`
> paths can differ if you changed `XDG` environment variables.
## 🌈 Highlight Groups
<details>
<summary>Click to see all highlight groups</summary>
<!-- colors:start -->
| Highlight Group | Default Group | Description |
| --------------------- | -------------------------- | --------------------------------------------------- |
| **LazyButton** | **_CursorLine_** | |
| **LazyButtonActive** | **_Visual_** | |
| **LazyComment** | **_Comment_** | |
| **LazyCommit** | **_@variable.builtin_** | commit ref |
| **LazyCommitIssue** | **_Number_** | |
| **LazyCommitScope** | **_Italic_** | conventional commit scope |
| **LazyCommitType** | **_Title_** | conventional commit type |
| **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_** | |
| **LazyReasonImport** | **_Identifier_** | |
| **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,
},
}

File diff suppressed because it is too large Load diff

View file

@ -7,73 +7,26 @@ 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,33 +37,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 = "",
event = "",
ft = "",
init = "",
import = "",
keys = "",
lazy = "󰒲 ",
lazy = " ",
loaded = "",
not_loaded = "",
plugin = "",
runtime = "",
require = "󰢱 ",
source = "",
start = " ",
start = "",
task = "",
list = {
"",
@ -122,52 +65,26 @@ 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" }, {
cwd = plugin.dir,
})
end,
desc = "Open lazygit log",
},
-- open lazygit log
["<localleader>l"] = function(plugin)
require("lazy.util").float_term({ "lazygit", "log" }, {
cwd = plugin.dir,
})
end,
["<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, {
cwd = plugin.dir,
})
end,
desc = "Open terminal in plugin dir",
},
-- open a terminal for the plugin dir
["<localleader>t"] = function(plugin)
require("lazy.util").float_term(nil, {
cwd = plugin.dir,
})
end,
},
},
-- 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,34 +131,14 @@ 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
-- 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
@ -264,13 +159,8 @@ 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
return #vim.api.nvim_list_uis() == 0
end
---@param opts? LazyConfig
@ -280,35 +170,26 @@ function M.setup(opts)
if type(M.options.spec) == "string" then
M.options.spec = { import = M.options.spec }
end
M.options.performance.cache = require("lazy.core.cache").config
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.dev.path = Util.norm(M.options.dev.path)
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,7 +201,10 @@ 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()
end
vim.api.nvim_create_autocmd("UIEnter", {
once = true,
@ -329,42 +213,22 @@ function M.setup(opts)
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.view.commands").setup()
if M.options.change_detection.enabled then
require("lazy.manage.reloader").enable()
end
if M.options.checker.enabled then
vim.defer_fn(function()
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
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()
end
if M.options.checker.enabled then
vim.defer_fn(function()
require("lazy.manage.checker").start()
end, 10)
end
end,
})
Util.very_lazy()
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,88 @@
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
Util.track()
end
self:trigger(event, pattern, groups)
Util.track()
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)
---@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",
" - **group:** `" .. autocmd.group_name .. "`",
" - **event:** " .. autocmd.event,
pattern and (" - **pattern:** " .. pattern),
})
end
Util.track({ event = autocmd.group_name })
Util.try(function()
vim.api.nvim_exec_autocmds(autocmd.event, { group = autocmd.group, modeline = false })
Util.track()
end)
end
end
end
end
-- Trigger an event
---@param opts LazyEventOpts
function M._trigger(opts)
if Config.options.debug then
Util.info({
"# Firing Events",
" - **event:** " .. opts.event,
opts.group and (" - **group:** " .. opts.group),
opts.buffer and (" - **buffer:** " .. opts.buffer),
})
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()
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,10 @@
local Config = require("lazy.core.config")
local Util = require("lazy.core.util")
local Config = require("lazy.core.config")
---@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,16 +19,10 @@ 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)
@ -39,19 +32,20 @@ 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)
end
for type in pairs(plugin._.handlers or {}) do
M.handlers[type]:add(plugin)
for type, handler in pairs(M.handlers) do
if plugin[type] then
handler:add(plugin)
end
end
end
end
@ -64,57 +58,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 +89,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,115 @@
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, ", ") .. ")"
---@param feed string
function M.replace_special(feed)
for special, key in pairs({ leader = vim.g.mapleader or "\\", localleader = vim.g.maplocalleader or "\\" }) do
local pattern = "<"
for i = 1, #special do
pattern = pattern .. "[" .. special:sub(i, i) .. special:upper():sub(i, i) .. "]"
end
pattern = pattern .. ">"
feed = feed:gsub(pattern, key)
end
return feed
end
if ret.mode ~= "n" then
ret.id = ret.id .. " (" .. ret.mode .. ")"
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 op = vim.v.operator
if op and op ~= "" and vim.api.nvim_get_mode().mode:find("o") then
keys = "<esc>" .. op .. keys
end
local feed = keys .. pending
feed = M.replace_special(feed)
if vim.v.count ~= 0 then
feed = vim.v.count .. feed
end
vim.api.nvim_input(feed)
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)
vim.keymap.set(keys.mode, lhs, function()
local key = self:key(value)
local plugins = self.active[key]
---@param buf? number
local function add(buf)
if M.is_nop(keys) then
return self:_set(keys, buf)
end
-- always delete the mapping immediately to prevent recursive mappings
self:_del(value)
self.active[key] = nil
vim.keymap.set(keys.mode, lhs, function()
local plugins = self.active[keys.id]
-- always delete the mapping immediately to prevent recursive mappings
self:_del(keys)
self.active[keys.id] = nil
if plugins then
local name = M.to_string(keys)
Util.track({ keys = name })
Loader.load(plugins, { keys = name })
Util.track()
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
Util.track({ keys = lhs })
Loader.load(plugins, { keys = lhs })
M.retrigger(lhs)
Util.track()
end, opts)
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,9 @@
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 Cache = require("lazy.core.cache")
local Plugin = require("lazy.core.plugin")
local Util = require("lazy.core.util")
---@class LazyCoreLoader
local M = {}
local DEFAULT_PRIORITY = 50
@ -16,7 +15,6 @@ 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
@ -35,7 +33,6 @@ function M.setup()
-- load the plugins
Plugin.load()
Handler.init()
-- install missing plugins
if Config.options.install.missing then
@ -44,14 +41,12 @@ function M.setup()
while M.install_missing() do
count = count + 1
if count > 5 then
Util.error("Too many rounds of missing plugins")
break
end
end
Util.track()
end
Config.mapleader = vim.g.mapleader
Config.maplocalleader = vim.g.maplocalleader
-- report any warnings & errors
Config.spec:report()
@ -59,7 +54,6 @@ function M.setup()
-- setup handlers
Util.track("handlers")
Handler.setup()
M.did_handlers = true
Util.track()
end
@ -67,25 +61,17 @@ end
-- 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 or Plugin.has_errors(plugin)) 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
require("lazy.manage").install({ wait = true, lockfile = true })
-- remove and installed plugins from indexed, so cache will index again
for _, p in pairs(Config.plugins) do
if p._.installed then
Cache.reset(p.dir)
Cache.indexed[p.dir] = nil
end
end
-- reload plugins
@ -104,7 +90,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 +119,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 +126,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 +142,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
@ -199,114 +180,6 @@ function M.load(plugins, reason, opts)
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)
end
end
end
---@param plugin LazyPlugin
---@param reason {[string]:string}
---@param opts? {force:boolean} when force is true, we skip the cond check
@ -315,14 +188,11 @@ function M._load(plugin, reason, opts)
return Util.error("Plugin " .. plugin.name .. " is not installed")
end
if plugin._.cond == false and not (opts and opts.force) then
return
end
if not Handler.did_setup then
Util.try(function()
Handler.enable(plugin)
end, "Failed to setup handlers for " .. plugin.name)
if plugin.cond ~= nil and not (opts and opts.force) then
if plugin.cond == false or (type(plugin.cond) == "function" and not plugin.cond()) then
plugin._.cond = false
return
end
end
---@diagnostic disable-next-line: assign-type-mismatch
@ -341,13 +211,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
M.add_to_rtp(plugin)
if plugin.dependencies then
Util.try(function()
@ -355,18 +219,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
M.packadd(plugin.dir)
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 +237,28 @@ 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 string[]
local mods = {}
for _, modname in ipairs(Cache.get_topmods(plugin.dir)) do
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
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 +269,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 +317,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,26 +335,13 @@ 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()
@ -520,7 +358,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 +366,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,68 +1,106 @@
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
local list_merge = { "dependencies" }
vim.list_extend(list_merge, vim.tbl_values(Handler.types))
---@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
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)
function Spec.new(spec)
local self = setmetatable({}, { __index = Spec })
self.plugins = {}
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
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
---@param plugin LazyPlugin
---@param results? string[]
---@param is_dep? boolean
function Spec:add(plugin, results, is_dep)
-- 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 plugin._ then
if results then
table.insert(results, plugin.name)
end
return plugin
end
if not plugin.url and plugin[1] then
plugin.url = Config.options.git.url_format:format(plugin[1])
end
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))
return
end
plugin.event = type(plugin.event) == "string" and { plugin.event } or plugin.event
plugin.keys = type(plugin.keys) == "string" and { plugin.keys } or plugin.keys
plugin.cmd = type(plugin.cmd) == "string" and { plugin.cmd } or plugin.cmd
plugin.ft = type(plugin.ft) == "string" and { plugin.ft } or plugin.ft
plugin._ = {}
plugin._.dep = is_dep
plugin.dependencies = plugin.dependencies and self:normalize(plugin.dependencies, {}, true) or nil
if self.plugins[plugin.name] then
plugin = self:merge(self.plugins[plugin.name], plugin)
end
self.plugins[plugin.name] = plugin
if results then
table.insert(results, plugin.name)
end
return plugin
end
function Spec:error(msg)
self:log(msg, vim.log.levels.ERROR)
end
@ -71,6 +109,53 @@ function Spec:warn(msg)
self:log(msg, vim.log.levels.WARN)
end
function Spec:fix_disabled()
---@type table<string,string[]> plugin to parent plugin
local dep_of = {}
---@type string[] dependencies of disabled plugins
local disabled_deps = {}
for _, plugin in pairs(self.plugins) do
local enabled = not (plugin.enabled == false or (type(plugin.enabled) == "function" and not plugin.enabled()))
if enabled then
for _, dep in ipairs(plugin.dependencies or {}) do
dep_of[dep] = dep_of[dep] or {}
table.insert(dep_of[dep], plugin.name)
end
else
plugin._.kind = "disabled"
self.plugins[plugin.name] = nil
self.disabled[plugin.name] = plugin
if plugin.dependencies then
vim.list_extend(disabled_deps, plugin.dependencies)
end
end
end
-- check deps of disabled plugins
for _, dep in ipairs(disabled_deps) do
-- only check if the plugin is still enabled and it is a dep
if self.plugins[dep] and self.plugins[dep]._.dep then
-- check if the dep is still used by another plugin
local keep = false
for _, parent in ipairs(dep_of[dep] or {}) do
if self.plugins[parent] then
keep = true
break
end
end
-- disable the dep when no longer needed
if not keep then
local plugin = self.plugins[dep]
plugin._.kind = "disabled"
self.plugins[plugin.name] = nil
self.disabled[plugin.name] = plugin
end
end
end
end
---@param msg string
---@param level number
function Spec:log(msg, level)
@ -79,32 +164,38 @@ 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
Util.notify(notif.msg, notif.level)
end
end
return count
end
---@param spec LazySpec|LazySpecImport
function Spec:normalize(spec)
---@param results? string[]
---@param is_dep? boolean
function Spec:normalize(spec, results, is_dep)
if type(spec) == "string" then
self.meta:add({ spec })
if is_dep and not spec:find("/", 1, true) then
-- spec is a plugin name
if results then
table.insert(results, spec)
end
else
self:add({ spec }, results, is_dep)
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)
---@cast spec LazyPlugin
local plugin = self:add(spec, results, is_dep)
---@diagnostic disable-next-line: cast-type-mismatch
---@cast spec LazySpecImport
if spec and spec.import then
self:import(spec)
---@cast plugin LazySpecImport
if plugin and plugin.import then
self:import(plugin)
end
elseif spec.import then
---@cast spec LazySpecImport
@ -112,6 +203,7 @@ function Spec:normalize(spec)
else
self:error("Invalid plugin spec " .. vim.inspect(spec))
end
return results
end
---@param spec LazySpecImport
@ -119,113 +211,81 @@ 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
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
Cache.indexed_unloaded = false
self.modules[#self.modules + 1] = 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
Util.lsmod(spec.import, function(modname)
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"
)
else
self:normalize(mod)
end
self:normalize(require(modname))
self.importing = nil
Util.track()
end, {
msg = "Failed to load `" .. modname .. "`",
on_error = function(msg)
self:error(msg)
self.importing = nil
Util.track()
end,
})
self.importing = nil
Util.track()
end
end)
if imported == 0 then
self:error("No specs found for module " .. vim.inspect(spec.import))
self:error("No specs found for module " .. spec.import)
end
end
function M.update_state()
---@type string[]
local cloning = {}
---@param old LazyPlugin
---@param new LazyPlugin
---@return LazyPlugin
function Spec:merge(old, new)
new._.dep = old._.dep and new._.dep
if new.url and old.url and new.url ~= old.url then
self:error("Two plugins with the same name and different url:\n" .. vim.inspect({ old = old, new = new }))
end
for _, prop in ipairs(list_merge) do
if new[prop] and old[prop] then
---@type any[]
local props = {}
---@diagnostic disable-next-line: no-unknown
for _, value in ipairs(old[prop]) do
props[#props + 1] = value
end
---@diagnostic disable-next-line: no-unknown
for _, value in ipairs(new[prop]) do
if not vim.tbl_contains(props, value) then
props[#props + 1] = value
end
end
---@diagnostic disable-next-line: no-unknown
new[prop] = props
end
end
new._.super = old
setmetatable(new, { __index = old })
return new
end
function M.update_state()
---@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 +297,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,66 +321,14 @@ function M.update_state()
end
end
function M.update_rocks_state()
local root = Config.options.rocks.root
---@type table<string,string>
local installed = {}
Util.ls(root, function(_, name, type)
if type == "directory" then
installed[name] = name
end
end)
for _, plugin in pairs(Config.plugins) do
if plugin.build == "rockspec" or plugin.name == "hererocks" then
plugin._.build = not installed[plugin.name]
end
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
end
function M.load()
M.loading = true
-- load specs
Util.track("spec")
Config.spec = Spec.new()
Config.spec:normalize(vim.deepcopy(Config.options.spec))
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)
-- add ourselves
Config.spec:add({ "folke/lazy.nvim" })
-- override some lazy props
local lazy = Config.spec.plugins["lazy.nvim"]
if lazy then
@ -340,31 +339,14 @@ function M.load()
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
Config.spec:fix_disabled()
local existing = Config.plugins
Config.plugins = Config.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,36 +354,22 @@ 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)
function M.find(path)
if not Config.spec then
return
end
opts = opts or {}
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]
end
return Config.spec.plugins[name]
end
return name and Config.plugins[name] or Config.spec.plugins[name] or nil
end
end
end
@ -409,76 +377,11 @@ end
---@param plugin LazyPlugin
function M.has_errors(plugin)
for _, task in ipairs(plugin._.tasks or {}) do
if task:has_errors() then
if task.error 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
return M

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,29 +23,11 @@ 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
@ -66,14 +47,14 @@ 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
@ -83,44 +64,38 @@ function M.norm(path)
return path:sub(-1) == "/" and path:sub(1, -2) or path
end
---@param opts? {level?: number}
function M.pretty_trace(opts)
opts = opts or {}
local Config = require("lazy.core.config")
local trace = {}
local level = opts.level or 2
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
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]]
local line = " - " .. source .. ":" .. info.currentline
if info.name then
line = line .. " _in_ **" .. info.name .. "**"
end
table.insert(trace, line)
end
level = level + 1
end
return #trace > 0 and ("\n\n# stacktrace:\n" .. table.concat(trace, "\n")) or ""
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()
local Config = require("lazy.core.config")
local trace = {}
local level = 1
while true do
local info = debug.getinfo(level, "Sln")
if not info then
break
end
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:~:.")
local line = " - " .. source .. ":" .. info.currentline
if info.name then
line = line .. " _in_ **" .. info.name .. "**"
end
table.insert(trace, line)
end
level = level + 1
end
msg = (msg and (msg .. "\n\n") or "") .. err
if #trace > 0 then
msg = msg .. "\n\n# stacktrace:\n" .. table.concat(trace, "\n")
end
if opts.on_error then
opts.on_error(msg)
else
@ -143,7 +118,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 +141,10 @@ end
function M.very_lazy()
local function _load()
vim.schedule(function()
if vim.v.exiting ~= vim.NIL then
return
end
vim.defer_fn(function()
vim.g.did_very_lazy = true
M.track({ event = "VeryLazy" })
vim.api.nvim_exec_autocmds("User", { pattern = "VeryLazy", modeline = false })
M.track()
end)
vim.cmd("do User VeryLazy")
end, 50)
end
vim.api.nvim_create_autocmd("User", {
@ -184,7 +154,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 +169,16 @@ 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)
-- HACK: assume type is a file if no type returned
-- see https://github.com/folke/lazy.nvim/issues/306
t = t or "file"
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,96 +195,17 @@ function M.walk(path, fn)
end)
end
---@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)
local Cache = require("lazy.core.cache")
local root = Cache.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
if vim.loop.fs_stat(root .. ".lua") then
fn(modname, root .. ".lua")
end
M.ls(root, function(path, name, type)
@ -325,38 +213,20 @@ function M.lsmod(modname, fn)
fn(modname, path)
elseif (type == "file" or type == "link") and name:sub(-4) == ".lua" then
fn(modname .. "." .. name:sub(1, -5), path)
elseif type == "directory" and vim.uv.fs_stat(path .. "/init.lua") then
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)
---@param opts? {lang:string, title:string}
function M.notify(msg, level, opts)
if vim.in_fast_event() then
return vim.schedule(function()
M.notify(msg, opts)
vim.schedule(function()
M.notify(msg, level, opts)
end)
return
end
opts = opts or {}
@ -368,20 +238,10 @@ 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
pcall(require, "nvim-treesitter")
vim.wo[win].conceallevel = 3
vim.wo[win].concealcursor = ""
vim.wo[win].spell = false
@ -391,126 +251,39 @@ function M.notify(msg, opts)
vim.bo[buf].syntax = lang
end
end,
title = opts.title or "lazy.nvim",
title = "lazy.nvim" .. (opts.title and ": " .. opts.title or ""),
})
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)
function M.warn(msg)
M.notify(msg, vim.log.levels.WARN)
end
---@param msg string|table
---@param opts? LazyNotifyOpts
function M.debug(msg, opts)
---@param level? number
---@param opts? {lang:string, title:string}
function M.debug(msg, level, 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)
M.notify(msg, level, opts)
else
opts.lang = "lua"
M.notify(vim.inspect(msg), opts)
M.notify(vim.inspect(msg), level, 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
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
width = math.min(width, #line:match("^%s*"))
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" or tag == "plugins" 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,18 @@ 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)
function M.plugins()
local Config = require("lazy.core.config")
local lines = { "## Plugins", "" }
Util.foreach(Config.plugins, function(name, plugin)
if plugin.url then
lines[#lines + 1] = "- [" .. name .. "](" .. plugin.url:gsub("%.git$", "") .. ")"
end
end)
return { content = table.concat(lines, "\n") }
M.save({ plugins = table.concat(lines, "\n") })
end
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,75 +1,9 @@
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")
vim.health.report_start("lazy.nvim")
local sites = vim.opt.packpath:get()
local default_site = vim.fn.stdpath("data") .. "/site"
@ -80,94 +14,41 @@ function M.check()
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
if not packs:find("/dist$") and vim.loop.fs_stat(packs) then
existing = true
warn("found existing packages at `" .. packs .. "`")
vim.health.report_warn("found existing packages at `" .. packs .. "`")
end
end
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
for _, plugin in pairs(spec.plugins) do
M.check_valid(plugin)
M.check_override(plugin)
end
if #spec.notifs > 0 then
vim.health.report_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
vim.health.report_error(line)
else
vim.health.report_warn(line)
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
@ -175,43 +56,50 @@ 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 .. ">")
vim.health.report_warn("{" .. plugin.name .. "}: unknown key <" .. key .. ">")
end
end
end
end
---@param plugin LazyPlugin
function M.check_override(plugin)
if not plugin._.super then
return
end
for key, value in pairs(plugin._.super) do
if key ~= "_" and plugin[key] and plugin[key] ~= value then
vim.health.report_warn("{" .. plugin.name .. "}: overriding <" .. key .. ">")
end
end
end
M.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",
"_",
}
return M

View file

@ -4,21 +4,20 @@ 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
---@param file string
local files = vim.tbl_flatten(vim.tbl_map(function(file)
return vim.fn.expand(plugin.dir .. "/" .. file, false, true)
end, Config.options.readme.files))
---@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
if vim.loop.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("^%-$", "")
local lines = vim.split(Util.read_file(file), "\n")
@ -42,16 +41,12 @@ 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 +59,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,31 +1,7 @@
---@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)
function M.setup(spec, opts)
@ -37,7 +13,7 @@ function M.setup(spec, opts)
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,24 +31,10 @@ 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.core.cache").setup(opts)
require("lazy.stats").track("LazyStart")
@ -80,18 +42,8 @@ function M.setup(spec, opts)
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
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")
@ -102,7 +54,7 @@ function M.setup(spec, opts)
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,7 +64,7 @@ function M.setup(spec, opts)
Loader.startup()
-- all done!
vim.api.nvim_exec_autocmds("User", { pattern = "LazyDone", modeline = false })
vim.cmd("do User LazyDone")
require("lazy.stats").track("LazyDone")
end
@ -122,7 +74,7 @@ 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",

View file

@ -1,9 +1,8 @@
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 Plugin = require("lazy.core.plugin")
local Git = require("lazy.manage.git")
local M = {}
@ -13,31 +12,14 @@ 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
if not plugin.pin and plugin._.installed then
plugin._.updates = nil
local info = Git.info(plugin.dir)
local ok, target = pcall(Git.get_target, plugin)
@ -49,28 +31,23 @@ function M.fast_check(opts)
M.report(opts.report ~= false)
end
function M.has_errors()
function M.check()
local errors = false
for _, plugin in pairs(Config.plugins) do
if Plugin.has_errors(plugin) then
return true
errors = true
break
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()
if errors then
vim.defer_fn(M.check, Config.options.checker.frequency * 1000)
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

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 = {}
@ -116,12 +116,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 +131,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 == nil and Config.options.defaults.version or plugin.version
if version then
local last = Semver.last(M.get_versions(plugin.dir, version))
if last then
@ -150,6 +143,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
@ -189,12 +183,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 +193,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 = {}
@ -18,13 +18,6 @@ local M = {}
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 +42,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 +74,14 @@ function M.install(opts)
opts = M.opts(opts, { mode = "install" })
return M.run({
pipeline = {
"plugin.exists",
"git.clone",
{ "git.checkout", lockfile = opts.lockfile },
"plugin.docs",
{
"wait",
---@param runner Runner
sync = function(runner)
require("lazy.pkg").update()
Plugin.load()
runner:update()
end,
},
"wait",
"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()
@ -109,22 +94,11 @@ 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,
},
"wait",
"plugin.build",
{ "git.log", updated = true },
},
@ -149,10 +123,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,14 +133,11 @@ 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,
@ -200,18 +168,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)
@ -239,11 +202,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]
f:write(([[ %q: { "branch": %q, "commit": %q }]]):format(name, info.branch, info.commit))
if n ~= #names then
f:write(",\n")
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
end
f:write("\n}\n")
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)
M.load()
if not M._loaded then
M.load()
end
return M.lock[plugin.name]
end

View file

@ -1,239 +1,190 @@
local Async = require("lazy.async")
local Config = require("lazy.core.config")
local M = {}
---@type table<vim.loop.Process, true>
M.running = {}
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",
}
---@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)
---@param cmd string
function M.spawn(cmd, opts)
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, {
local env = {
"GIT_TERMINAL_PROMPT=0",
"GIT_SSH_COMMAND=ssh -oBatchMode=yes",
}
if opts.env then
vim.list_extend(env, opts.env)
end
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 M.kill(handle) then
killed = true
end
end)
end
handle = uv.spawn(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()
args = opts.args,
cwd = opts.cwd,
env = env,
}, function(exit_code, signal)
M.running[handle] = nil
if timeout then
timeout:stop()
timeout:close()
end
self.handle:close()
handle:close()
stdout:close()
stderr:close()
self:resume()
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"
elseif signal ~= 0 then
output = output .. "\n" .. "Process was killed with SIG" .. M.signals[signal]
end
vim.schedule(function()
opts.on_exit(exit_code == 0 and signal == 0, output)
end)
end
end)
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()
if not handle then
if opts.on_exit then
opts.on_exit(false, "Failed to spawn process " .. cmd .. " " .. vim.inspect(opts))
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()
M.running[handle] = true
---@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)
end
uv.read_start(stdout, on_output)
uv.read_start(stderr, on_output)
return handle
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
function M.kill(handle)
if handle and not handle:is_closing() then
M.running[handle] = nil
uv.process_kill(handle, "sigint")
return true
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)
end
function M.abort()
for _, proc in pairs(M.running) do
proc:kill()
for handle in pairs(M.running) do
M.kill(handle)
end
end
---@async
---@param cmd string|string[]
---@param opts? ProcessOpts
---@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())
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
@ -76,32 +73,20 @@ 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
local lines = { "# Config Change Detected. Reloading...", "" }
for _, change in ipairs(changes) do
table.insert(lines, "- **" .. change.what .. "**: `" .. vim.fn.fnamemodify(change.file, ":p:~:.") .. "`")
vim.schedule(function()
if Config.options.change_detection.notify and not Config.headless() then
local lines = { "# Config Change Detected. Reloading...", "" }
for _, change in ipairs(changes) do
table.insert(lines, "- **" .. change.what .. "**: `" .. vim.fn.fnamemodify(change.file, ":p:~:.") .. "`")
end
Util.warn(lines)
end
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 })
end)
Plugin.load()
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 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]
end
--- Update plugins
function Runner:update()
for name in pairs(self._plugins) do
self._plugins[name] = Config.plugins[name] or 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
function Runner:start()
---@async
self._running = Async.new(function()
self:_start()
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
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
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
if not entry.status.waiting and self:_resume(entry) then
running = running + 1
if self._opts.concurrency and running >= self._opts.concurrency then
break
elseif s.step < #self._pipeline then
-- next
s.step = s.step + 1
local step = self._pipeline[s.step]
if step.task == "wait" then
plugin._.working = false
waiting = waiting + 1
wait_step = s.step
break
else
s.task = self:queue(plugin, step)
plugin._.working = true
if s.task then
active = active + 1
s.task:wake(false)
break
end
end
end
end
end
end
return running > 0 or (not waiting and self:resume(true))
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)
function Runner: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
if active > 0 then
self._running:suspend()
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
---@param plugin LazyPlugin
function Runner:run_pipeline(plugin)
coroutine.yield()
for _, step in ipairs(self._pipeline) do
if step.task == "wait" then
coroutine.yield({ waiting = true })
else
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
---@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
end
rm(dir)
Util.walk(dir, function(path, _, type)
if type == "directory" then
vim.loop.fs_rmdir(path)
else
vim.loop.fs_unlink(path)
end
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,13 +29,11 @@ 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))
local info = assert(Git.info(self.plugin.dir))
local target = assert(Git.get_target(self.plugin))
if not target.commit then
for k, v in pairs(target) do
error(k .. " '" .. v .. "' not found")
@ -92,17 +41,8 @@ M.log = {
error("no target commit found")
end
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
if not Git.eq(info, target) then
self.plugin._.updates = { from = info, to = target }
end
table.insert(args, info.commit .. ".." .. target.commit)
else
@ -113,14 +53,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 +60,20 @@ 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",
"--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 +81,6 @@ M.clone = {
self.plugin._.cloned = true
self.plugin._.installed = true
self.plugin._.dirty = true
vim.uv.fs_unlink(marker)
end
end,
})
@ -189,9 +95,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,79 +113,13 @@ 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",
@ -289,18 +128,9 @@ M.fetch = {
"--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 +142,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,7 +163,7 @@ 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
self.plugin._.updated = {
@ -351,10 +179,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()
function Task:start()
if vim.in_fast_event() then
return vim.schedule(function()
self:start()
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()
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
end
function Task:render()
vim.schedule(function()
vim.api.nvim_exec_autocmds("User", { pattern = "LazyRender", modeline = false })
end)
end
function Task:headless()
if not Config.options.headless.log then
return
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
end
self._ended = vim.uv.hrtime()
self._ended = vim.loop.hrtime()
if self._opts.on_done then
self._opts.on_done(self)
end
self:render()
vim.schedule(function()
vim.api.nvim_exec_autocmds("User", {
pattern = "LazyPlugin" .. self.name:sub(1, 1):upper() .. self.name:sub(2),
data = { plugin = self.plugin.name },
})
end)
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
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
end
if opts.on_exit then
pcall(opts.on_exit, ok, proc.data)
end
return ok
table.insert(self._running, function()
return proc and not proc:is_closing()
end)
end
function Task:prefix()
local plugin = "[" .. self.plugin.name .. "] "
local task = string.rep(" ", 20 - #(self.name .. self.plugin.name)) .. self.name
---@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
end
return true
end
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,38 @@
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)))
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 +41,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

@ -6,7 +6,7 @@ local M = {}
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,
@ -21,8 +21,7 @@ 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 })
vim.cmd([[do User LazyVimStarted]])
end
function M.track(event)
@ -51,11 +50,11 @@ function M.cputime()
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
return tonumber(pnano[0].tv_sec) / 1e6 + tonumber(pnano[0].tv_nsec) / 1e6
end
local function fallback()
return (vim.uv.hrtime() - require("lazy")._start) / 1e6
return (vim.loop.hrtime() - require("lazy")._start) / 1e6
end
local ok, ret = pcall(real)

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

@ -2,101 +2,65 @@
---@alias LazyPluginKind "normal"|"clean"|"disabled"
---@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 is_local boolean
---@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 cloned? boolean
---@field kind? LazyPluginKind
---@field dep? boolean True if this plugin is only in the spec as a dependency
---@field cond? boolean
---@field super? LazyPlugin
---@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 import string spec module to import
---@field enabled? boolean|(fun():boolean)
---@field cond? boolean|(fun():boolean)
---@class LazyFragment
---@field id number
---@field pkg? boolean
---@field pid? number
---@field deps? number[]
---@field frags? number[]
---@field dep? boolean
---@field name string
---@field url? string
---@field dir? string
---@field spec LazyPlugin

View file

@ -2,42 +2,37 @@
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
function M.float(opts)
opts = opts or {}
if require("lazy.view").visible() then
opts = vim.tbl_deep_extend("force", {
zindex = 60,
border = "none",
margin = { top = 3, left = 2, right = 2 },
}, opts)
end
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
end
end
---@param opts? {system?:boolean}
function M.open(uri, opts)
opts = opts or {}
if not opts.system and M.file_exists(uri) then
function M.open(uri)
if 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
if Config.options.ui.browser then
cmd = { Config.options.ui.browser, uri }
elseif vim.fn.has("win32") == 1 then
cmd = { "explorer", uri }
elseif vim.fn.has("macunix") == 1 then
cmd = { "open", uri }
else
if vim.fn.executable("xdg-open") == 1 then
if vim.fn.executable("xdg-open") then
cmd = { "xdg-open", uri }
elseif vim.fn.executable("wslview") == 1 then
cmd = { "wslview", uri }
else
cmd = { "open", uri }
end
@ -73,55 +68,29 @@ 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
---@async
async = require("lazy.async").new(function()
repeat
pending = false
fn()
async:sleep(ms)
if not running then
if first then
wrapped()
first = false
end
until not pending
end)
end
end
timer:start(ms, 0, function()
running = false
vim.schedule(wrapped)
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
running = true
end
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
@ -138,7 +107,7 @@ function M.float_term(cmd, opts)
cmd = { cmd }
end
if #cmd == 0 then
cmd = { vim.o.shell }
cmd = { vim.env.SHELL or vim.o.shell }
end
opts = opts or {}
local float = M.float(opts)
@ -149,7 +118,7 @@ function M.float_term(cmd, opts)
once = true,
buffer = float.buf,
callback = function()
float:close({ wipe = true })
float:close()
vim.cmd.checktime()
end,
})
@ -162,21 +131,12 @@ end
---@param opts? LazyCmdOptions|{filetype?:string}
function M.float_cmd(cmd, opts)
opts = opts or {}
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 Process = require("lazy.manage.process")
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
@ -226,9 +186,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 +202,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
table.insert(result, ("[%q]="):format(k))
end
M._dump(v, result)
table.insert(result, ",")
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, ",")
i = i + 1
end
table.insert(result, "}")
else
@ -277,14 +232,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)
for _, key in ipairs(keys) do

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,27 @@ 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 +51,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 = {}
@ -34,21 +33,6 @@ M.commands = {
health = function()
vim.cmd.checkhealth("lazy")
end,
---@param opts ManagerOpts
pkg = function(opts)
local Pkg = require("lazy.pkg")
Pkg.update()
require("lazy.manage.reloader").reload({
{
file = "pkg",
what = "changed",
},
})
for _, plugin in pairs(opts and opts.plugins or {}) do
local spec = Pkg.get(plugin.dir)
Util.info(vim.inspect(spec), { lang = "lua", title = plugin.name })
end
end,
home = function()
View.show("home")
end,
@ -69,15 +53,6 @@ M.commands = {
-- 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)
end
end,
log = Manage.log,
build = Manage.build,
clean = Manage.clean,
@ -89,14 +64,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
@ -114,9 +86,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 +94,6 @@ function M.setup()
end
M.cmd(prefix, opts)
end, {
bar = true,
bang = true,
nargs = "?",
desc = "Lazy",
@ -137,7 +105,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 +114,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",
@ -34,8 +32,6 @@ M.keys = {
profile_sort = "<C-s>",
profile_filter = "<C-f>",
abort = "<C-c>",
next = "]]",
prev = "[[",
}
---@type table<string,LazyViewCommand>
@ -145,16 +141,10 @@ M.commands = {
},
build = {
desc = "Rebuild a plugin",
id = 15,
id = 13,
plugins = true,
plugins_required = true,
key_plugin = "gb",
},
reload = {
desc = "Reload a plugin (experimental!!)",
plugins = true,
plugins_required = true,
id = 16,
key_plugin = "b",
},
}

View file

@ -25,7 +25,7 @@ M.handlers = {
["diffview.nvim"] = function(plugin, diff)
local args
if diff.commit then
args = ("-C=%s"):format(plugin.dir) .. " " .. diff.commit .. "^!"
args = ("-C=%s"):format(plugin.dir) .. " " .. diff.commit
else
args = ("-C=%s"):format(plugin.dir) .. " " .. diff.from .. ".." .. diff.to
end

View file

@ -1,5 +1,4 @@
local Config = require("lazy.core.config")
local Util = require("lazy.util")
local ViewConfig = require("lazy.view.config")
---@class LazyFloatOptions
@ -10,21 +9,12 @@ local ViewConfig = require("lazy.view.config")
---@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
---@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
local M = {}
@ -34,12 +24,6 @@ setmetatable(M, {
end,
})
local _id = 0
local function next_id()
_id = _id + 1
return _id
end
---@param opts? LazyFloatOptions
function M.new(opts)
local self = setmetatable({}, { __index = M })
@ -48,13 +32,10 @@ end
---@param opts? LazyFloatOptions
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,
border = Config.options.ui.border,
zindex = 50,
}, opts or {})
@ -68,18 +49,11 @@ function M:init(opts)
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,
noautocmd = true,
}
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
@ -92,11 +66,6 @@ function M:layout()
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
if self.opts.margin then
if self.opts.margin.top then
self.win_opts.height = self.win_opts.height - self.opts.margin.top
@ -116,80 +85,33 @@ function M:layout()
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)
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, {
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"
self.buf = vim.api.nvim_create_buf(false, false)
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: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
vim.bo[self.buf].buftype = "nofile"
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()
@ -198,62 +120,40 @@ function M:mount()
---@diagnostic disable-next-line: no-unknown
config[key] = self.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 +162,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
self.buf = 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
@ -50,44 +50,26 @@ 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()
if not (self.buf and vim.api.nvim_buf_is_valid(self.buf)) then
return true
end
self:update()
end, { pattern = pattern })
end
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)
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 })
return "<c-c>"
end, { silent = true, buffer = self.buf, expr = true })
-- plugin details
self:on_key(ViewConfig.keys.details, function()
@ -97,49 +79,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 +109,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 +129,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 +149,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,24 +163,21 @@ 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.diff)
self:on_pattern(ViewConfig.commands.restore.key_plugin, {
[commit_pattern] = function(hash)
self:restore({ commit = hash })
end,
}, self.restore, "Restore")
}, self.restore)
end
---@param opts? {commit:string}
@ -248,13 +195,13 @@ function M:restore(opts)
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 +215,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 +230,6 @@ function M:diff(opts)
else
Diff.handlers[Config.options.diff.cmd](plugin, diff)
end
return true
end
end
@ -295,8 +237,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 +259,7 @@ function M:on_pattern(key, patterns, fallback, desc)
if fallback then
fallback(self)
end
end, desc)
end)
end
function M:setup_modes()
@ -335,28 +276,11 @@ function M:setup_modes()
end
if m.key_plugin and name ~= "restore" 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)
if plugin then
plugins[plugin.name] = plugin
end
end
plugins = vim.tbl_values(plugins)
else
plugins[1] = self.render:get_plugin()
local plugin = self.render:get_plugin()
if plugin then
Commands.cmd(name, { plugins = { 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")
@ -51,7 +51,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 +74,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 +84,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
@ -122,49 +109,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
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,7 +157,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("Use "):append("<C-c>", "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()
@ -199,15 +171,7 @@ function M:help()
: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("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 +199,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 +231,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 +306,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,37 +338,7 @@ 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",
@ -438,32 +367,49 @@ function M:diagnostics(plugin)
})
end
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(" ")
local reason = {}
for handler in pairs(Handler.types) do
if plugin[handler] then
for _, value in ipairs(plugin[handler]) do
reason[handler] = value
end
end
end
for _, other in pairs(Config.plugins) do
if vim.tbl_contains(other.dependencies or {}, plugin.name) then
reason.plugin = other.name
end
end
self:reason(reason)
end
self:diagnostics(plugin)
self:nl()
@ -476,22 +422,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 +430,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({
["#%d+"] = "LazyCommitIssue",
["^%S+:"] = dimmed and "Bold" or "LazyCommitType",
["^%S+(%(.*%))!?:"] = "LazyCommitScope",
["`.-`"] = "@markup.raw.markdown_inline",
["%*.-%*"] = "Italic",
["%*%*.-%*%*"] = "Bold",
})
self:append(" " .. time, "LazyComment")
self:nl()
-- else
-- self:append(line, "LazyTaskOutput", { indent = 6 }):nl()
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 })
self:append(vim.trim(msg)):highlight({
["#%d+"] = "LazyCommitIssue",
["^%S+:"] = "LazyCommitType",
["^%S+(%(.*%)):"] = "LazyCommitScope",
["`.-`"] = "@text.literal.markdown_inline",
["%*.-%*"] = "Italic",
["%*%*.-%*%*"] = "Bold",
})
-- string.gsub
self:append(" " .. time, "LazyComment")
self:nl()
end
self:nl()
end
@ -575,15 +489,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
table.insert(props, { "commit", git.commit:sub(1, 7), "LazyCommit" })
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 +504,24 @@ function M:details(plugin)
end
end)
for handler in pairs(plugin._.handlers or {}) do
table.insert(props, {
handler,
function()
self:handlers(plugin, handler)
end,
})
for handler in pairs(Handler.types) do
if plugin[handler] then
table.insert(props, {
handler,
function()
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}
@ -655,7 +550,7 @@ function M:profile()
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()
@ -685,12 +580,12 @@ function M:profile()
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 +650,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 +667,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" }
end
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
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,14 +63,14 @@ return {
{
---@param plugin LazyPlugin
filter = function(plugin)
return plugin._.updates ~= nil
return plugin._.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",
@ -95,13 +89,7 @@ return {
},
{
filter = function(plugin)
return plugin._.outdated
end,
title = "Outdated",
},
{
filter = function(plugin)
return plugin._.loaded ~= nil
return plugin._.loaded
end,
title = "Loaded",
},

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,32 +73,30 @@ 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
local col = self.padding
for _, segment in ipairs(line) do
local width = vim.fn.strlen(segment.str)
for _, segment in ipairs(line) do
local width = vim.fn.strlen(segment.str)
local extmark = segment.hl
if extmark then
if type(extmark) == "string" then
extmark = { hl_group = extmark, end_col = col + width }
end
---@cast extmark Extmark
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)
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 })
)
end
local extmark = segment.hl
if extmark then
if type(extmark) == "string" then
extmark = { hl_group = extmark, end_col = col + width }
end
---@cast extmark Extmark
col = col + width
local extmark_col = extmark.col or col
extmark.col = nil
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 })
)
end
end
col = col + width
end
end
end

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
column_width = 120

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.notifs == 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
@ -158,7 +45,6 @@ describe("plugin spec opt", function()
local spec = Plugin.Spec.new(vim.deepcopy(test))
assert(#spec.notifs == 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,27 +75,25 @@ 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)
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()
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
local spec = Plugin.Spec.new(vim.deepcopy(test))
assert(#spec.notifs == 0)
Config.plugins = spec.plugins
@ -216,74 +102,31 @@ 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",
_ = {},
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
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
})
end
end)
it("handles opt from dep", function()
Config.options.defaults.lazy = false
@ -339,13 +182,10 @@ describe("plugin spec opt", function()
local spec = Plugin.Spec.new(test)
assert(#spec.notifs == 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"))
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)
end)
@ -401,16 +241,13 @@ describe("plugin spec opt", function()
{ { "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"])
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)
@ -423,6 +260,7 @@ describe("plugin spec opt", function()
}
for test, ret in pairs(tests) do
local spec = Plugin.Spec.new(test)
spec:fix_disabled()
assert(#spec.notifs == 0)
if ret then
assert(spec.plugins.bar)
@ -433,94 +271,4 @@ describe("plugin spec opt", function()
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 } } },
{
"foo/bar",
opts = { key = true },
},
},
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"))
end
end)
end)

View file

@ -1,11 +1,10 @@
local Util = require("lazy.util")
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
@ -13,7 +12,7 @@ describe("util", function()
end
end
Helpers.fs_rm("")
assert(not vim.uv.fs_stat(Helpers.path("")), "fs root should be deleted")
assert(not vim.loop.fs_stat(Helpers.path("")), "fs root should be deleted")
end)
it("lsmod lists all mods in dir", function()
@ -52,8 +51,10 @@ describe("util", function()
local files = Helpers.fs_create(test.files)
-- test with empty cache
Cache.reset()
local root = Util.find_root(test.mod)
Cache.cache = {}
Cache.indexed = {}
Cache.indexed_rtp = false
local root = Cache.find_root(test.mod)
assert(root, "no root found for " .. test.mod .. " (test " .. t .. ")")
assert.same(Helpers.path(test.root), root)
local mods = {}
@ -64,8 +65,13 @@ describe("util", function()
assert.same(expected, mods)
-- fill the cache
Cache.reset()
root = Util.find_root(test.mod)
Cache.cache = {}
for i, file in ipairs(files) do
Cache.cache[test.mods[i]] = { modpath = file }
end
Cache.indexed = {}
Cache.indexed_rtp = false
root = Cache.find_root(test.mod)
assert(root, "no root found for " .. test.mod .. " (test " .. t .. ")")
assert.same(Helpers.path(test.root), root)
mods = {}
@ -78,70 +84,52 @@ describe("util", function()
end)
it("find the correct root with dels", function()
Cache.reset()
Cache.cache = {}
Cache.indexed = {}
Cache.indexed_rtp = false
vim.opt.rtp:append(Helpers.path("old"))
Helpers.fs_create({ "old/lua/foobar/init.lua" })
local root = Util.find_root("foobar")
Cache.cache["foobar"] = { modpath = Helpers.path("old/lua/foobar/init.lua") }
local root = Cache.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")
assert(not vim.loop.fs_stat(Helpers.path("old/lua/foobar")), "old/lua/foobar should not exist")
-- vim.opt.rtp = rtp
Cache.indexed = {}
Cache.indexed_rtp = false
vim.opt.rtp:append(Helpers.path("new"))
Helpers.fs_create({ "new/lua/foobar/init.lua" })
root = Util.find_root("foobar")
root = Cache.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 },
},
}
it("find the correct root with mod dels", function()
Cache.cache = {}
Cache.indexed = {}
Cache.indexed_rtp = false
Cache.enabled = true
vim.opt.rtp:append(Helpers.path("old"))
Helpers.fs_create({ "old/lua/foobar/test.lua" })
Cache.cache["foobar.test"] = { modpath = Helpers.path("old/lua/foobar/test.lua") }
local root = Cache.find_root("foobar")
assert(root, "foobar root not found")
assert.same(Helpers.path("old/lua/foobar"), root)
assert(not Cache.cache["foobar"], "foobar should not be in cache")
assert(Cache.cache["foobar.test"], "foobar.test not found in cache")
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
Helpers.fs_rm("old")
-- vim.opt.rtp = rtp
Cache.indexed = {}
Cache.indexed_rtp = false
vim.opt.rtp:append(Helpers.path("new"))
Helpers.fs_create({ "new/lua/foobar/test.lua" })
root = Cache.find_root("foobar")
assert(root, "foobar root not found")
assert.same(Helpers.path("new/lua/foobar"), root)
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

@ -26,12 +26,12 @@ 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)
vim.loop.fs_rmdir(path)
else
vim.uv.fs_unlink(path)
vim.loop.fs_unlink(path)
end
end)
vim.uv.fs_rmdir(dir)
vim.loop.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()
table.insert(runs, { plugin = task.plugin.name, task = task.name })
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,96 @@
--# 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
---@type string?
local error
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()
running = false
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 +98,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(task.output == "foo\nbar\n" or task.output == "bar\nfoo\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"