Compare commits

..

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

96 changed files with 3502 additions and 128234 deletions

11
.busted
View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,15 +1,72 @@
name: CI name: CI
on: on:
push: push:
branches: [main, master]
pull_request: pull_request:
jobs: jobs:
ci: tests:
uses: folke/github/.github/workflows/ci.yml@main strategy:
secrets: inherit matrix:
# os: [ubuntu-latest, windows-latest]
os: [ubuntu-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v3
- name: Install Neovim
shell: bash
run: |
if [ "$RUNNER_OS" == "Linux" ]; then
wget -q https://github.com/neovim/neovim/releases/download/nightly/nvim-linux64.deb -O /tmp/nvim.deb
sudo dpkg -i /tmp/nvim.deb
else
choco install neovim --pre
echo "C:/tools/neovim/nvim-win64/bin" >> $GITHUB_PATH
fi
- name: Run Tests
run: |
nvim --version
nvim --headless -u tests/init.lua -c "PlenaryBustedDirectory tests// {minimal_init = 'tests//init.lua', sequential = true}"
docs:
runs-on: ubuntu-latest
needs: tests
if: ${{ github.ref == 'refs/heads/main' }}
steps:
- uses: actions/checkout@v3
- name: panvimdoc
uses: kdheepak/panvimdoc@main
with: with:
plugin: lazy.nvim vimdoc: lazy.nvim
repo: folke/lazy.nvim version: "Neovim >= 0.8.0"
docs: false 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

13
.gitignore vendored
View file

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

View file

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

View file

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

File diff suppressed because it is too large Load diff

772
README.md
View file

@ -1,36 +1,4 @@
<h4 align="center"> # 💤 lazy.nvim
<a href="https://lazy.folke.io/installation">Install</a>
·
<a href="https://lazy.folke.io/configuration">Configure</a>
·
<a href="https://lazy.folke.io">Docs</a>
</h4>
<div align="center"><p>
<a href="https://github.com/folke/lazy.nvim/releases/latest">
<img alt="Latest release" src="https://img.shields.io/github/v/release/folke/lazy.nvim?style=for-the-badge&logo=starship&color=C9CBFF&logoColor=D9E0EE&labelColor=302D41&include_prerelease&sort=semver" />
</a>
<a href="https://github.com/folke/lazy.nvim/pulse">
<img alt="Last commit" src="https://img.shields.io/github/last-commit/folke/lazy.nvim?style=for-the-badge&logo=starship&color=8bd5ca&logoColor=D9E0EE&labelColor=302D41"/>
</a>
<a href="https://github.com/folke/lazy.nvim/blob/main/LICENSE">
<img alt="License" src="https://img.shields.io/github/license/folke/lazy.nvim?style=for-the-badge&logo=starship&color=ee999f&logoColor=D9E0EE&labelColor=302D41" />
</a>
<a href="https://github.com/folke/lazy.nvim/stargazers">
<img alt="Stars" src="https://img.shields.io/github/stars/folke/lazy.nvim?style=for-the-badge&logo=starship&color=c69ff5&logoColor=D9E0EE&labelColor=302D41" />
</a>
<a href="https://github.com/folke/lazy.nvim/issues">
<img alt="Issues" src="https://img.shields.io/github/issues/folke/lazy.nvim?style=for-the-badge&logo=bilibili&color=F5E0DC&logoColor=D9E0EE&labelColor=302D41" />
</a>
<a href="https://github.com/folke/lazy.nvim">
<img alt="Repo Size" src="https://img.shields.io/github/repo-size/folke/lazy.nvim?color=%23DDB6F2&label=SIZE&logo=codesandbox&style=for-the-badge&logoColor=D9E0EE&labelColor=302D41" />
</a>
<a href="https://twitter.com/intent/follow?screen_name=folke">
<img alt="follow on Twitter" src="https://img.shields.io/twitter/follow/folke?style=for-the-badge&logo=twitter&color=8aadf3&logoColor=D9E0EE&labelColor=302D41" />
</a>
</div>
**lazy.nvim** is a modern plugin manager for Neovim. **lazy.nvim** is a modern plugin manager for Neovim.
@ -39,9 +7,9 @@
## ✨ Features ## ✨ Features
- 📦 Manage all your Neovim plugins with a powerful UI - 📦 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 - 💾 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 - ⏳ Automatically install missing plugins before starting up Neovim, allowing you to start using it right away
- 💪 Async execution for improved performance - 💪 Async execution for improved performance
- 🛠️ No need to manually compile plugins - 🛠️ No need to manually compile plugins
@ -61,9 +29,735 @@
- Neovim >= **0.8.0** (needs to be built with **LuaJIT**) - Neovim >= **0.8.0** (needs to be built with **LuaJIT**)
- Git >= **2.19.0** (for partial clones support) - Git >= **2.19.0** (for partial clones support)
- a [Nerd Font](https://www.nerdfonts.com/) **_(optional)_** - 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
},
lockfile = vim.fn.stdpath("config") .. "/lazy-lock.json", -- lockfile generated after running update.
concurrency = nil, ---@type number limit the maximum amount of concurrent tasks
git = {
-- defaults for the `Lazy log` command
-- log = { "-10" }, -- show the last 10 commits
log = { "--since=3 days ago" }, -- show commits from the last 3 days
timeout = 120, -- kill processes that take more than 2 minutes
url_format = "https://github.com/%s.git",
},
dev = {
-- directory where you store your local plugin projects
path = "~/projects",
---@type string[] plugins that match these patterns will use your local versions instead of being fetched from GitHub
patterns = {}, -- For example {"folke"}
},
install = {
-- install missing plugins on startup. This doesn't increase startup time.
missing = true,
-- try to load one of these colorschemes when starting an installation during startup
colorscheme = { "habamax" },
},
ui = {
-- a number <1 is a percentage., >1 is a fixed size
size = { width = 0.8, height = 0.8 },
-- The border to use for the UI window. Accepts same border values as |nvim_open_win()|.
border = "none",
icons = {
loaded = "●",
not_loaded = "○",
cmd = " ",
config = "",
event = "",
ft = " ",
init = " ",
keys = " ",
plugin = " ",
runtime = " ",
source = " ",
start = "",
task = "✔ ",
lazy = "鈴 ",
list = {
"●",
"➜",
"★",
"",
},
},
-- 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").open_cmd({ "lazygit", "log" }, {
cwd = plugin.dir,
terminal = true,
close_on_exit = true,
enter = true,
})
end,
-- open a terminal for the plugin dir
["<localleader>t"] = function(plugin)
require("lazy.util").open_cmd({ vim.go.shell }, {
cwd = plugin.dir,
terminal = true,
close_on_exit = true,
enter = true,
})
end,
},
},
diff = {
-- diff command <d> can be one of:
-- * browser: opens the github compare view. Note that this is always mapped to <K> as well,
-- so you can have a different command for diff <d>
-- * git: will run git diff and open a buffer with filetype git
-- * terminal_git: will open a pseudo terminal with git diff
-- * diffview.nvim: will open Diffview to show the diff
cmd = "git",
},
checker = {
-- automatically check for plugin updates
enabled = false,
concurrency = nil, ---@type number? set to 1 to check for updates very slowly
notify = true, -- get a notification when new updates are found
frequency = 3600, -- check for updates every hour
},
change_detection = {
-- automatically check for config file changes and reload the ui
enabled = true,
notify = true, -- get a notification when changes are found
},
performance = {
cache = {
enabled = true,
path = vim.fn.stdpath("cache") .. "/lazy/cache",
-- Once one of the following events triggers, caching will be disabled.
-- To cache all modules, set this to `{}`, but that is not recommended.
-- The default is to disable on:
-- * VimEnter: not useful to cache anything else beyond startup
-- * BufReadPre: this will be triggered early when opening a file from the command line directly
disable_events = { "VimEnter", "BufReadPre" },
ttl = 3600 * 24 * 5, -- keep unused modules for up to 5 days
},
reset_packpath = true, -- reset the package path to improve startup time
rtp = {
reset = true, -- reset the runtime path to $VIMRUNTIME and your config directory
---@type string[]
paths = {}, -- add any custom paths here that you want to indluce in the rtp
---@type string[] list any plugins you want to disable here
disabled_plugins = {
-- "gzip",
-- "matchit",
-- "matchparen",
-- "netrwPlugin",
-- "tarPlugin",
-- "tohtml",
-- "tutor",
-- "zipPlugin",
},
},
},
-- lazy can generate helptags from the headings in markdown readme files,
-- so :help works even for plugins that don't have vim docs.
-- when the readme opens with :help it will be correctly displayed as markdown
readme = {
root = vim.fn.stdpath("state") .. "/lazy/readme",
files = { "README.md", "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` |
| `: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.
startuptime_cputime = false,
count = 0, -- total number of plugins
loaded = 0, -- number of loaded plugins
}
```
<!-- stats:end -->
**lazy.nvim** provides a statusline component that you can use to show the number of pending updates.
Make sure to enable `config.checker.enabled = true` to make this work.
<details>
<summary>Example of configuring <a href="https://github.com/nvim-lualine/lualine.nvim">lualine.nvim</a></summary>
```lua
require("lualine").setup({
sections = {
lualine_x = {
{
require("lazy.status").updates,
cond = require("lazy.status").has_updates,
color = { fg = "#ff9e64" },
},
},
},
})
```
</details>
### 📆 User Events
The following user events will be triggered:
- **LazyDone**: when lazy has finished starting up and loaded your config
- **LazySync**: after running sync
- **LazyInstall**: after an install
- **LazyUpdate**: after an update
- **LazyClean**: after a clean
- **LazyCheck**: after checking for updates
- **LazyLog**: after running log
- **LazyReload**: triggered by change detection after reloading plugin specs
- **VeryLazy**: triggered after `LazyDone` and processing `VimEnter` auto commands
- **LazyVimStarted**: triggered after `UIEnter` when `require("lazy").stats().startuptime` has been calculated.
Useful to update the startuptime on your dashboard.
## 🔒 Lockfile `lazy-lock.json`
After every **update**, the local lockfile is updated with the installed revisions.
It is recommended to have this file under version control.
If you use your Neovim config on multiple machines, using the lockfile, you can
ensure that the same version of every plugin is installed.
If you are on another machine, you can do `:Lazy restore`, to update all your plugins to
the version from the lockfile.
## ⚡ Performance
Great care has been taken to make the startup code (`lazy.core`) as efficient as possible.
During startup, all Lua files used before `VimEnter` or `BufReadPre` are byte-compiled and cached,
similar to what [impatient.nvim](https://github.com/lewis6991/impatient.nvim) does.
My config for example loads in about `11ms` with `93` plugins. I do a lot of lazy-loading though :)
**lazy.nvim** comes with an advanced profiler `:Lazy profile` to help you improve performance.
The profiling view shows you why and how long it took to load your plugins.
![image](https://user-images.githubusercontent.com/292349/208301766-5c400561-83c3-4811-9667-1ec4bb3c43b8.png)
## 🪲 Debug
See an overview of active lazy-loading handlers and what's in the module cache
![image](https://user-images.githubusercontent.com/292349/208301790-7eedbfa5-d202-4e70-852e-de68aa47233b.png)
## ▶️ Startup Sequence
**lazy.nvim** does **NOT** use Neovim packages and even disables plugin loading
completely (`vim.go.loadplugins = false`). It takes over the complete
startup sequence for more flexibility and better performance.
In practice this means that step 10 of [Neovim Initialization](https://neovim.io/doc/user/starting.html#initialization) is done by Lazy:
1. all the plugins' `init()` functions are executed
2. all plugins with `lazy=false` are loaded. This includes sourcing `/plugin` and `/ftdetect` files. (`/after` will not be sourced yet)
3. all files from `/plugin` and `/ftdetect` directories in you rtp are sourced (excluding `/after`)
4. all `/after/plugin` files are sourced (this inludes `/after` from plugins)
Files from runtime directories are always sourced in alphabetical order.
## 📂 Structuring Your Plugins
Some users may want to split their plugin specs in multiple files.
Instead of passing a spec table to `setup()`, you can use a Lua module.
The specs from the **module** and any top-level **sub-modules** will be merged together in the final spec,
so it is not needed to add `require` calls in your main plugin file to the other files.
The benefits of using this approach:
- simple to **add** new plugin specs. Just create a new file in your plugins module.
- allows for **caching** of all your plugin specs. This becomes important if you have a lot of smaller plugin specs.
- spec changes will automatically be **reloaded** when they're updated, so the `:Lazy` UI is always up to date
Example:
- `~/.config/nvim/init.lua`
```lua
require("lazy").setup("plugins")
```
- `~/.config/nvim/lua/plugins.lua` or `~/.config/nvim/lua/plugins/init.lua` **_(this file is optional)_**
```lua
return {
"folke/neodev.nvim",
"folke/which-key.nvim",
{ "folke/neoconf.nvim", cmd = "Neoconf" },
}
```
- any lua file in `~/.config/nvim/lua/plugins/*.lua` will be automatically merged in the main plugin spec
For a real-life example, you can check my personal dots:
- [init.lua](https://github.com/folke/dot/blob/master/config/nvim/init.lua) where I require `config.lazy`
- [config.lazy](https://github.com/folke/dot/blob/master/config/nvim/lua/config/lazy.lua) where I bootstrap and setup **lazy.nvim**
- [config.plugins](https://github.com/folke/dot/blob/master/config/nvim/lua/plugins/init.lua) is my main plugin config module
- Any submodule of [config.plugins (submodules)](https://github.com/folke/dot/tree/master/config/nvim/lua/plugins) will be automatically loaded as well.
## 📦 Migration Guide
### [packer.nvim](https://github.com/wbthomason/packer.nvim)
- `setup` ➡️ `init`
- `requires` ➡️ `dependencies`
- `as` ➡️ `name`
- `opt` ➡️ `lazy`
- `run` ➡️ `build`
- `lock` ➡️ `pin`
- `disable=true` ➡️ `enabled = false`
- `tag='*'` ➡️ `version="*"`
- `after` **_not needed_** for most use-cases. Use `dependencies` otherwise.
- `wants` **_not needed_** for most use-cases. Use `dependencies` otherwise.
- `config` don't support string type, use `fun(LazyPlugin)` instead.
- `module` is auto-loaded. No need to specify
- `keys` spec is [different](#%EF%B8%8F-lazy-key-mappings)
- `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_** | |
| **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 # ✅ 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] fancy UI to manage all your Neovim plugins
- [x] auto lazy-loading of lua modules - [x] auto lazy-loading of lua modules
- [x] lazy-loading on events, commands, filetypes and key mappings - [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` - [ ] 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 - [ ] 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) - [ ] package meta index (package.lua cache for all packages)
- [x] document highlight groups - [x] document highlight groups
@ -65,7 +55,7 @@
Maybe a quick, "for example, if you have a lua file 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 `~/.config/nvim/lua/config/plugins.lua` that returns a table" or something it'd
remove most question marks I think. 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. window, but on the non-floating window in the background.
- [x] Doing `:Lazy clean` doesn't show which plugins were removed. - [x] Doing `:Lazy clean` doesn't show which plugins were removed.
- [x] Shouldn't the "Versioning" section be in the "Lockfile" chapter? - [x] Shouldn't the "Versioning" section be in the "Lockfile" chapter?

View file

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

View file

File diff suppressed because it is too large Load diff

View file

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

View file

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

File diff suppressed because it is too large Load diff

View file

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

View file

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

View file

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

View file

@ -7,73 +7,26 @@ local M = {}
M.defaults = { M.defaults = {
root = vim.fn.stdpath("data") .. "/lazy", -- directory where plugins will be installed root = vim.fn.stdpath("data") .. "/lazy", -- directory where plugins will be installed
defaults = { 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? lazy = false, -- should plugins be lazy-loaded?
-- It's recommended to leave version=false for now, since a lot the plugin that support versioning, version = nil,
-- have outdated releases, which may break your Neovim install. -- version = "*", -- enable this to try installing the latest stable versions of plugins
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
}, },
-- leave nil when passing the spec as the first argument to setup() -- leave nil when passing the spec as the first argument to setup()
spec = nil, ---@type LazySpec 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. lockfile = vim.fn.stdpath("config") .. "/lazy-lock.json", -- lockfile generated after running update.
---@type number? limit the maximum amount of concurrent tasks concurrency = nil, ---@type number limit the maximum amount of concurrent tasks
concurrency = jit.os:find("Windows") and (vim.uv.available_parallelism() * 2) or nil,
git = { git = {
-- defaults for the `Lazy log` command -- defaults for the `Lazy log` command
-- log = { "--since=3 days ago" }, -- show commits from the last 3 days -- log = { "-10" }, -- show the last 10 commits
log = { "-8" }, -- show the last 8 commits log = { "--since=3 days ago" }, -- show commits from the last 3 days
timeout = 120, -- kill processes that take more than 2 minutes timeout = 120, -- kill processes that take more than 2 minutes
url_format = "https://github.com/%s.git", 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 = { dev = {
-- Directory where you store your local plugin projects. If a function is used, -- directory where you store your local plugin projects
-- the plugin directory (e.g. `~/projects/plugin-name`) must be returned.
---@type string | fun(plugin: LazyPlugin): string
path = "~/projects", path = "~/projects",
---@type string[] plugins that match these patterns will use your local versions instead of being fetched from GitHub ---@type string[] plugins that match these patterns will use your local versions instead of being fetched from GitHub
patterns = {}, -- For example {"folke"} patterns = {}, -- For example {"folke"}
fallback = false, -- Fallback to git when local plugin doesn't exist
}, },
install = { install = {
-- install missing plugins on startup. This doesn't increase startup time. -- install missing plugins on startup. This doesn't increase startup time.
@ -84,31 +37,21 @@ M.defaults = {
ui = { ui = {
-- a number <1 is a percentage., >1 is a fixed size -- a number <1 is a percentage., >1 is a fixed size
size = { width = 0.8, height = 0.8 }, 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()|. -- The border to use for the UI window. Accepts same border values as |nvim_open_win()|.
border = "none", 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 = { icons = {
cmd = "", cmd = "",
config = "", config = "",
debug = "",
event = "", event = "",
favorite = "",
ft = "", ft = "",
init = "", init = "",
import = "", import = "",
keys = "", keys = "",
lazy = "󰒲 ", lazy = " ",
loaded = "", loaded = "",
not_loaded = "", not_loaded = "",
plugin = "", plugin = "",
runtime = "", runtime = "",
require = "󰢱 ",
source = "", source = "",
start = "", start = "",
task = "", task = "",
@ -122,52 +65,32 @@ M.defaults = {
-- leave nil, to automatically select a browser depending on your OS. -- leave nil, to automatically select a browser depending on your OS.
-- If you want to use a specific browser, you can define it here -- If you want to use a specific browser, you can define it here
browser = nil, ---@type string? 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 = { custom_keys = {
-- You can define custom key maps here. If present, the description will -- you can define custom key maps here.
-- be shown in the help menu. -- To disable one of the defaults, set it to false
-- To disable one of the defaults, set it to false.
["<localleader>l"] = { -- open lazygit log
function(plugin) ["<localleader>l"] = function(plugin)
require("lazy.util").float_term({ "lazygit", "log" }, { require("lazy.util").open_cmd({ "lazygit", "log" }, {
cwd = plugin.dir, cwd = plugin.dir,
terminal = true,
close_on_exit = true,
enter = true,
}) })
end, end,
desc = "Open lazygit log",
},
["<localleader>i"] = { -- open a terminal for the plugin dir
function(plugin) ["<localleader>t"] = function(plugin)
Util.notify(vim.inspect(plugin), { require("lazy.util").open_cmd({ vim.go.shell }, {
title = "Inspect " .. plugin.name,
lang = "lua",
})
end,
desc = "Inspect Plugin",
},
["<localleader>t"] = {
function(plugin)
require("lazy.util").float_term(nil, {
cwd = plugin.dir, cwd = plugin.dir,
terminal = true,
close_on_exit = true,
enter = true,
}) })
end, end,
desc = "Open terminal in plugin dir",
}, },
}, },
},
-- Output options for headless mode
headless = {
-- show the output from process commands like git
process = true,
-- show log messages
log = true,
-- show task start/end
task = true,
-- use ansi colors
colors = true,
},
diff = { diff = {
-- diff command <d> can be one of: -- diff command <d> can be one of:
-- * browser: opens the github compare view. Note that this is always mapped to <K> as well, -- * browser: opens the github compare view. Note that this is always mapped to <K> as well,
@ -183,7 +106,6 @@ M.defaults = {
concurrency = nil, ---@type number? set to 1 to check for updates very slowly concurrency = nil, ---@type number? set to 1 to check for updates very slowly
notify = true, -- get a notification when new updates are found notify = true, -- get a notification when new updates are found
frequency = 3600, -- check for updates every hour frequency = 3600, -- check for updates every hour
check_pinned = false, -- check for pinned packages that can't be updated
}, },
change_detection = { change_detection = {
-- automatically check for config file changes and reload the ui -- automatically check for config file changes and reload the ui
@ -191,14 +113,13 @@ M.defaults = {
notify = true, -- get a notification when changes are found notify = true, -- get a notification when changes are found
}, },
performance = { performance = {
cache = { ---@type LazyCacheConfig
enabled = true, cache = nil,
},
reset_packpath = true, -- reset the package path to improve startup time reset_packpath = true, -- reset the package path to improve startup time
rtp = { rtp = {
reset = true, -- reset the runtime path to $VIMRUNTIME and your config directory reset = true, -- reset the runtime path to $VIMRUNTIME and your config directory
---@type string[] ---@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 ---@type string[] list any plugins you want to disable here
disabled_plugins = { disabled_plugins = {
-- "gzip", -- "gzip",
@ -216,34 +137,14 @@ M.defaults = {
-- so :help works even for plugins that don't have vim docs. -- 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 -- when the readme opens with :help it will be correctly displayed as markdown
readme = { readme = {
enabled = true,
root = vim.fn.stdpath("state") .. "/lazy/readme", root = vim.fn.stdpath("state") .. "/lazy/readme",
files = { "README.md", "lua/**/README.md" }, 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, 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, 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") M.ns = vim.api.nvim_create_namespace("lazy")
---@type LazySpecLoader ---@type LazySpecLoader
@ -264,14 +165,7 @@ M.me = nil
---@type string ---@type string
M.mapleader = nil M.mapleader = nil
---@type string M.headless = #vim.api.nvim_list_uis() == 0
M.maplocalleader = nil
M.suspended = false
function M.headless()
return not M.suspended and #vim.api.nvim_list_uis() == 0
end
---@param opts? LazyConfig ---@param opts? LazyConfig
function M.setup(opts) function M.setup(opts)
@ -280,35 +174,26 @@ function M.setup(opts)
if type(M.options.spec) == "string" then if type(M.options.spec) == "string" then
M.options.spec = { import = M.options.spec } M.options.spec = { import = M.options.spec }
end end
M.options.performance.cache = require("lazy.core.cache").config
table.insert(M.options.install.colorscheme, "habamax") table.insert(M.options.install.colorscheme, "habamax")
-- root
M.options.root = Util.norm(M.options.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) M.options.dev.path = Util.norm(M.options.dev.path)
end
M.options.lockfile = Util.norm(M.options.lockfile) M.options.lockfile = Util.norm(M.options.lockfile)
M.options.readme.root = Util.norm(M.options.readme.root) M.options.readme.root = Util.norm(M.options.readme.root)
vim.fn.mkdir(M.options.root, "p")
if M.options.performance.reset_packpath then if M.options.performance.reset_packpath then
vim.go.packpath = vim.env.VIMRUNTIME vim.go.packpath = vim.env.VIMRUNTIME
end end
M.me = debug.getinfo(1, "S").source:sub(2) M.me = debug.getinfo(1, "S").source:sub(2)
M.me = Util.norm(vim.fn.fnamemodify(M.me, ":p:h:h:h:h")) 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 if M.options.performance.rtp.reset then
---@type vim.Option
vim.opt.rtp = { vim.opt.rtp = {
vim.fn.stdpath("config"), vim.fn.stdpath("config"),
vim.fn.stdpath("data") .. "/site",
M.me, M.me,
vim.env.VIMRUNTIME, vim.env.VIMRUNTIME,
lib, vim.fn.fnamemodify(vim.v.progpath, ":p:h:h") .. "/lib/nvim",
vim.fn.stdpath("config") .. "/after", vim.fn.stdpath("config") .. "/after",
} }
end end
@ -320,22 +205,21 @@ function M.setup(opts)
-- disable plugin loading since we do all of that ourselves -- disable plugin loading since we do all of that ourselves
vim.go.loadplugins = false vim.go.loadplugins = false
M.mapleader = vim.g.mapleader M.mapleader = vim.g.mapleader
M.maplocalleader = vim.g.maplocalleader
if M.headless then
require("lazy.view.commands").setup()
else
vim.api.nvim_create_autocmd("UIEnter", { vim.api.nvim_create_autocmd("UIEnter", {
once = true,
callback = function() callback = function()
require("lazy.stats").on_ui_enter() require("lazy.stats").on_ui_enter()
end, end,
}) })
if M.headless() then
require("lazy.view.commands").setup()
else
vim.api.nvim_create_autocmd("User", { vim.api.nvim_create_autocmd("User", {
pattern = "VeryLazy", pattern = "VeryLazy",
once = true, once = true,
callback = function() callback = function()
require("lazy.core.cache").autosave()
require("lazy.view.commands").setup() require("lazy.view.commands").setup()
if M.options.change_detection.enabled then if M.options.change_detection.enabled then
require("lazy.manage.reloader").enable() require("lazy.manage.reloader").enable()
@ -345,23 +229,6 @@ function M.setup(opts)
require("lazy.manage.checker").start() require("lazy.manage.checker").start()
end, 10) end, 10)
end 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,
}) })
end end

View file

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

View file

@ -1,5 +1,5 @@
local Loader = require("lazy.core.loader")
local Util = require("lazy.core.util") local Util = require("lazy.core.util")
local Loader = require("lazy.core.loader")
---@class LazyCmdHandler:LazyHandler ---@class LazyCmdHandler:LazyHandler
local M = {} local M = {}
@ -14,38 +14,16 @@ end
---@param cmd string ---@param cmd string
function M:_add(cmd) function M:_add(cmd)
vim.api.nvim_create_user_command(cmd, function(event) 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) self:_load(cmd)
vim.cmd(
local info = vim.api.nvim_get_commands({})[cmd] or vim.api.nvim_buf_get_commands(0, {})[cmd] ("%s %s%s%s %s"):format(
if not info then event.mods or "",
vim.schedule(function() event.line1 == event.line2 and "" or event.line1 .. "," .. event.line2,
Util.error("Command `" .. cmd .. "` not found after loading " .. plugins) cmd,
end) event.bang and "!" or "",
return event.args or ""
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)
end, { end, {
bang = true, bang = true,
range = true, range = true,

View file

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

View file

@ -1,10 +1,17 @@
local Event = require("lazy.core.handler.event") local Event = require("lazy.core.handler.event")
local Util = require("lazy.core.util")
local Loader = require("lazy.core.loader") local Loader = require("lazy.core.loader")
local Config = require("lazy.core.config")
---@class LazyFiletypeHandler:LazyEventHandler ---@class LazyFiletypeHandler:LazyEventHandler
local M = {} local M = {}
M.extends = Event M.extends = Event
---@param value string
function M:_event(value)
return "FileType " .. value
end
---@param plugin LazyPlugin ---@param plugin LazyPlugin
function M:add(plugin) function M:add(plugin)
self.super.add(self, plugin) self.super.add(self, plugin)
@ -13,13 +20,21 @@ function M:add(plugin)
end end
end end
---@return LazyEvent ---@param pattern? string
function M:_parse(value) function M:trigger(_, pattern, _)
return { for _, group in ipairs({ "filetypeplugin", "filetypeindent" }) do
id = value, Util.try(function()
event = "FileType", if Config.options.debug then
pattern = value, 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 end
return M return M

View file

@ -1,11 +1,10 @@
local Config = require("lazy.core.config")
local Util = require("lazy.core.util") local Util = require("lazy.core.util")
local Config = require("lazy.core.config")
---@class LazyHandler ---@class LazyHandler
---@field type LazyHandlerTypes ---@field type LazyHandlerTypes
---@field extends? LazyHandler ---@field extends? LazyHandler
---@field active table<string,table<string,string>> ---@field active table<string,table<string,string>>
---@field managed table<string,string> mapping handler keys to plugin names
---@field super LazyHandler ---@field super LazyHandler
local M = {} local M = {}
@ -20,16 +19,10 @@ M.types = {
---@type table<string,LazyHandler> ---@type table<string,LazyHandler>
M.handlers = {} M.handlers = {}
M.did_setup = false function M.setup()
function M.init()
for _, type in pairs(M.types) do for _, type in pairs(M.types) do
M.handlers[type] = M.new(type) M.handlers[type] = M.new(type)
end end
end
function M.setup()
M.did_setup = true
for _, plugin in pairs(Config.plugins) do for _, plugin in pairs(Config.plugins) do
Util.try(function() Util.try(function()
M.enable(plugin) M.enable(plugin)
@ -39,19 +32,20 @@ end
---@param plugin LazyPlugin ---@param plugin LazyPlugin
function M.disable(plugin) function M.disable(plugin)
for type in pairs(plugin._.handlers or {}) do for type, handler in pairs(M.handlers) do
M.handlers[type]:del(plugin) if plugin[type] then
handler:del(plugin)
end
end end
end end
---@param plugin LazyPlugin ---@param plugin LazyPlugin
function M.enable(plugin) function M.enable(plugin)
if not plugin._.loaded then if not plugin._.loaded then
if not plugin._.handlers then for type, handler in pairs(M.handlers) do
M.resolve(plugin) if plugin[type] then
handler:add(plugin)
end end
for type in pairs(plugin._.handlers or {}) do
M.handlers[type]:add(plugin)
end end
end end
end end
@ -64,57 +58,30 @@ function M.new(type)
local self = setmetatable({}, { __index = setmetatable(handler, { __index = super }) }) local self = setmetatable({}, { __index = setmetatable(handler, { __index = super }) })
self.super = super self.super = super
self.active = {} self.active = {}
self.managed = {}
self.type = type self.type = type
return self return self
end end
---@param _value string ---@param value string
---@protected ---@protected
function M:_add(_value) end function M:_add(value) end
---@param _value string ---@param value string
---@protected ---@protected
function M:_del(_value) end function M:_del(value) end
---@param value any ---@return string
---@param _plugin LazyPlugin function M:key(value)
---@return string|{id:string}
function M:_parse(value, _plugin)
assert(type(value) == "string", "Expected string, got " .. vim.inspect(value))
return value return value
end 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 ---@param plugin LazyPlugin
function M:add(plugin) 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 if not self.active[key] then
self.active[key] = {} self.active[key] = {}
self:_add(value) self:_add(value)
self.managed[key] = plugin.name
end end
self.active[key][plugin.name] = plugin.name self.active[key][plugin.name] = plugin.name
end end
@ -122,10 +89,8 @@ end
---@param plugin LazyPlugin ---@param plugin LazyPlugin
function M:del(plugin) function M:del(plugin)
if not plugin._.handlers then for _, value in ipairs(plugin[self.type] or {}) do
return local key = self:key(value)
end
for key, value in pairs(plugin._.handlers[self.type] or {}) do
if self.active[key] and self.active[key][plugin.name] then if self.active[key] and self.active[key][plugin.name] then
self.active[key][plugin.name] = nil self.active[key][plugin.name] = nil
if vim.tbl_isempty(self.active[key]) then 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 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 desc? string
---@field mode? string|string[]
---@field noremap? boolean ---@field noremap? boolean
---@field remap? boolean ---@field remap? boolean
---@field expr? 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 ---@class LazyKeysHandler:LazyHandler
local M = {} local M = {}
local skip = { mode = true, id = true, ft = true, rhs = true, lhs = true } ---@param feed string
function M.replace_special(feed)
---@param value string|LazyKeysSpec for special, key in pairs({ leader = vim.g.mapleader, localleader = vim.g.maplocalleader }) do
---@param mode? string local pattern = "<"
---@return LazyKeys for i = 1, #special do
function M.parse(value, mode) pattern = pattern .. "[" .. special:sub(i, i) .. special:upper():sub(i, i) .. "]"
value = type(value) == "string" and { value } or value --[[@as LazyKeysSpec]] end
local ret = vim.deepcopy(value) --[[@as LazyKeys]] pattern = pattern .. ">"
ret.lhs = ret[1] or "" feed = feed:gsub(pattern, key)
ret.rhs = ret[2] end
---@diagnostic disable-next-line: no-unknown return feed
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, ", ") .. ")"
end end
if ret.mode ~= "n" then function M.retrigger(keys)
ret.id = ret.id .. " (" .. ret.mode .. ")" local pending = ""
while true do
---@type number|string
local c = vim.fn.getchar(0)
if c == 0 then
break
end 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 return ret
end 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) function M.opts(keys)
local opts = {} ---@type LazyKeysBase local opts = {}
---@diagnostic disable-next-line: no-unknown
for k, v in pairs(keys) do for k, v in pairs(keys) do
if type(k) ~= "number" and not skip[k] then if type(k) ~= "number" and k ~= "mode" then
---@diagnostic disable-next-line: no-unknown
opts[k] = v opts[k] = v
end end
end end
return opts return opts
end end
---@param keys LazyKeys ---@return string
function M.is_nop(keys) function M:key(value)
return type(keys.rhs) == "string" and (keys.rhs == "" or keys.rhs:lower() == "<nop>") 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 end
---@param keys LazyKeys ---@param value string|LazyKeys
function M:_add(keys) function M:_add(value)
local lhs = keys.lhs local keys = M.parse(value)
local lhs = keys[1]
local opts = M.opts(keys) local opts = M.opts(keys)
---@param buf? number
local function add(buf)
if M.is_nop(keys) then
return self:_set(keys, buf)
end
vim.keymap.set(keys.mode, lhs, function() vim.keymap.set(keys.mode, lhs, function()
local plugins = self.active[keys.id] local key = self:key(value)
local plugins = self.active[key]
-- always delete the mapping immediately to prevent recursive mappings -- always delete the mapping immediately to prevent recursive mappings
self:_del(keys) self:_del(value)
self.active[keys.id] = nil self.active[key] = nil
if plugins then Util.track({ keys = lhs })
local name = M.to_string(keys) Loader.load(plugins, { keys = lhs })
Util.track({ keys = name }) M.retrigger(lhs)
Loader.load(plugins, { keys = name })
Util.track() Util.track()
end, opts)
end end
if keys.mode:sub(-1) == "a" then ---@param value string|LazyKeys
lhs = lhs .. "<C-]>" function M:_del(value)
end local keys = M.parse(value)
local feed = vim.api.nvim_replace_termcodes("<Ignore>" .. lhs, true, true, true) pcall(vim.keymap.del, keys.mode, keys[1])
-- insert instead of append the lhs if keys[2] then
vim.api.nvim_feedkeys(feed, "i", false) vim.keymap.set(keys.mode, keys[1], keys[2], M.opts(keys))
end, {
desc = opts.desc,
nowait = opts.nowait,
-- we do not return anything, but this is still needed to make operator pending mappings work
expr = true,
buffer = buf,
})
end
-- buffer-local mappings
if keys.ft then
vim.api.nvim_create_autocmd("FileType", {
pattern = keys.ft,
callback = function(event)
if self.active[keys.id] and not M.is_nop(keys) then
add(event.buf)
else
-- Only create the mapping if its managed by lazy
-- otherwise the plugin is supposed to manage it
self:_set(keys, event.buf)
end
end,
})
else
add()
end
end
-- Delete a mapping and create the real global/buffer-local
-- mapping when needed
---@param keys LazyKeys
function M:_del(keys)
-- bufs will be all buffers of the filetype for a buffer-local mapping
-- OR `false` for a global mapping
local bufs = { false }
if keys.ft then
local ft = type(keys.ft) == "string" and { keys.ft } or keys.ft --[[@as string[] ]]
bufs = vim.tbl_filter(function(buf)
return vim.tbl_contains(ft, vim.bo[buf].filetype)
end, vim.api.nvim_list_bufs())
end
for _, buf in ipairs(bufs) do
pcall(vim.keymap.del, keys.mode, keys.lhs, { buffer = buf or nil })
self:_set(keys, buf or nil)
end
end
-- Create a mapping if it is managed by lazy
---@param keys LazyKeys
---@param buf number?
function M:_set(keys, buf)
if keys.rhs then
local opts = M.opts(keys)
---@diagnostic disable-next-line: inject-field
opts.buffer = buf
vim.keymap.set(keys.mode, keys.lhs, keys.rhs, opts)
end end
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 Config = require("lazy.core.config")
local Handler = require("lazy.core.handler") local Handler = require("lazy.core.handler")
local Cache = require("lazy.core.cache")
local Plugin = require("lazy.core.plugin") local Plugin = require("lazy.core.plugin")
local Util = require("lazy.core.util")
---@class LazyCoreLoader
local M = {} local M = {}
local DEFAULT_PRIORITY = 50 local DEFAULT_PRIORITY = 50
@ -16,7 +15,6 @@ M.init_done = false
M.disabled_rtp_plugins = { packer_compiled = true } M.disabled_rtp_plugins = { packer_compiled = true }
---@type table<string,string> ---@type table<string,string>
M.did_ftdetect = {} M.did_ftdetect = {}
M.did_handlers = false
function M.disable_rtp_plugin(plugin) function M.disable_rtp_plugin(plugin)
M.disabled_rtp_plugins[plugin] = true M.disabled_rtp_plugins[plugin] = true
@ -35,7 +33,6 @@ function M.setup()
-- load the plugins -- load the plugins
Plugin.load() Plugin.load()
Handler.init()
-- install missing plugins -- install missing plugins
if Config.options.install.missing then if Config.options.install.missing then
@ -44,14 +41,12 @@ function M.setup()
while M.install_missing() do while M.install_missing() do
count = count + 1 count = count + 1
if count > 5 then if count > 5 then
Util.error("Too many rounds of missing plugins")
break break
end end
end end
Util.track() Util.track()
end end
Config.mapleader = vim.g.mapleader Config.mapleader = vim.g.mapleader
Config.maplocalleader = vim.g.maplocalleader
-- report any warnings & errors -- report any warnings & errors
Config.spec:report() Config.spec:report()
@ -59,7 +54,6 @@ function M.setup()
-- setup handlers -- setup handlers
Util.track("handlers") Util.track("handlers")
Handler.setup() Handler.setup()
M.did_handlers = true
Util.track() Util.track()
end end
@ -67,25 +61,17 @@ end
-- multiple rounds can happen when importing a spec from a missing plugin -- multiple rounds can happen when importing a spec from a missing plugin
function M.install_missing() function M.install_missing()
for _, plugin in pairs(Config.plugins) do for _, plugin in pairs(Config.plugins) do
local installed = plugin._.installed if not (plugin._.installed or Plugin.has_errors(plugin)) then
local has_errors = Plugin.has_errors(plugin)
if not has_errors and not (installed and not plugin._.build) then
for _, colorscheme in ipairs(Config.options.install.colorscheme) do for _, colorscheme in ipairs(Config.options.install.colorscheme) do
if colorscheme == "default" then if pcall(vim.cmd.colorscheme, colorscheme) then
break
end
M.colorscheme(colorscheme)
if vim.g.colors_name or pcall(vim.cmd.colorscheme, colorscheme) then
break break
end end
end end
Cache.reset() require("lazy.manage").install({ wait = true, lockfile = true })
require("lazy.manage").install({ wait = true, lockfile = true, clear = false }) -- remove and installed plugins from indexed, so cache will index again
-- remove any installed plugins from indexed, so cache will index again
for _, p in pairs(Config.plugins) do for _, p in pairs(Config.plugins) do
if p._.installed then if p._.installed then
Cache.reset(p.dir) Cache.indexed[p.dir] = nil
end end
end end
-- reload plugins -- reload plugins
@ -104,7 +90,7 @@ function M.startup()
M.source(vim.env.VIMRUNTIME .. "/filetype.lua") M.source(vim.env.VIMRUNTIME .. "/filetype.lua")
-- backup original rtp -- backup original rtp
local rtp = vim.opt.rtp:get() --[[@as string[] ]] local rtp = vim.opt.rtp:get()
-- 1. run plugin init -- 1. run plugin init
Util.track({ start = "init" }) Util.track({ start = "init" })
@ -133,9 +119,6 @@ function M.startup()
Util.track({ start = "rtp plugins" }) Util.track({ start = "rtp plugins" })
for _, path in ipairs(rtp) do for _, path in ipairs(rtp) do
if not path:find("after/?$") then 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) M.packadd(path)
end end
end end
@ -143,9 +126,7 @@ function M.startup()
-- 4. load after plugins -- 4. load after plugins
Util.track({ start = "after" }) Util.track({ start = "after" })
for _, path in for _, path in ipairs(vim.opt.rtp:get()) do
ipairs(vim.opt.rtp:get() --[[@as string[] ]])
do
if path:find("after/?$") then if path:find("after/?$") then
M.source_runtime(path, "plugin") M.source_runtime(path, "plugin")
end end
@ -161,7 +142,7 @@ function M.get_start_plugins()
---@type LazyPlugin[] ---@type LazyPlugin[]
local start = {} local start = {}
for _, plugin in pairs(Config.plugins) do 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 start[#start + 1] = plugin
end end
end end
@ -176,8 +157,7 @@ end
---@class Loader ---@class Loader
---@param plugins string|LazyPlugin|string[]|LazyPlugin[] ---@param plugins string|LazyPlugin|string[]|LazyPlugin[]
---@param reason {[string]:string} ---@param reason {[string]:string}
---@param opts? {force:boolean} when force is true, we skip the cond check function M.load(plugins, reason)
function M.load(plugins, reason, opts)
---@diagnostic disable-next-line: cast-local-type ---@diagnostic disable-next-line: cast-local-type
plugins = (type(plugins) == "string" or plugins.name) and { plugins } or plugins plugins = (type(plugins) == "string" or plugins.name) and { plugins } or plugins
---@cast plugins (string|LazyPlugin)[] ---@cast plugins (string|LazyPlugin)[]
@ -194,135 +174,23 @@ function M.load(plugins, reason, opts)
end end
end end
if plugin and not plugin._.loaded then if plugin and not plugin._.loaded then
M._load(plugin, reason, opts) M._load(plugin, reason)
end
end
end
---@param plugin LazyPlugin
function M.deactivate(plugin)
if not plugin._.loaded then
return
end
local main = M.get_main(plugin)
if main then
Util.try(function()
local mod = require(main)
if mod.deactivate then
mod.deactivate(plugin)
end
end, "Failed to deactivate plugin " .. plugin.name)
end
-- execute deactivate when needed
if plugin.deactivate then
Util.try(function()
plugin.deactivate(plugin)
end, "Failed to deactivate plugin " .. plugin.name)
end
-- disable handlers
Handler.disable(plugin)
-- clear plugin properties cache
plugin._.cache = nil
-- remove loaded lua modules
Util.walkmods(plugin.dir .. "/lua", function(modname)
package.loaded[modname] = nil
package.preload[modname] = nil
end)
-- clear vim.g.loaded_ for plugins
Util.ls(plugin.dir .. "/plugin", function(_, name, type)
if type == "file" then
vim.g["loaded_" .. name:gsub("%..*", "")] = nil
end
end)
-- set as not loaded
plugin._.loaded = nil
end
--- reload a plugin
---@param plugin LazyPlugin|string
function M.reload(plugin)
if type(plugin) == "string" then
plugin = Config.plugins[plugin]
end
if not plugin then
error("Plugin not found")
end
local load = plugin._.loaded ~= nil
M.deactivate(plugin)
-- enable handlers
Handler.enable(plugin)
-- run init
if plugin.init then
Util.try(function()
plugin.init(plugin)
end, "Failed to run `init` for **" .. plugin.name .. "**")
end
-- if this is a start plugin, load it now
if plugin.lazy == false then
load = true
end
local events = plugin._.handlers and plugin._.handlers.event and plugin._.handlers.event or {}
for _, event in pairs(events) do
if event.id:find("VimEnter") or event.id:find("UIEnter") or event.id:find("VeryLazy") then
load = true
break
end
end
-- reload any vimscript files for this plugin
local scripts = vim.fn.getscriptinfo()
local loaded_scripts = {}
for _, s in ipairs(scripts) do
---@type string
local path = s.name
if
path:sub(-4) == ".vim"
and path:find(plugin.dir, 1, true) == 1
and not path:find("/plugin/", 1, true)
and not path:find("/ftplugin/", 1, true)
then
loaded_scripts[#loaded_scripts + 1] = path
end
end
if load then
M.load(plugin, { start = "reload" })
for _, s in ipairs(loaded_scripts) do
M.source(s)
end end
end end
end end
---@param plugin LazyPlugin ---@param plugin LazyPlugin
---@param reason {[string]:string} ---@param reason {[string]:string}
---@param opts? {force:boolean} when force is true, we skip the cond check function M._load(plugin, reason)
function M._load(plugin, reason, opts)
if not plugin._.installed then if not plugin._.installed then
return Util.error("Plugin " .. plugin.name .. " is not installed") return Util.error("Plugin " .. plugin.name .. " is not installed")
end end
if plugin._.cond == false and not (opts and opts.force) then if plugin.cond ~= nil then
if plugin.cond == false or (type(plugin.cond) == "function" and not plugin.cond()) then
plugin._.cond = false
return return
end end
if not Handler.did_setup then
Util.try(function()
Handler.enable(plugin)
end, "Failed to setup handlers for " .. plugin.name)
end end
---@diagnostic disable-next-line: assign-type-mismatch ---@diagnostic disable-next-line: assign-type-mismatch
@ -341,13 +209,7 @@ function M._load(plugin, reason, opts)
Util.track({ plugin = plugin.name, start = reason.start }) Util.track({ plugin = plugin.name, start = reason.start })
Handler.disable(plugin) Handler.disable(plugin)
if not plugin.virtual then
M.add_to_rtp(plugin) M.add_to_rtp(plugin)
end
if plugin._.pkg and plugin._.pkg.source == "rockspec" then
M.add_to_luapath(plugin)
end
if plugin.dependencies then if plugin.dependencies then
Util.try(function() Util.try(function()
@ -355,18 +217,15 @@ function M._load(plugin, reason, opts)
end, "Failed to load deps for " .. plugin.name) end, "Failed to load deps for " .. plugin.name)
end end
if not plugin.virtual then
M.packadd(plugin.dir) M.packadd(plugin.dir)
end if plugin.config then
if plugin.config or plugin.opts then
M.config(plugin) M.config(plugin)
end end
plugin._.loaded.time = Util.track().time plugin._.loaded.time = Util.track().time
table.remove(M.loading) table.remove(M.loading)
vim.schedule(function() vim.schedule(function()
vim.api.nvim_exec_autocmds("User", { pattern = "LazyLoad", modeline = false, data = plugin.name }) vim.cmd("do User LazyRender")
vim.api.nvim_exec_autocmds("User", { pattern = "LazyRender", modeline = false })
end) end)
end end
@ -376,38 +235,13 @@ function M.config(plugin)
local fn local fn
if type(plugin.config) == "function" then if type(plugin.config) == "function" then
fn = function() fn = function()
local opts = Plugin.values(plugin, "opts", false) plugin.config(plugin)
plugin.config(plugin, opts)
end end
else else
local main = M.get_main(plugin)
if main then
fn = function()
local opts = Plugin.values(plugin, "opts", false)
require(main).setup(opts)
end
else
return Util.error(
"Lua module not found for config of " .. plugin.name .. ". Please use a `config()` function instead"
)
end
end
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) local normname = Util.normname(plugin.name)
---@type string[] ---@type string[]
local mods = {} local mods = {}
for _, mod in ipairs(Cache.find("*", { all = true, rtp = false, paths = { plugin.dir } })) do for _, modname in ipairs(Cache.get_topmods(plugin.dir)) do
local modname = mod.modname
mods[#mods + 1] = modname mods[#mods + 1] = modname
local modnorm = Util.normname(modname) local modnorm = Util.normname(modname)
-- if we found an exact match, then use that -- if we found an exact match, then use that
@ -416,8 +250,21 @@ function M.get_main(plugin)
break break
end end
end end
if #mods == 1 then
return #mods == 1 and mods[1] or nil fn = function()
local opts = plugin.config
if opts == true then
opts = nil
end
require(mods[1]).setup(opts)
end
else
return Util.error(
"Lua module not found for config of " .. plugin.name .. ". Please use a `config()` function instead"
)
end
end
Util.try(fn, "Failed to run `config` for " .. plugin.name)
end end
---@param path string ---@param path string
@ -468,8 +315,10 @@ function M.add_to_rtp(plugin)
local rtp = vim.api.nvim_get_runtime_file("", true) local rtp = vim.api.nvim_get_runtime_file("", true)
local idx_dir, idx_after local idx_dir, idx_after
local is_win = jit.os:find("Windows")
for i, path in ipairs(rtp) do for i, path in ipairs(rtp) do
if Util.is_win then if is_win then
path = Util.norm(path) path = Util.norm(path)
end end
if path == Config.me then if path == Config.me then
@ -484,30 +333,17 @@ function M.add_to_rtp(plugin)
table.insert(rtp, idx_dir or (#rtp + 1), plugin.dir) table.insert(rtp, idx_dir or (#rtp + 1), plugin.dir)
local after = plugin.dir .. "/after" 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) table.insert(rtp, idx_after or (#rtp + 1), after)
end end
---@type vim.Option
vim.opt.rtp = rtp vim.opt.rtp = rtp
end 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) function M.source(path)
Util.track({ runtime = path }) Util.track({ runtime = path })
Util.try(function() Util.try(function()
vim.cmd("source " .. path) vim.cmd("silent source " .. path)
end, "Failed to source `" .. path .. "`") end, "Failed to source `" .. path .. "`")
Util.track() Util.track()
end end
@ -520,7 +356,7 @@ function M.colorscheme(name)
if not plugin._.loaded then if not plugin._.loaded then
for _, ext in ipairs({ "lua", "vim" }) do for _, ext in ipairs({ "lua", "vim" }) do
local path = plugin.dir .. "/colors/" .. name .. "." .. ext 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 }) return M.load(plugin, { colorscheme = name })
end end
end end
@ -528,49 +364,4 @@ function M.colorscheme(name)
end end
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 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,99 @@
local Config = require("lazy.core.config") local Config = require("lazy.core.config")
local Meta = require("lazy.core.meta")
local Pkg = require("lazy.pkg")
local Util = require("lazy.core.util") local Util = require("lazy.core.util")
local Handler = require("lazy.core.handler")
local Cache = require("lazy.core.cache")
---@class LazyCorePlugin ---@class LazyCorePlugin
local M = {} local M = {}
M.loading = false
local list_merge = { "dependencies" }
vim.list_extend(list_merge, vim.tbl_values(Handler.types))
---@class LazySpecLoader ---@class LazySpecLoader
---@field meta LazyMeta
---@field plugins table<string, LazyPlugin> ---@field plugins table<string, LazyPlugin>
---@field disabled table<string, LazyPlugin> ---@field disabled table<string, LazyPlugin>
---@field ignore_installed table<string, true>
---@field modules string[] ---@field modules string[]
---@field notifs {msg:string, level:number, file?:string}[] ---@field notifs {msg:string, level:number, file?:string}[]
---@field importing? string ---@field importing? string
---@field optional? boolean
local Spec = {} local Spec = {}
M.Spec = Spec M.Spec = Spec
M.LOCAL_SPEC = ".lazy.lua"
---@param spec? LazySpec ---@param spec? LazySpec
---@param opts? {optional?:boolean, pkg?:boolean} function Spec.new(spec)
function Spec.new(spec, opts) local self = setmetatable({}, { __index = Spec })
local self = setmetatable({}, Spec) self.plugins = {}
self.meta = Meta.new(self)
self.disabled = {} self.disabled = {}
self.modules = {} self.modules = {}
self.notifs = {} 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 if spec then
self:parse(spec) self:normalize(spec)
end end
return self return self
end 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 -- PERF: optimized code to get package name without using lua patterns
---@return string
function Spec.get_name(pkg) function Spec.get_name(pkg)
local name = pkg:sub(-4) == ".git" and pkg:sub(1, -5) or 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?]] local slash = name:reverse():find("/", 1, true) --[[@as number?]]
return slash and name:sub(#name - slash + 2) or pkg:gsub("%W+", "_") return slash and name:sub(#name - slash + 2) or pkg:gsub("%W+", "_")
end 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
return results and table.insert(results, plugin.name)
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))
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
return results and table.insert(results, plugin.name)
end
function Spec:error(msg) function Spec:error(msg)
self:log(msg, vim.log.levels.ERROR) self:log(msg, vim.log.levels.ERROR)
end end
@ -71,6 +102,53 @@ function Spec:warn(msg)
self:log(msg, vim.log.levels.WARN) self:log(msg, vim.log.levels.WARN)
end 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 msg string
---@param level number ---@param level number
function Spec:log(msg, level) function Spec:log(msg, level)
@ -79,39 +157,39 @@ end
function Spec:report(level) function Spec:report(level)
level = level or vim.log.levels.ERROR level = level or vim.log.levels.ERROR
local count = 0
for _, notif in ipairs(self.notifs) do for _, notif in ipairs(self.notifs) do
if notif.level >= level then if notif.level >= level then
Util.notify(notif.msg, { level = notif.level }) Util.notify(notif.msg, notif.level)
count = count + 1
end end
end end
return count
end end
---@param spec LazySpec|LazySpecImport ---@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 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 elseif #spec > 1 or Util.is_list(spec) then
---@cast spec LazySpec[] ---@cast spec LazySpec[]
for _, s in ipairs(spec) do for _, s in ipairs(spec) do
self:normalize(s) self:normalize(s, results, is_dep)
end
elseif spec[1] or spec.dir or spec.url then
---@cast spec LazyPluginSpec
self.meta:add(spec)
---@diagnostic disable-next-line: cast-type-mismatch
---@cast spec LazySpecImport
if spec and spec.import then
self:import(spec)
end end
elseif spec.import then elseif spec.import then
---@cast spec LazySpecImport ---@cast spec LazySpecImport
self:import(spec) self:import(spec)
else else
self:error("Invalid plugin spec " .. vim.inspect(spec)) ---@cast spec LazyPlugin
self:add(spec, results, is_dep)
end end
return results
end end
---@param spec LazySpecImport ---@param spec LazySpecImport
@ -119,113 +197,77 @@ function Spec:import(spec)
if spec.import == "lazy" then if spec.import == "lazy" then
return self:error("You can't name your plugins module `lazy`.") return self:error("You can't name your plugins module `lazy`.")
end 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 if spec.enabled == false or (type(spec.enabled) == "function" and not spec.enabled()) then
return return
end end
self.modules[#self.modules + 1] = import_name Cache.indexed_unloaded = false
self.modules[#self.modules + 1] = spec.import
local import = spec.import
local imported = 0 local imported = 0
Util.lsmod(spec.import, function(modname)
---@type {modname: string, load: fun():(LazyPluginSpec?, string?)}[]
local modspecs = {}
if type(import) == "string" then
Util.lsmod(import, function(modname, modpath)
modspecs[#modspecs + 1] = {
modname = modname,
load = function()
local mod, err = loadfile(modpath)
if mod then
return mod()
else
return nil, err
end
end,
}
end)
table.sort(modspecs, function(a, b)
return a.modname < b.modname
end)
else
modspecs = { { modname = import_name, load = spec.import } }
end
for _, modspec in ipairs(modspecs) do
imported = imported + 1 imported = imported + 1
local modname = modspec.modname
Util.track({ import = modname }) Util.track({ import = modname })
self.importing = modname self.importing = modname
-- unload the module so we get a clean slate -- unload the module so we get a clean slate
---@diagnostic disable-next-line: no-unknown ---@diagnostic disable-next-line: no-unknown
package.loaded[modname] = nil package.loaded[modname] = nil
Util.try(function() Util.try(function()
local mod, err = modspec.load() self:normalize(require(modname))
if err then self.importing = nil
self:error("Failed to load `" .. modname .. "`:\n" .. err) Util.track()
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
end, { end, {
msg = "Failed to load `" .. modname .. "`", msg = "Failed to load `" .. modname .. "`",
on_error = function(msg) on_error = function(msg)
self:error(msg) self:error(msg)
end,
})
self.importing = nil self.importing = nil
Util.track() Util.track()
end end,
})
end)
if imported == 0 then 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
end end
---@param old LazyPlugin
---@param new LazyPlugin
---@return LazyPlugin
function Spec:merge(old, new)
new._.dep = old._.dep and new._.dep
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() function M.update_state()
---@type string[]
local cloning = {}
---@type table<string,FileType> ---@type table<string,FileType>
local installed = {} local installed = {}
Util.ls(Config.options.root, function(_, name, type) Util.ls(Config.options.root, function(_, name, type)
if type == "directory" and name ~= "readme" then if type == "directory" and name ~= "readme" then
installed[name] = type installed[name] = type
elseif type == "file" and name:sub(-8) == ".cloning" then
name = name:sub(1, -9)
cloning[#cloning + 1] = name
end end
end) end)
for _, failed in ipairs(cloning) do
installed[failed] = nil
end
for _, plugin in pairs(Config.plugins) do for _, plugin in pairs(Config.plugins) do
plugin._ = plugin._ or {} plugin._ = plugin._ or {}
if plugin.lazy == nil then if plugin.lazy == nil then
@ -237,24 +279,15 @@ function M.update_state()
or plugin.cmd or plugin.cmd
plugin.lazy = lazy and true or false plugin.lazy = lazy and true or false
end end
if plugin.virtual then if plugin.dir:find(Config.options.root, 1, true) == 1 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
plugin._.installed = installed[plugin.name] ~= nil plugin._.installed = installed[plugin.name] ~= nil
installed[plugin.name] = nil installed[plugin.name] = nil
else else
plugin._.is_local = true plugin._.is_local = true
plugin._.installed = vim.fn.isdirectory(plugin.dir) == 1 plugin._.installed = true -- local plugins are managed by the user
end end
end end
for name in pairs(Config.spec.ignore_installed) do
installed[name] = nil
end
M.update_rocks_state()
Config.to_clean = {} Config.to_clean = {}
for pack, dir_type in pairs(installed) do for pack, dir_type in pairs(installed) do
table.insert(Config.to_clean, { table.insert(Config.to_clean, {
@ -270,66 +303,14 @@ function M.update_state()
end end
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() function M.load()
M.loading = true
-- load specs -- load specs
Util.track("spec") Util.track("spec")
Config.spec = Spec.new() Config.spec = Spec.new()
Config.spec:normalize(vim.deepcopy(Config.options.spec))
local specs = { -- add ourselves
---@diagnostic disable-next-line: param-type-mismatch Config.spec:add({ "folke/lazy.nvim" })
vim.deepcopy(Config.options.spec),
}
specs[#specs + 1] = M.find_local_spec()
specs[#specs + 1] = { "folke/lazy.nvim" }
Config.spec:parse(specs)
-- override some lazy props -- override some lazy props
local lazy = Config.spec.plugins["lazy.nvim"] local lazy = Config.spec.plugins["lazy.nvim"]
if lazy then if lazy then
@ -340,31 +321,14 @@ function M.load()
end end
lazy._.loaded = {} lazy._.loaded = {}
end end
Config.spec:fix_disabled()
-- add hererocks when enabled and needed
for _, plugin in pairs(Config.spec.plugins) do
if plugin.build == "rockspec" then
if Config.hererocks() then
Config.spec.meta:add({
"luarocks/hererocks",
build = "rockspec",
lazy = true,
})
end
break
end
end
local existing = Config.plugins local existing = Config.plugins
Config.plugins = Config.spec.plugins Config.plugins = Config.spec.plugins
-- copy state. This wont do anything during startup -- copy state. This wont do anything during startup
for name, plugin in pairs(existing) do for name, plugin in pairs(existing) do
if Config.plugins[name] then if Config.plugins[name] then
local new_state = Config.plugins[name]._
Config.plugins[name]._ = plugin._ 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
end end
Util.track() Util.track()
@ -372,36 +336,22 @@ function M.load()
Util.track("state") Util.track("state")
M.update_state() M.update_state()
Util.track() Util.track()
require("lazy.core.cache").indexed_unloaded = false
if Config.options.pkg.enabled and Pkg.dirty then
Pkg.update()
return M.load()
end
M.loading = false
vim.api.nvim_exec_autocmds("User", { pattern = "LazyPlugins", modeline = false })
end end
-- Finds the plugin that has this path -- Finds the plugin that has this path
---@param path string ---@param path string
---@param opts? {fast?:boolean} function M.find(path)
function M.find(path, opts)
if not Config.spec then if not Config.spec then
return return
end end
opts = opts or {}
local lua = path:find("/lua/", 1, true) local lua = path:find("/lua/", 1, true)
if lua then if lua then
local name = path:sub(1, lua - 1) local name = path:sub(1, lua - 1)
local slash = name:reverse():find("/", 1, true) local slash = name:reverse():find("/", 1, true)
if slash then if slash then
name = name:sub(#name - slash + 2) name = name:sub(#name - slash + 2)
if name then return name and Config.plugins[name] or Config.spec.plugins[name] or nil
if opts.fast then
return Config.spec.meta.plugins[name]
end
return Config.spec.plugins[name]
end
end end
end end
end end
@ -409,76 +359,11 @@ end
---@param plugin LazyPlugin ---@param plugin LazyPlugin
function M.has_errors(plugin) function M.has_errors(plugin)
for _, task in ipairs(plugin._.tasks or {}) do for _, task in ipairs(plugin._.tasks or {}) do
if task:has_errors() then if task.error then
return true return true
end end
end end
return false return false
end 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 return M

View file

@ -5,7 +5,6 @@ local M = {}
---@type LazyProfile[] ---@type LazyProfile[]
M._profiles = { { name = "lazy" } } M._profiles = { { name = "lazy" } }
M.is_win = jit.os:find("Windows")
---@param data (string|{[string]:string})? ---@param data (string|{[string]:string})?
---@param time number? ---@param time number?
@ -13,7 +12,7 @@ function M.track(data, time)
if data then if data then
local entry = { local entry = {
data = data, data = data,
time = time or vim.uv.hrtime(), time = time or vim.loop.hrtime(),
} }
table.insert(M._profiles[#M._profiles], entry) table.insert(M._profiles[#M._profiles], entry)
@ -24,29 +23,11 @@ function M.track(data, time)
else else
---@type LazyProfile ---@type LazyProfile
local entry = table.remove(M._profiles) local entry = table.remove(M._profiles)
entry.time = vim.uv.hrtime() - entry.time entry.time = vim.loop.hrtime() - entry.time
return entry return entry
end end
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() ---@generic F: fun()
---@param data (string|{[string]:string})? ---@param data (string|{[string]:string})?
---@param fn F ---@param fn F
@ -66,14 +47,14 @@ end
---@param name string ---@param name string
---@return string ---@return string
function M.normname(name) 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 return ret
end end
---@return string ---@return string
function M.norm(path) function M.norm(path)
if path:sub(1, 1) == "~" then 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 if home:sub(-1) == "\\" or home:sub(-1) == "/" then
home = home:sub(1, -2) home = home:sub(1, -2)
end end
@ -83,23 +64,26 @@ function M.norm(path)
return path:sub(-1) == "/" and path:sub(1, -2) or path return path:sub(-1) == "/" and path:sub(1, -2) or path
end end
---@param opts? {level?: number} ---@param opts? string|{msg:string, on_error:fun(msg)}
function M.pretty_trace(opts) function M.try(fn, opts)
opts = opts or {} opts = type(opts) == "string" and { msg = opts } or opts or {}
local msg = opts.msg
-- error handler
local error_handler = function(err)
local Config = require("lazy.core.config") local Config = require("lazy.core.config")
local trace = {} local trace = {}
local level = opts.level or 2 local level = 1
while true do while true do
local info = debug.getinfo(level, "Sln") local info = debug.getinfo(level, "Sln")
if not info then if not info then
break break
end end
if info.what ~= "C" and (Config.options.debug or not info.source:find("lazy.nvim")) then if info.what ~= "C" and not info.source:find("lazy.nvim") then
local source = info.source:sub(2) local source = info.source:sub(2)
if source:find(Config.options.root, 1, true) == 1 then if source:find(Config.options.root, 1, true) == 1 then
source = source:sub(#Config.options.root + 1) source = source:sub(#Config.options.root + 1)
end end
source = vim.fn.fnamemodify(source, ":p:~:.") --[[@as string]] source = vim.fn.fnamemodify(source, ":p:~:.")
local line = " - " .. source .. ":" .. info.currentline local line = " - " .. source .. ":" .. info.currentline
if info.name then if info.name then
line = line .. " _in_ **" .. info.name .. "**" line = line .. " _in_ **" .. info.name .. "**"
@ -108,19 +92,10 @@ function M.pretty_trace(opts)
end end
level = level + 1 level = level + 1
end end
return #trace > 0 and ("\n\n# stacktrace:\n" .. table.concat(trace, "\n")) or "" msg = msg .. "\n\n" .. err
if #trace > 0 then
msg = msg .. "\n\n# stacktrace:\n" .. table.concat(trace, "\n")
end end
---@generic R
---@param fn fun():R?
---@param opts? string|{msg:string, on_error:fun(msg)}
---@return R
function M.try(fn, opts)
opts = type(opts) == "string" and { msg = opts } or opts or {}
local msg = opts.msg
-- error handler
local error_handler = function(err)
msg = (msg and (msg .. "\n\n") or "") .. err .. M.pretty_trace()
if opts.on_error then if opts.on_error then
opts.on_error(msg) opts.on_error(msg)
else else
@ -143,7 +118,7 @@ function M.get_source()
if not info then if not info then
break break
end 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) return info.source:sub(2)
end end
f = f + 1 f = f + 1
@ -166,15 +141,9 @@ end
function M.very_lazy() function M.very_lazy()
local function _load() local function _load()
vim.schedule(function() vim.defer_fn(function()
if vim.v.exiting ~= vim.NIL then vim.cmd("do User VeryLazy")
return end, 50)
end
vim.g.did_very_lazy = true
M.track({ event = "VeryLazy" })
vim.api.nvim_exec_autocmds("User", { pattern = "VeryLazy", modeline = false })
M.track()
end)
end end
vim.api.nvim_create_autocmd("User", { vim.api.nvim_create_autocmd("User", {
@ -184,7 +153,7 @@ function M.very_lazy()
if vim.v.vim_did_enter == 1 then if vim.v.vim_did_enter == 1 then
_load() _load()
else else
vim.api.nvim_create_autocmd("UIEnter", { vim.api.nvim_create_autocmd("VimEnter", {
once = true, once = true,
callback = function() callback = function()
_load() _load()
@ -199,19 +168,16 @@ end
---@param path string ---@param path string
---@param fn fun(path: string, name:string, type:FileType):boolean? ---@param fn fun(path: string, name:string, type:FileType):boolean?
function M.ls(path, fn) function M.ls(path, fn)
local handle = vim.uv.fs_scandir(path) local handle = vim.loop.fs_scandir(path)
while handle do 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 if not name then
break break
end end
if fn(path .. "/" .. name, name, t) == false then
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
break break
end end
end end
@ -228,96 +194,17 @@ function M.walk(path, fn)
end) end)
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 modname string
---@param fn fun(modname:string, modpath:string) ---@param fn fun(modname:string, modpath:string)
function M.lsmod(modname, fn) 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 if not root then
return return
end end
if match:sub(-4) == ".lua" then if vim.loop.fs_stat(root .. ".lua") then
fn(modname, match) fn(modname, root .. ".lua")
if not vim.uv.fs_stat(root) then
return
end
end end
M.ls(root, function(path, name, type) M.ls(root, function(path, name, type)
@ -325,38 +212,20 @@ function M.lsmod(modname, fn)
fn(modname, path) fn(modname, path)
elseif (type == "file" or type == "link") and name:sub(-4) == ".lua" then elseif (type == "file" or type == "link") and name:sub(-4) == ".lua" then
fn(modname .. "." .. name:sub(1, -5), path) 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") fn(modname .. "." .. name, path .. "/init.lua")
end end
end) 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 msg string|string[]
---@param opts? LazyNotifyOpts ---@param opts? {lang:string, title:string}
function M.notify(msg, opts) function M.notify(msg, level, opts)
if vim.in_fast_event() then if vim.in_fast_event() then
return vim.schedule(function() vim.schedule(function()
M.notify(msg, opts) M.notify(msg, level, opts)
end) end)
return
end end
opts = opts or {} opts = opts or {}
@ -368,20 +237,10 @@ function M.notify(msg, opts)
"\n" "\n"
) )
end end
if opts.stacktrace then
msg = msg .. M.pretty_trace({ level = opts.stacklevel or 2 })
end
local lang = opts.lang or "markdown" local lang = opts.lang or "markdown"
local n = opts.once and vim.notify_once or vim.notify vim.notify(msg, level, {
n(msg, opts.level or vim.log.levels.INFO, {
ft = lang,
on_open = function(win) on_open = function(win)
local ok = pcall(function()
vim.treesitter.language.add("markdown")
end)
if not ok then
pcall(require, "nvim-treesitter") pcall(require, "nvim-treesitter")
end
vim.wo[win].conceallevel = 3 vim.wo[win].conceallevel = 3
vim.wo[win].concealcursor = "" vim.wo[win].concealcursor = ""
vim.wo[win].spell = false vim.wo[win].spell = false
@ -391,126 +250,39 @@ function M.notify(msg, opts)
vim.bo[buf].syntax = lang vim.bo[buf].syntax = lang
end end
end, end,
title = opts.title or "lazy.nvim", title = "lazy.nvim" .. (opts.title and ": " .. opts.title or ""),
}) })
end end
---@param msg string|string[] ---@param msg string|string[]
---@param opts? LazyNotifyOpts function M.error(msg)
function M.error(msg, opts) M.notify(msg, vim.log.levels.ERROR)
opts = opts or {}
opts.level = vim.log.levels.ERROR
M.notify(msg, opts)
end end
---@param msg string|string[] ---@param msg string|string[]
---@param opts? LazyNotifyOpts function M.info(msg)
function M.info(msg, opts) M.notify(msg, vim.log.levels.INFO)
opts = opts or {}
opts.level = vim.log.levels.INFO
M.notify(msg, opts)
end end
---@param msg string|string[] ---@param msg string|string[]
---@param opts? LazyNotifyOpts function M.warn(msg)
function M.warn(msg, opts) M.notify(msg, vim.log.levels.WARN)
opts = opts or {}
opts.level = vim.log.levels.WARN
M.notify(msg, opts)
end end
---@param msg string|table ---@param msg string|table
---@param opts? LazyNotifyOpts ---@param level? number
function M.debug(msg, opts) ---@param opts? {lang:string, title:string}
function M.debug(msg, level, opts)
if not require("lazy.core.config").options.debug then if not require("lazy.core.config").options.debug then
return return
end end
opts = opts or {} opts = opts or {}
if opts.title then
opts.title = "lazy.nvim: " .. opts.title
end
if type(msg) == "string" then if type(msg) == "string" then
M.notify(msg, opts) M.notify(msg, level, opts)
else else
opts.lang = "lua" opts.lang = "lua"
M.notify(vim.inspect(msg), opts) M.notify(vim.inspect(msg), level, opts)
end end
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 return M

View file

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

View file

@ -18,22 +18,30 @@ return {
"nvim-neorg/neorg", "nvim-neorg/neorg",
-- lazy-load on filetype -- lazy-load on filetype
ft = "norg", ft = "norg",
-- options for neorg. This will automatically call `require("neorg").setup(opts)` -- custom config that will be executed when loading the plugin
opts = { config = function()
load = { require("neorg").setup()
["core.defaults"] = {}, 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", "dstein64/vim-startuptime",
-- lazy-load on a command -- lazy-load on a command
cmd = "StartupTime", 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, 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 -- you can use the VeryLazy event for things that can
-- load later and are not important for the initial UI -- load later and are not important for the initial UI
{ "stevearc/dressing.nvim", event = "VeryLazy" }, { "stevearc/dressing.nvim", event = "VeryLazy" },
{ {
"Wansmer/treesj", "cshuaimin/ssr.nvim",
keys = { -- init is always executed during startup, but doesn't load the plugin yet.
{ "J", "<cmd>TSJToggle<cr>", desc = "Join Toggle" }, init = function()
}, vim.keymap.set({ "n", "x" }, "<leader>cR", function()
opts = { use_default_keymaps = false, max_join_length = 150 }, -- 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 -- you can use a custom url to fetch a plugin
{ url = "git@github.com:folke/noice.nvim.git" }, { url = "git@github.com:folke/noice.nvim.git" },
-- local plugins can also be configured with the dev option. -- local plugins can also be configure with the dev option.
-- This will use {config.dev.path}/noice.nvim/ instead of fetching it from GitHub -- 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 -- With the dev option, you can easily switch between the local and installed version of a plugin
{ "folke/noice.nvim", dev = true }, { "folke/noice.nvim", dev = true },
} }

View file

@ -1,75 +1,9 @@
local Config = require("lazy.core.config") local Config = require("lazy.core.config")
local Process = require("lazy.manage.process")
local uv = vim.uv or vim.loop
local M = {} 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() function M.check()
start("lazy.nvim") vim.health.report_start("lazy.nvim")
info("{lazy.nvim} version `" .. Config.version .. "`")
M.have("git")
local sites = vim.opt.packpath:get() local sites = vim.opt.packpath:get()
local default_site = vim.fn.stdpath("data") .. "/site" local default_site = vim.fn.stdpath("data") .. "/site"
@ -80,138 +14,92 @@ function M.check()
local existing = false local existing = false
for _, site in pairs(sites) do for _, site in pairs(sites) do
for _, packs in ipairs(vim.fn.expand(site .. "/pack/*", false, true)) 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 existing = true
warn("found existing packages at `" .. packs .. "`") vim.health.report_warn("found existing packages at `" .. packs .. "`")
end end
end end
end end
if not existing then if not existing then
ok("no existing packages found by other package managers") vim.health.report_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
end end
local packer_compiled = vim.fn.stdpath("config") .. "/plugin/packer_compiled.lua" local packer_compiled = vim.fn.stdpath("config") .. "/plugin/packer_compiled.lua"
if uv.fs_stat(packer_compiled) then if vim.loop.fs_stat(packer_compiled) then
error("please remove the file `" .. packer_compiled .. "`") vim.health.report_error("please remove the file `" .. packer_compiled .. "`")
else else
ok("packer_compiled.lua not found") vim.health.report_ok("packer_compiled.lua not found")
end end
local spec = Config.spec 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 for _, plugin in pairs(spec.plugins) do
M.check_valid(plugin) M.check_valid(plugin)
M.check_override(plugin)
end end
if #spec.notifs > 0 then if #spec.notifs > 0 then
error("Issues were reported when loading your specs:") vim.health.report_error("Issues were reported when loading your specs:")
for _, notif in ipairs(spec.notifs) do for _, notif in ipairs(spec.notifs) do
local lines = vim.split(notif.msg, "\n") local lines = vim.split(notif.msg, "\n")
for _, line in ipairs(lines) do for _, line in ipairs(lines) do
if notif.level == vim.log.levels.ERROR then if notif.level == vim.log.levels.ERROR then
error(line) vim.health.report_error(line)
else else
warn(line) vim.health.report_warn(line)
end end
end end
end end
end end
end end
start("luarocks")
if Config.options.rocks.enabled then
if Config.hererocks() then
info("checking `hererocks` installation")
else
info("checking `luarocks` installation")
end
local need_luarocks = {}
for _, plugin in pairs(spec.plugins) do
if plugin.build == "rockspec" then
table.insert(need_luarocks, plugin.name)
end
end
if #need_luarocks == 0 then
ok("no plugins require `luarocks`, so you can ignore any warnings below")
else
local lines = vim.tbl_map(function(name)
return " * `" .. name .. "`"
end, need_luarocks)
info("you have some plugins that require `luarocks`:\n" .. table.concat(lines, "\n"))
end
local ok = require("lazy.pkg.rockspec").check({
error = #need_luarocks > 0 and error or warn,
warn = warn,
ok = ok,
})
if not ok then
warn(table.concat({
"Lazy won't be able to install plugins that require `luarocks`.",
"Here's what you can do:",
" - fix your `luarocks` installation",
Config.hererocks() and " - disable *hererocks* with `opts.rocks.hererocks = false`"
or " - enable `hererocks` with `opts.rocks.hererocks = true`",
" - disable `luarocks` support completely with `opts.rocks.enabled = false`",
}, "\n"))
end
else
ok("luarocks disabled")
end
end
---@param plugin LazyPlugin ---@param plugin LazyPlugin
function M.check_valid(plugin) function M.check_valid(plugin)
for key in pairs(plugin) do for key in pairs(plugin) do
if not vim.tbl_contains(M.valid, key) then if not vim.tbl_contains(M.valid, key) then
if key ~= "module" or type(plugin.module) ~= "boolean" 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
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 = { M.valid = {
1, 1,
"_",
"branch",
"build",
"cmd",
"commit",
"cond",
"config",
"deactivate",
"dependencies",
"dev",
"dir",
"enabled",
"event",
"ft",
"import",
"init",
"keys",
"lazy",
"main",
"module",
"name", "name",
"optional",
"opts",
"pin",
"priority",
"submodules",
"tag",
"url", "url",
"enabled",
"lazy",
"dev",
"dependencies",
"init",
"config",
"build",
"branch",
"tag",
"commit",
"version", "version",
"module",
"pin",
"cmd",
"event",
"keys",
"ft",
"dir",
"priority",
"cond",
"_",
} }
return M return M

View file

@ -4,21 +4,20 @@ local Util = require("lazy.util")
local M = {} local M = {}
function M.index(plugin) 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 {} return {}
end end
local files = {} ---@param file string
local files = vim.tbl_flatten(vim.tbl_map(function(file)
for _, file in ipairs(Config.options.readme.files) do return vim.fn.expand(plugin.dir .. "/" .. file, false, true)
vim.list_extend(files, vim.fn.expand(plugin.dir .. "/" .. file, false, true)) end, Config.options.readme.files))
end
---@type table<string,{file:string, tag:string, line:string}> ---@type table<string,{file:string, tag:string, line:string}>
local tags = {} local tags = {}
for _, file in ipairs(files) do for _, file in ipairs(files) do
file = Util.norm(file) 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 rel_file = file:sub(#plugin.dir + 1)
local tag_filename = plugin.name .. vim.fn.fnamemodify(rel_file, ":h"):gsub("%W+", "-"):gsub("^%-$", "") local tag_filename = plugin.name .. vim.fn.fnamemodify(rel_file, ":h"):gsub("%W+", "-"):gsub("^%-$", "")
local lines = vim.split(Util.read_file(file), "\n") local lines = vim.split(Util.read_file(file), "\n")
@ -42,16 +41,12 @@ function M.update()
if Config.plugins["lazy.nvim"] then if Config.plugins["lazy.nvim"] then
vim.cmd.helptags(Config.plugins["lazy.nvim"].dir .. "/doc") vim.cmd.helptags(Config.plugins["lazy.nvim"].dir .. "/doc")
end end
if Config.options.readme.enabled == false then
return
end
local docs = Config.options.readme.root .. "/doc" local docs = Config.options.readme.root .. "/doc"
vim.fn.mkdir(docs, "p") vim.fn.mkdir(docs, "p")
Util.ls(docs, function(path, name, type) Util.ls(docs, function(path, name, type)
if type == "file" and name:sub(-2) == "md" then if type == "file" and name:sub(-2) == "md" then
vim.uv.fs_unlink(path) vim.loop.fs_unlink(path)
end end
end) end)
---@type {file:string, tag:string, line:string}[] ---@type {file:string, tag:string, line:string}[]
@ -64,7 +59,7 @@ function M.update()
local lines = { [[!_TAG_FILE_ENCODING utf-8 //]] } local lines = { [[!_TAG_FILE_ENCODING utf-8 //]] }
Util.foreach(tags, function(_, tag) Util.foreach(tags, function(_, tag)
table.insert(lines, ("%s\t%s\t/%s"):format(tag.tag, tag.file, tag.line)) 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")) Util.write_file(docs .. "/tags", table.concat(lines, "\n"))
end end

View file

@ -1,31 +1,7 @@
---@class Lazy: LazyCommands ---@type LazyCommands
local M = {} local M = {}
M._start = 0 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(opts: LazyConfig)
---@overload fun(spec:LazySpec, opts: LazyConfig) ---@overload fun(spec:LazySpec, opts: LazyConfig)
function M.setup(spec, opts) function M.setup(spec, opts)
@ -37,7 +13,7 @@ function M.setup(spec, opts)
opts.spec = spec opts.spec = spec
end 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 if vim.g.lazy_did_setup then
return vim.notify( return vim.notify(
"Re-sourcing your config is not supported with lazy.nvim", "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 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" }) return vim.notify("lazy.nvim requires Neovim built with LuaJIT", vim.log.levels.ERROR, { title = "lazy.nvim" })
end 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 -- load module cache before anything else
if enable_cache then require("lazy.core.cache").setup(opts)
Cache.enable()
end
if vim.tbl_get(opts, "profiling", "require") then
profile_require()
end
require("lazy.stats").track("LazyStart") require("lazy.stats").track("LazyStart")
@ -80,18 +42,8 @@ function M.setup(spec, opts)
local Config = require("lazy.core.config") local Config = require("lazy.core.config")
local Loader = require("lazy.core.loader") 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({ plugin = "lazy.nvim" }) -- setup start
Util.track("module", vim.uv.hrtime() - start) Util.track("module", vim.loop.hrtime() - start)
-- load config -- load config
Util.track("config") Util.track("config")
@ -102,7 +54,7 @@ function M.setup(spec, opts)
Loader.setup() Loader.setup()
-- correct time delta and loaded -- correct time delta and loaded
local delta = vim.uv.hrtime() - start local delta = vim.loop.hrtime() - start
Util.track().time = delta -- end setup Util.track().time = delta -- end setup
if Config.plugins["lazy.nvim"] then if Config.plugins["lazy.nvim"] then
Config.plugins["lazy.nvim"]._.loaded = { time = delta, source = "init.lua" } Config.plugins["lazy.nvim"]._.loaded = { time = delta, source = "init.lua" }
@ -112,7 +64,7 @@ function M.setup(spec, opts)
Loader.startup() Loader.startup()
-- all done! -- all done!
vim.api.nvim_exec_autocmds("User", { pattern = "LazyDone", modeline = false }) vim.cmd("do User LazyDone")
require("lazy.stats").track("LazyDone") require("lazy.stats").track("LazyDone")
end end
@ -122,7 +74,7 @@ end
function M.bootstrap() function M.bootstrap()
local lazypath = vim.fn.stdpath("data") .. "/lazy/lazy.nvim" 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({ vim.fn.system({
"git", "git",
"clone", "clone",

View file

@ -1,9 +1,8 @@
local Config = require("lazy.core.config") local Config = require("lazy.core.config")
local Git = require("lazy.manage.git")
local Manage = require("lazy.manage") local Manage = require("lazy.manage")
local Plugin = require("lazy.core.plugin")
local State = require("lazy.state")
local Util = require("lazy.util") local Util = require("lazy.util")
local Plugin = require("lazy.core.plugin")
local Git = require("lazy.manage.git")
local M = {} local M = {}
@ -13,31 +12,14 @@ M.reported = {}
function M.start() function M.start()
M.fast_check() M.fast_check()
if M.schedule() > 0 and not M.has_errors() then M.check()
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
end end
---@param opts? {report:boolean} report defaults to true ---@param opts? {report:boolean} report defaults to true
function M.fast_check(opts) function M.fast_check(opts)
opts = opts or {} opts = opts or {}
for _, plugin in pairs(Config.plugins) do for _, plugin in pairs(Config.plugins) do
-- don't check local plugins here, since we mark them as needing updates if not plugin.pin and plugin._.installed then
-- only if local is behind upstream (if the git log task gives no output)
if plugin._.installed and not (plugin.pin or plugin._.is_local) then
plugin._.updates = nil plugin._.updates = nil
local info = Git.info(plugin.dir) local info = Git.info(plugin.dir)
local ok, target = pcall(Git.get_target, plugin) local ok, target = pcall(Git.get_target, plugin)
@ -49,28 +31,23 @@ function M.fast_check(opts)
M.report(opts.report ~= false) M.report(opts.report ~= false)
end end
function M.has_errors() function M.check()
local errors = false
for _, plugin in pairs(Config.plugins) do for _, plugin in pairs(Config.plugins) do
if Plugin.has_errors(plugin) then if Plugin.has_errors(plugin) then
return true errors = true
break
end end
end end
return false if errors then
end vim.defer_fn(M.check, Config.options.checker.frequency * 1000)
function M.check()
State.checker.last_check = os.time()
State.write() -- update state
if M.has_errors() then
M.schedule()
else else
Manage.check({ Manage.check({
clear = false,
show = false, show = false,
concurrency = Config.options.checker.concurrency, concurrency = Config.options.checker.concurrency,
}):wait(function() }):wait(function()
M.report() M.report()
M.schedule() vim.defer_fn(M.check, Config.options.checker.frequency * 1000)
end) end)
end end
end end
@ -88,7 +65,7 @@ function M.report(notify)
end end
end end
end end
if #lines > 0 and notify and Config.options.checker.notify and not Config.headless() then if #lines > 0 and notify and Config.options.checker.notify then
table.insert(lines, 1, "# Plugin Updates") table.insert(lines, 1, "# Plugin Updates")
Util.info(lines) Util.info(lines)
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 Config = require("lazy.core.config")
local Process = require("lazy.manage.process") local Process = require("lazy.manage.process")
local Semver = require("lazy.manage.semver")
local Util = require("lazy.util")
local M = {} local M = {}
@ -116,12 +116,6 @@ end
---@param plugin LazyPlugin ---@param plugin LazyPlugin
---@return GitInfo? ---@return GitInfo?
function M.get_target(plugin) 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)) local branch = assert(M.get_branch(plugin))
if plugin.commit then if plugin.commit then
@ -137,8 +131,7 @@ function M.get_target(plugin)
commit = M.ref(plugin.dir, "tags/" .. plugin.tag), commit = M.ref(plugin.dir, "tags/" .. plugin.tag),
} }
end end
local version = plugin.version == nil and Config.options.defaults.version or plugin.version
local version = (plugin.version == nil and plugin.branch == nil) and Config.options.defaults.version or plugin.version
if version then if version then
local last = Semver.last(M.get_versions(plugin.dir, version)) local last = Semver.last(M.get_versions(plugin.dir, version))
if last then if last then
@ -150,6 +143,7 @@ function M.get_target(plugin)
} }
end end
end end
---@diagnostic disable-next-line: return-type-mismatch
return { branch = branch, commit = M.get_commit(plugin.dir, branch, true) } return { branch = branch, commit = M.get_commit(plugin.dir, branch, true) }
end end
@ -189,12 +183,7 @@ function M.get_tag_refs(repo, tagref)
tagref = tagref or "--tags" tagref = tagref or "--tags"
---@type table<string,string> ---@type table<string,string>
local tags = {} local tags = {}
local ok, lines = pcall(function() local lines = Process.exec({ "git", "show-ref", "-d", tagref }, { cwd = repo })
return Process.exec({ "git", "show-ref", "-d", tagref }, { cwd = repo })
end)
if not ok then
return {}
end
for _, line in ipairs(lines) do for _, line in ipairs(lines) do
local ref, tag = line:match("^(%w+) refs/tags/([^%^]+)%^?{?}?$") local ref, tag = line:match("^(%w+) refs/tags/([^%^]+)%^?{?}?$")
if ref then if ref then
@ -204,46 +193,4 @@ function M.get_tag_refs(repo, tagref)
return tags return tags
end 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 return M

View file

@ -1,6 +1,6 @@
local Config = require("lazy.core.config") local Config = require("lazy.core.config")
local Plugin = require("lazy.core.plugin")
local Runner = require("lazy.manage.runner") local Runner = require("lazy.manage.runner")
local Plugin = require("lazy.core.plugin")
local M = {} local M = {}
@ -18,13 +18,6 @@ local M = {}
function M.run(ropts, opts) function M.run(ropts, opts)
opts = opts or {} 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 if opts.plugins then
---@param plugin string|LazyPlugin ---@param plugin string|LazyPlugin
opts.plugins = vim.tbl_map(function(plugin) opts.plugins = vim.tbl_map(function(plugin)
@ -49,15 +42,16 @@ function M.run(ropts, opts)
local runner = Runner.new(ropts) local runner = Runner.new(ropts)
runner:start() runner:start()
vim.api.nvim_exec_autocmds("User", { pattern = "LazyRender", modeline = false }) vim.cmd([[do User LazyRender]])
-- wait for post-install to finish -- wait for post-install to finish
runner:wait(function() runner:wait(function()
vim.api.nvim_exec_autocmds("User", { pattern = "LazyRender", modeline = false }) vim.cmd([[do User LazyRender]])
Plugin.update_state() Plugin.update_state()
require("lazy.manage.checker").fast_check({ report = false }) require("lazy.manage.checker").fast_check({ report = false })
if event then local mode = opts.mode
vim.api.nvim_exec_autocmds("User", { pattern = event, modeline = false }) if mode then
vim.cmd("do User Lazy" .. mode:sub(1, 1):upper() .. mode:sub(2))
end end
end) end)
@ -80,23 +74,14 @@ function M.install(opts)
opts = M.opts(opts, { mode = "install" }) opts = M.opts(opts, { mode = "install" })
return M.run({ return M.run({
pipeline = { pipeline = {
"plugin.exists",
"git.clone", "git.clone",
{ "git.checkout", lockfile = opts.lockfile }, { "git.checkout", lockfile = opts.lockfile },
"plugin.docs", "plugin.docs",
{
"wait", "wait",
---@param runner Runner
sync = function(runner)
require("lazy.pkg").update()
Plugin.load()
runner:update()
end,
},
"plugin.build", "plugin.build",
}, },
plugins = function(plugin) plugins = function(plugin)
return not (plugin._.installed and not plugin._.build) return plugin.url and not plugin._.installed
end, end,
}, opts):wait(function() }, opts):wait(function()
require("lazy.manage.lock").update() require("lazy.manage.lock").update()
@ -109,22 +94,11 @@ function M.update(opts)
opts = M.opts(opts, { mode = "update" }) opts = M.opts(opts, { mode = "update" })
return M.run({ return M.run({
pipeline = { pipeline = {
"plugin.exists",
"git.origin",
"git.branch", "git.branch",
"git.fetch", "git.fetch",
"git.status",
{ "git.checkout", lockfile = opts.lockfile }, { "git.checkout", lockfile = opts.lockfile },
"plugin.docs", "plugin.docs",
{
"wait", "wait",
---@param runner Runner
sync = function(runner)
require("lazy.pkg").update()
Plugin.load()
runner:update()
end,
},
"plugin.build", "plugin.build",
{ "git.log", updated = true }, { "git.log", updated = true },
}, },
@ -149,10 +123,7 @@ function M.check(opts)
opts = opts or {} opts = opts or {}
return M.run({ return M.run({
pipeline = { pipeline = {
"plugin.exists",
{ "git.origin", check = true },
"git.fetch", "git.fetch",
"git.status",
"wait", "wait",
{ "git.log", check = true }, { "git.log", check = true },
}, },
@ -162,14 +133,11 @@ function M.check(opts)
}, opts) }, opts)
end end
---@param opts? ManagerOpts | {check?:boolean} ---@param opts? ManagerOpts
function M.log(opts) function M.log(opts)
opts = M.opts(opts, { mode = "log" }) opts = M.opts(opts, { mode = "log" })
return M.run({ return M.run({
pipeline = { pipeline = { "git.log" },
{ "git.origin", check = true },
{ "git.log", check = opts.check },
},
plugins = function(plugin) plugins = function(plugin)
return plugin.url and plugin._.installed return plugin.url and plugin._.installed
end, end,
@ -200,18 +168,13 @@ function M.sync(opts)
end) end)
opts.show = false opts.show = false
end end
local clean = M.clean(opts)
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 install = M.install(opts) local install = M.install(opts)
local update = M.update(opts) local update = M.update(opts)
clean:wait(function() clean:wait(function()
install:wait(function() install:wait(function()
update:wait(function() update:wait(function()
vim.api.nvim_exec_autocmds("User", { pattern = "LazySync", modeline = false }) vim.cmd([[do User LazySync]])
end) end)
end) end)
end) end)
@ -239,11 +202,11 @@ function M.clear(plugins)
if plugin._.tasks then if plugin._.tasks then
---@param task LazyTask ---@param task LazyTask
plugin._.tasks = vim.tbl_filter(function(task) plugin._.tasks = vim.tbl_filter(function(task)
return task:running() or task:has_errors() return task:is_running()
end, plugin._.tasks) end, plugin._.tasks)
end end
end end
vim.api.nvim_exec_autocmds("User", { pattern = "LazyRender", modeline = false }) vim.cmd([[do User LazyRender]])
end end
return M return M

View file

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

View file

@ -1,239 +1,190 @@
local Async = require("lazy.async")
local Config = require("lazy.core.config") 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 ---@diagnostic disable-next-line: no-unknown
local uv = vim.uv local uv = vim.loop
---@class ProcessOpts ---@class ProcessOpts
---@field args string[] ---@field args string[]
---@field cwd? 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_exit? fun(ok:boolean, output:string)
---@field on_data? fun(data:string, is_stderr?:boolean)
---@field timeout? number ---@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 ---@param opts? ProcessOpts
function Process.new(cmd, opts) ---@param cmd string
local self = setmetatable({}, { __index = Process }) function M.spawn(cmd, opts)
---@async
Process.init(self, function()
self:_run()
end)
opts = opts or {} 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) 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 local env = {
opts.cwd = uv.os_homedir() "GIT_TERMINAL_PROMPT=0",
end "GIT_SSH_COMMAND=ssh -oBatchMode=yes",
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 if opts.env then
self.data = "" vim.list_extend(env, opts.env)
self.opts = opts
self.code = 1
self.signal = 0
return self
end end
---@async for key, value in
function Process:_run() pairs(uv.os_environ() --[[@as string[] ]])
self:guard() do
local stdout = assert(uv.new_pipe()) table.insert(env, key .. "=" .. value)
local stderr = assert(uv.new_pipe())
self.handle = uv.spawn(self.cmd, {
stdio = { nil, stdout, stderr },
args = self.opts.args,
cwd = self.opts.cwd,
env = self:env(),
}, function(code, signal)
self.code = code
self.signal = signal
if self.timeout then
self.timeout:stop()
end end
self.handle:close()
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 = opts.args,
cwd = opts.cwd,
env = env,
}, function(exit_code, signal)
M.running[handle] = nil
if timeout then
timeout:stop()
timeout:close()
end
handle:close()
stdout:close() stdout:close()
stderr:close() stderr:close()
self:resume() local check = uv.new_check()
end) check:start(function()
if not stdout:is_closing() or not stderr:is_closing() then
if self.handle then
M.running[self.handle] = self
stdout:read_start(function(err, data)
self:on_data(err, data)
end)
stderr:read_start(function(err, data)
self:on_data(err, data, true)
end)
self:suspend()
while not (self.handle:is_closing() and stdout:is_closing() and stderr:is_closing()) do
Async.yield()
end
else
self.data = "Failed to spawn process " .. self.cmd .. " " .. vim.inspect(self.opts)
end
self:on_exit()
end
function Process:on_exit()
self.data = self.data:gsub("[^\r\n]+\r", "")
if self.timedout then
self.data = self.data .. "\n" .. "Process was killed because it reached the timeout"
elseif self.signal ~= 0 then
self.data = self.data .. "\n" .. "Process was killed with SIG" .. M.signals[self.signal]:upper()
end
if self.opts.on_exit then
self.opts.on_exit(self.code == 0 and self.signal == 0, self.data)
end
end
function Process:guard()
if self.opts.timeout then
self.timeout = assert(uv.new_timer())
self.timeout:start(self.opts.timeout, 0, function()
self.timedout = true
self:kill()
end)
end
end
function Process:env()
---@type table<string, string>
local env = vim.tbl_extend("force", {
GIT_SSH_COMMAND = "ssh -oBatchMode=yes",
}, uv.os_environ(), self.opts.env or {})
env.GIT_DIR = nil
env.GIT_WORK_TREE = nil
env.GIT_TERMINAL_PROMPT = "0"
env.GIT_INDEX_FILE = nil
---@type string[]
local env_flat = {}
for k, v in pairs(env) do
env_flat[#env_flat + 1] = k .. "=" .. v
end
return env_flat
end
---@param signals uv.aliases.signals|uv.aliases.signals[]|nil
function Process:kill(signals)
if not self.handle or self.handle:is_closing() then
return return
end end
signals = signals or { "sigterm", "sigkill" } check:stop()
signals = type(signals) == "table" and signals or { signals } if opts.on_exit then
---@cast signals uv.aliases.signals[] output = output:gsub("[^\r\n]+\r", "")
local timer = assert(uv.new_timer()) if killed then
timer:start(0, 1000, function() output = output .. "\n" .. "Process was killed because it reached the timeout"
if self.handle and not self.handle:is_closing() and #signals > 0 then elseif signal ~= 0 then
self.handle:kill(table.remove(signals, 1)) output = output .. "\n" .. "Process was killed with SIG" .. M.signals[signal]
else
timer:stop()
end
end)
end end
---@param err? string vim.schedule(function()
opts.on_exit(exit_code == 0 and signal == 0, output)
end)
end
end)
end)
if not handle then
if opts.on_exit then
opts.on_exit(false, "Failed to spawn process " .. cmd .. " " .. vim.inspect(opts))
end
return
end
M.running[handle] = true
---@param data? string ---@param data? string
---@param is_stderr? boolean local function on_output(err, data)
function Process:on_data(err, data, is_stderr)
assert(not err, err) assert(not err, err)
if not data then
return
end
if self.opts.on_data then if data then
self.opts.on_data(data, is_stderr) output = output .. data:gsub("\r\n", "\n")
end local lines = vim.split(vim.trim(output:gsub("\r$", "")):gsub("[^\n\r]+\r", ""), "\n")
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 if opts.on_line then
self.opts.on_line(lines[#lines]) vim.schedule(function()
opts.on_line(lines[#lines])
end)
end
end end
end end
M.signals = { uv.read_start(stdout, on_output)
"hup", uv.read_start(stderr, on_output)
"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[] return handle
---@param opts? ProcessOpts end
function M.spawn(cmd, opts)
return Process.new(cmd, opts) function M.kill(handle)
if handle and not handle:is_closing() then
M.running[handle] = nil
uv.process_kill(handle, "sigint")
return true
end
end end
function M.abort() function M.abort()
for _, proc in pairs(M.running) do for handle in pairs(M.running) do
proc:kill() M.kill(handle)
end end
end end
---@async ---@param cmd string[]
---@param cmd string|string[] ---@param opts? {cwd:string, env:table}
---@param opts? ProcessOpts
function M.exec(cmd, opts) function M.exec(cmd, opts)
opts = opts or {} opts = opts or {}
local proc = M.spawn(cmd, opts) ---@type string[]
proc:wait() local lines
return vim.split(proc.data, "\n"), proc.code 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 end
return M return M

View file

@ -1,22 +1,25 @@
local Cache = require("lazy.core.cache")
local Config = require("lazy.core.config") local Config = require("lazy.core.config")
local Loader = require("lazy.core.loader")
local Plugin = require("lazy.core.plugin")
local Util = require("lazy.util") local Util = require("lazy.util")
local Plugin = require("lazy.core.plugin")
local Loader = require("lazy.core.loader")
local M = {} local M = {}
---@type table<string, uv.aliases.fs_stat_table> ---@type table<string, CacheHash>
M.files = {} M.files = {}
---@type uv_timer_t ---@type vim.loop.Timer
M.timer = nil M.timer = nil
M.root = nil
function M.enable() function M.enable()
if M.timer then if M.timer then
M.timer:stop() M.timer:stop()
end end
if #Config.spec.modules > 0 then 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.check(true)
M.timer:start(2000, 2000, M.check) M.timer:start(2000, 2000, M.check)
end end
@ -29,12 +32,6 @@ function M.disable()
end end
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) function M.check(start)
---@type table<string,true> ---@type table<string,true>
local checked = {} local checked = {}
@ -44,10 +41,10 @@ function M.check(start)
-- spec is a module -- spec is a module
local function check(_, modpath) local function check(_, modpath)
checked[modpath] = true checked[modpath] = true
local hash = vim.uv.fs_stat(modpath) local hash = Cache.hash(modpath)
if hash then if hash then
if M.files[modpath] 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 M.files[modpath] = hash
table.insert(changes, { file = modpath, what = "changed" }) table.insert(changes, { file = modpath, what = "changed" })
end end
@ -76,22 +73,9 @@ function M.check(start)
Config.mapleader = vim.g.mapleader Config.mapleader = vim.g.mapleader
end 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 if not (start or #changes == 0) then
M.reload(changes)
end
end
---@param {file:string, what:string}[]
function M.reload(changes)
vim.schedule(function() vim.schedule(function()
if Config.options.change_detection.notify and not Config.headless() then if Config.options.change_detection.notify then
local lines = { "# Config Change Detected. Reloading...", "" } local lines = { "# Config Change Detected. Reloading...", "" }
for _, change in ipairs(changes) do for _, change in ipairs(changes) do
table.insert(lines, "- **" .. change.what .. "**: `" .. vim.fn.fnamemodify(change.file, ":p:~:.") .. "`") table.insert(lines, "- **" .. change.what .. "**: `" .. vim.fn.fnamemodify(change.file, ":p:~:.") .. "`")
@ -99,9 +83,10 @@ function M.reload(changes)
Util.warn(lines) Util.warn(lines)
end end
Plugin.load() Plugin.load()
vim.api.nvim_exec_autocmds("User", { pattern = "LazyRender", modeline = false }) vim.cmd([[do User LazyRender]])
vim.api.nvim_exec_autocmds("User", { pattern = "LazyReload", modeline = false }) vim.cmd([[do User LazyReload]])
end) end)
end end
end
return M 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 Task = require("lazy.manage.task")
local Config = require("lazy.core.config")
local Util = require("lazy.util")
---@class RunnerOpts ---@class RunnerOpts
---@field pipeline (string|{[1]:string, [string]:any})[] ---@field pipeline (string|{[1]:string, [string]:any})[]
---@field plugins? LazyPlugin[]|fun(plugin:LazyPlugin):any? ---@field plugins? LazyPlugin[]|fun(plugin:LazyPlugin):any?
---@field concurrency? number ---@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 ---@class Runner
---@field _plugins table<string,LazyPlugin> ---@field _plugins LazyPlugin[]
---@field _running LazyRunnerTask[]
---@field _pipeline PipelineStep[] ---@field _pipeline PipelineStep[]
---@field _on_done fun()[]
---@field _opts RunnerOpts ---@field _opts RunnerOpts
---@field _running? Async
local Runner = {} local Runner = {}
---@param opts RunnerOpts ---@param opts RunnerOpts
@ -26,17 +24,13 @@ function Runner.new(opts)
self._opts = opts or {} self._opts = opts or {}
local plugins = self._opts.plugins local plugins = self._opts.plugins
---@type LazyPlugin[]
local pp = {}
if type(plugins) == "function" then if type(plugins) == "function" then
pp = vim.tbl_filter(plugins, Config.plugins) self._plugins = vim.tbl_filter(plugins, Config.plugins)
else else
pp = plugins or Config.plugins self._plugins = plugins or Config.plugins
end
self._plugins = {}
for _, plugin in ipairs(pp) do
self._plugins[plugin.name] = plugin
end end
self._running = {}
self._on_done = {}
---@param step string|(TaskOptions|{[1]:string}) ---@param step string|(TaskOptions|{[1]:string})
self._pipeline = vim.tbl_map(function(step) self._pipeline = vim.tbl_map(function(step)
@ -46,152 +40,118 @@ function Runner.new(opts)
return self return self
end end
function Runner:plugin(name) ---@param entry LazyRunnerTask
return self._plugins[name] 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 end
--- Update plugins function Runner:resume(waiting)
function Runner:update() local running = 0
for name in pairs(self._plugins) do for _, entry in ipairs(self._running) do
self._plugins[name] = Config.plugins[name] or self._plugins[name] if entry.status then
if waiting and entry.status.waiting then
entry.status.waiting = false
end end
if not entry.status.waiting and self:_resume(entry) then
running = running + 1
if self._opts.concurrency and running >= self._opts.concurrency then
break
end
end
end
end
return running > 0 or (not waiting and self:resume(true))
end end
function Runner:start() function Runner:start()
---@async for _, plugin in pairs(self._plugins) do
self._running = Async.new(function() local co = coroutine.create(self.run_pipeline)
self:_start() local ok, err = coroutine.resume(co, self, plugin)
if ok then
table.insert(self._running, { co = co, status = {} })
else
Util.error("Could not start tasks for " .. plugin.name .. "\n" .. err)
end
end
local check = vim.loop.new_check()
check:start(function()
if self:resume() then
return
end
check:stop()
self._running = {}
for _, cb in ipairs(self._on_done) do
vim.schedule(cb)
end
self._on_done = {}
end) end)
end end
---@async ---@async
function Runner:_start() ---@param plugin LazyPlugin
---@type string[] function Runner:run_pipeline(plugin)
local names = vim.tbl_keys(self._plugins) coroutine.yield()
table.sort(names) for _, step in ipairs(self._pipeline) do
---@type table<string,RunnerTask>
local state = {}
local active = 1
local waiting = 0
---@type number?
local wait_step = nil
---@async
---@param resume? boolean
local function continue(resume)
active = 0
waiting = 0
wait_step = nil
local next = {} ---@type string[]
-- check running tasks
for _, name in ipairs(names) do
state[name] = state[name] or { step = 0 }
local s = state[name]
local is_running = s.task and s.task:running()
local step = self._pipeline[s.step]
if is_running then
-- still running
active = active + 1
-- selene:allow(empty_if)
elseif s.task and s.task:has_errors() then
-- don't continue tasks if there are errors
elseif step and step.task == "wait" and not resume then
-- waiting for sync
waiting = waiting + 1
wait_step = s.step
else
next[#next + 1] = name
end
end
-- schedule next tasks
for _, name in ipairs(next) do
if self._opts.concurrency and active >= self._opts.concurrency then
break
end
local s = state[name]
local plugin = self:plugin(name)
while s.step <= #self._pipeline do
if s.step == #self._pipeline then
-- done
s.task = nil
plugin._.working = false
break
elseif s.step < #self._pipeline then
-- next
s.step = s.step + 1
local step = self._pipeline[s.step]
if step.task == "wait" then if step.task == "wait" then
plugin._.working = false coroutine.yield({ waiting = true })
waiting = waiting + 1
wait_step = s.step
break
else else
s.task = self:queue(plugin, step) local task = self:queue(plugin, step.task, step.opts)
plugin._.working = true if task then
if s.task then coroutine.yield({ task = task })
active = active + 1 assert(task:is_done())
s.task:wake(false) if task.error then
break return
end end
end end
end end
end end
end end
end
while active > 0 do
continue()
if active == 0 and waiting > 0 then
local sync = self._pipeline[wait_step]
if sync and sync.opts and type(sync.opts.sync) == "function" then
sync.opts.sync(self)
end
continue(true)
end
if active > 0 then
self._running:suspend()
end
end
end
---@param plugin LazyPlugin ---@param plugin LazyPlugin
---@param step PipelineStep ---@param task_name string
---@param opts? TaskOptions
---@return LazyTask? ---@return LazyTask?
function Runner:queue(plugin, step) function Runner:queue(plugin, task_name, opts)
assert(self._running and self._running:running(), "Runner is not running") assert(self._running)
local def = vim.split(step.task, ".", { plain = true }) local def = vim.split(task_name, ".", { plain = true })
---@type LazyTaskDef ---@type LazyTaskDef
local task_def = require("lazy.manage.task." .. def[1])[def[2]] local task_def = require("lazy.manage.task." .. def[1])[def[2]]
assert(task_def, "Task not found: " .. step.task) assert(task_def)
local opts = step.opts or {} opts = opts or {}
if not (task_def.skip and task_def.skip(plugin, opts)) then 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
end end
function Runner:is_running()
return self._running and self._running:running()
end
-- Execute the callback async when done. -- Execute the callback async when done.
-- When no callback is specified, this will wait sync -- When no callback is specified, this will wait sync
---@param cb? fun() ---@param cb? fun()
function Runner:wait(cb) function Runner:wait(cb)
if not self:is_running() then if #self._running == 0 then
if cb then if cb then
cb() cb()
end end
return self return self
end end
if cb then if cb then
self._running:on("done", cb) table.insert(self._on_done, cb)
else else
self._running:wait() -- sync wait
while #self._running > 0 do
vim.wait(10)
end
end end
return self return self
end end

View file

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

View file

@ -1,42 +1,28 @@
local Config = require("lazy.core.config")
local Util = require("lazy.util") local Util = require("lazy.util")
local Config = require("lazy.core.config")
---@type table<string, LazyTaskDef> ---@type table<string, LazyTaskDef>
local M = {} 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 = { M.clean = {
skip = function(plugin) skip = function(plugin)
return plugin._.is_local return plugin._.is_local
end, end,
---@param opts? {rocks_only?:boolean} run = function(self)
run = function(self, opts)
opts = opts or {}
local dir = self.plugin.dir:gsub("/+$", "") local dir = self.plugin.dir:gsub("/+$", "")
assert(dir:find(Config.options.root, 1, true) == 1, self.plugin.dir .. " should be under packpath!") 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 local stat = vim.loop.fs_lstat(dir)
if vim.uv.fs_stat(rock_root) then assert(stat.type == "directory", self.plugin.dir .. " should be a directory!")
rm(rock_root)
end
if opts.rocks_only then Util.walk(dir, function(path, _, type)
return if type == "directory" then
vim.loop.fs_rmdir(path)
else
vim.loop.fs_unlink(path)
end end
end)
rm(dir) vim.loop.fs_rmdir(dir)
self.plugin._.installed = false self.plugin._.installed = false
end, 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 Git = require("lazy.manage.git")
local Lock = require("lazy.manage.lock") local Lock = require("lazy.manage.lock")
local Util = require("lazy.util") local Config = require("lazy.core.config")
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
---@type table<string, LazyTaskDef> ---@type table<string, LazyTaskDef>
local M = {} local M = {}
@ -61,13 +14,11 @@ M.log = {
if opts.updated and not (plugin._.updated and plugin._.updated.from ~= plugin._.updated.to) then if opts.updated and not (plugin._.updated and plugin._.updated.from ~= plugin._.updated.to) then
return true return true
end end
local stat = vim.uv.fs_stat(plugin.dir .. "/.git") local stat = vim.loop.fs_stat(plugin.dir .. "/.git")
return not (stat and stat.type == "directory") return stat and stat.type ~= "directory"
end, end,
---@async
---@param opts {args?: string[], updated?:boolean, check?:boolean} ---@param opts {args?: string[], updated?:boolean, check?:boolean}
run = function(self, opts) run = function(self, opts)
-- self:spawn({ "sleep", "5" })
local args = { local args = {
"log", "log",
"--pretty=format:%h %s (%cr)", "--pretty=format:%h %s (%cr)",
@ -78,13 +29,11 @@ M.log = {
"--no-show-signature", "--no-show-signature",
} }
local info, target
if opts.updated then if opts.updated then
table.insert(args, self.plugin._.updated.from .. ".." .. (self.plugin._.updated.to or "HEAD")) table.insert(args, self.plugin._.updated.from .. ".." .. (self.plugin._.updated.to or "HEAD"))
elseif opts.check then elseif opts.check then
info = assert(Git.info(self.plugin.dir)) local info = assert(Git.info(self.plugin.dir))
target = assert(Git.get_target(self.plugin)) local target = assert(Git.get_target(self.plugin))
if not target.commit then if not target.commit then
for k, v in pairs(target) do for k, v in pairs(target) do
error(k .. " '" .. v .. "' not found") error(k .. " '" .. v .. "' not found")
@ -92,18 +41,9 @@ M.log = {
error("no target commit found") error("no target commit found")
end end
assert(target.commit, self.plugin.name .. " " .. target.branch) assert(target.commit, self.plugin.name .. " " .. target.branch)
if not self.plugin._.is_local then if not Git.eq(info, target) 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 } self.plugin._.updates = { from = info, to = target }
end end
end
table.insert(args, info.commit .. ".." .. target.commit) table.insert(args, info.commit .. ".." .. target.commit)
else else
vim.list_extend(args, opts.args or Config.options.git.log) vim.list_extend(args, opts.args or Config.options.git.log)
@ -113,14 +53,6 @@ M.log = {
args = args, args = args,
cwd = self.plugin.dir, 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, end,
} }
@ -128,45 +60,20 @@ M.clone = {
skip = function(plugin) skip = function(plugin)
return plugin._.installed or plugin._.is_local return plugin._.installed or plugin._.is_local
end, end,
---@async
run = function(self) run = function(self)
throttle.wait()
local args = { local args = {
"clone", "clone",
self.plugin.url, 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 if self.plugin.branch then
vim.list_extend(args, { "-b", self.plugin.branch }) vim.list_extend(args, { "-b", self.plugin.branch })
end end
table.insert(args, self.plugin.dir) 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", { self:spawn("git", {
args = args, args = args,
on_exit = function(ok) on_exit = function(ok)
@ -174,7 +81,6 @@ M.clone = {
self.plugin._.cloned = true self.plugin._.cloned = true
self.plugin._.installed = true self.plugin._.installed = true
self.plugin._.dirty = true self.plugin._.dirty = true
vim.uv.fs_unlink(marker)
end end
end, end,
}) })
@ -189,9 +95,8 @@ M.branch = {
return true return true
end end
local branch = assert(Git.get_branch(plugin)) local branch = assert(Git.get_branch(plugin))
return Git.get_commit(plugin.dir, branch, true) return Git.get_commit(plugin.dir, branch)
end, end,
---@async
run = function(self) run = function(self)
local args = { local args = {
"remote", "remote",
@ -208,79 +113,13 @@ M.branch = {
end, 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 -- fetches all needed origin branches
M.fetch = { M.fetch = {
skip = function(plugin) skip = function(plugin)
return not plugin._.installed or plugin._.is_local or cooldown(plugin) return not plugin._.installed or plugin._.is_local
end, end,
---@async
run = function(self) run = function(self)
throttle.wait()
local args = { local args = {
"fetch", "fetch",
"--recurse-submodules", "--recurse-submodules",
@ -289,18 +128,9 @@ M.fetch = {
"--progress", "--progress",
} }
if self.plugin.submodules == false then
table.remove(args, 2)
end
self:spawn("git", { self:spawn("git", {
args = args, args = args,
cwd = self.plugin.dir, cwd = self.plugin.dir,
on_exit = function(ok)
if ok then
self.plugin._.last_check = vim.uv.now()
end
end,
}) })
end, end,
} }
@ -312,10 +142,8 @@ M.checkout = {
return not plugin._.installed or plugin._.is_local return not plugin._.installed or plugin._.is_local
end, end,
---@async
---@param opts {lockfile?:boolean} ---@param opts {lockfile?:boolean}
run = function(self, opts) run = function(self, opts)
throttle.wait()
local info = assert(Git.info(self.plugin.dir)) local info = assert(Git.info(self.plugin.dir))
local target = assert(Git.get_target(self.plugin)) local target = assert(Git.get_target(self.plugin))
@ -335,7 +163,7 @@ M.checkout = {
end end
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 -- unless we just cloned, since then we won't have any data yet
if Git.eq(info, target) and info.branch == target.branch then if Git.eq(info, target) and info.branch == target.branch then
self.plugin._.updated = { self.plugin._.updated = {
@ -351,10 +179,6 @@ M.checkout = {
"--recurse-submodules", "--recurse-submodules",
} }
if self.plugin.submodules == false then
table.remove(args, 3)
end
if lock then if lock then
table.insert(args, lock.commit) table.insert(args, lock.commit)
elseif target.tag then 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 Process = require("lazy.manage.process")
local Terminal = require("lazy.terminal")
local colors = Config.options.headless.colors
---@class LazyTaskDef ---@class LazyTaskDef
---@field skip? fun(plugin:LazyPlugin, opts?:TaskOptions):any? ---@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 ---@class LazyTask
---@field msg string
---@field level? number
---@class LazyTask: Async
---@field plugin LazyPlugin ---@field plugin LazyPlugin
---@field name string ---@field name string
---@field private _log LazyMsg[] ---@field output string
---@field private _started number ---@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 _ended? number
---@field private _opts TaskOptions ---@field private _opts TaskOptions
---@field private _level number local Task = {}
local Task = setmetatable({}, { __index = Async.Async })
---@class TaskOptions: {[string]:any} ---@class TaskOptions: {[string]:any}
---@field on_done? fun(task:LazyTask) ---@field on_done? fun(task:LazyTask)
@ -31,210 +25,146 @@ local Task = setmetatable({}, { __index = Async.Async })
---@param plugin LazyPlugin ---@param plugin LazyPlugin
---@param name string ---@param name string
---@param opts? TaskOptions ---@param opts? TaskOptions
---@param task LazyTaskFn ---@param task fun(task:LazyTask)
function Task.new(plugin, name, task, opts) function Task.new(plugin, name, task, opts)
local self = setmetatable({}, { __index = Task }) local self = setmetatable({}, {
---@async __index = Task,
Task.init(self, function() })
self:_run(task)
end)
self:set_level()
self._opts = opts or {} self._opts = opts or {}
self._log = {} self._running = {}
self._task = task
self._started = nil
self.plugin = plugin self.plugin = plugin
self.name = name self.name = name
self._started = vim.uv.hrtime() self.output = ""
self.status = ""
plugin._.tasks = plugin._.tasks or {}
---@param other LazyTask ---@param other LazyTask
plugin._.tasks = vim.tbl_filter(function(other) plugin._.tasks = vim.tbl_filter(function(other)
return other.name ~= name or other:running() return other.name ~= name or other:is_running()
end, plugin._.tasks or {}) end, plugin._.tasks)
table.insert(plugin._.tasks, self) table.insert(plugin._.tasks, self)
self:render()
return self return self
end end
---@param level? number function Task:has_started()
---@return LazyMsg[] return self._started ~= nil
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)
end end
---@param level? number function Task:is_done()
function Task:output(level) return self:has_started() and not self:is_running()
return table.concat(
---@param m LazyMsg
vim.tbl_map(function(m)
return m.msg
end, self:get_log(level)),
"\n"
)
end end
function Task:status() function Task:is_running()
local ret = self._log[#self._log] return self:has_started() and self._ended == nil
local msg = ret and vim.trim(ret.msg) or ""
return msg ~= "" and msg or nil
end end
function Task:has_errors() function Task:start()
return self._level >= vim.log.levels.ERROR if vim.in_fast_event() then
end return vim.schedule(function()
self:start()
function Task:has_warnings()
return self._level >= vim.log.levels.WARN
end
---@param level? number
function Task:set_level(level)
self._level = level or vim.log.levels.TRACE
end
---@async
---@param task LazyTaskFn
function Task:_run(task)
if Config.headless() and Config.options.headless.task then
self:log("Running task " .. self.name, vim.log.levels.INFO)
end
self
:on("done", function()
self:_done()
end)
:on("error", function(err)
self:error(err)
end)
:on("yield", function(msg)
self:log(msg)
end)
task(self, self._opts)
end
---@param msg string|string[]|LazyMsg
---@param level? number
function Task:log(msg, level)
if type(msg) == "table" and msg.msg then
level = msg.level or level
msg = msg.msg
end
level = level or vim.log.levels.DEBUG
self._level = math.max(self._level or 0, level or 0)
msg = type(msg) == "table" and table.concat(msg, "\n") or msg
---@cast msg string
table.insert(self._log, { msg = msg, level = level })
self:render()
if Config.headless() then
self:headless()
end
end
function Task:render()
vim.schedule(function()
vim.api.nvim_exec_autocmds("User", { pattern = "LazyRender", modeline = false })
end) end)
end end
self._started = vim.loop.hrtime()
function Task:headless() ---@type boolean, string|any
if not Config.options.headless.log then local ok, err = pcall(self._task, self, self._opts)
return if not ok then
self.error = err or "failed"
end end
local msg = self._log[#self._log] self:_check()
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)
end end
---@private ---@private
function Task:_done() function Task:_check()
if Config.headless() and Config.options.headless.task then for _, state in ipairs(self._running) do
local ms = math.floor(self:time() + 0.5) if state() then
self:log("Finished task " .. self.name .. " in " .. ms .. "ms", vim.log.levels.INFO) return
end end
self._ended = vim.uv.hrtime() end
self._ended = vim.loop.hrtime()
if self._opts.on_done then if self._opts.on_done then
self._opts.on_done(self) self._opts.on_done(self)
end end
self:render() vim.cmd("do User LazyRender")
vim.schedule(function()
vim.api.nvim_exec_autocmds("User", { vim.api.nvim_exec_autocmds("User", {
pattern = "LazyPlugin" .. self.name:sub(1, 1):upper() .. self.name:sub(2), pattern = "LazyPlugin" .. self.name:sub(1, 1):upper() .. self.name:sub(2),
data = { plugin = self.plugin.name }, data = { plugin = self.plugin.name },
}) })
end)
end end
function Task:time() 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 end
---@async
---@param cmd string ---@param cmd string
---@param opts? ProcessOpts ---@param opts? ProcessOpts
function Task:spawn(cmd, opts) function Task:spawn(cmd, opts)
opts = opts or {} opts = opts or {}
local on_line = opts.on_line local on_line = opts.on_line
local on_exit = opts.on_exit
local headless = Config.headless() and Config.options.headless.process
function opts.on_line(line) function opts.on_line(line)
if not headless then self.status = line
return self:log(line, vim.log.levels.TRACE)
end
if on_line then if on_line then
pcall(on_line, line) pcall(on_line, line)
end end
vim.cmd("do User LazyRender")
end end
if headless then ---@param output string
opts.on_data = function(data) function opts.on_exit(ok, output)
-- prefix with plugin name self.output = self.output .. output
io.write(Terminal.prefix(data, self:prefix())) if not ok then
self.error = self.error and (self.error .. "\n" .. output) or output
end end
if on_exit then
pcall(on_exit, ok, output)
end
self:_check()
end end
local proc = Process.spawn(cmd, opts) local proc = Process.spawn(cmd, opts)
proc:wait() table.insert(self._running, function()
return proc and not proc:is_closing()
local ok = proc.code == 0 and proc.signal == 0 end)
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 end
if opts.on_exit then ---@param tasks (LazyTask?)[]
pcall(opts.on_exit, ok, proc.data) function Task.all_done(tasks)
for _, task in ipairs(tasks) do
if task and not task:is_done() then
return false
end end
return ok end
return true
end end
function Task:prefix() function Task:wait()
local plugin = "[" .. self.plugin.name .. "] " while self:is_running() do
local task = string.rep(" ", 20 - #(self.name .. self.plugin.name)) .. self.name vim.wait(10)
end
return colors and Terminal.magenta(plugin) .. Terminal.cyan(task) .. Terminal.bright_black(" | ")
or plugin .. " " .. task .. " | "
end end
return Task 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 Util = require("lazy.util")
local Loader = require("lazy.core.loader")
---@type table<string, LazyTaskDef> ---@type table<string, LazyTaskDef>
local M = {} 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 = { M.build = {
---@param opts? {force:boolean} ---@param opts? {force:boolean}
skip = function(plugin, opts) skip = function(plugin, opts)
if opts and opts.force then if opts and opts.force then
return false return false
end end
return not ((plugin._.dirty or plugin._.build) and (plugin.build or get_build_file(plugin))) return not (plugin._.dirty and plugin.build)
end, end,
---@async
run = function(self) run = function(self)
vim.cmd([[silent! runtime plugin/rplugin.vim]]) Loader.load(self.plugin, { task = "build" })
local builders = self.plugin.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 if builders then
builders = type(builders) == "table" and builders or { builders } builders = type(builders) == "table" and builders or { builders }
---@cast builders (string|fun(LazyPlugin))[] ---@cast builders (string|fun(LazyPlugin))[]
for _, build in ipairs(builders) do 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) 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 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 end
end end
@ -87,23 +41,12 @@ M.build = {
M.docs = { M.docs = {
skip = function(plugin) skip = function(plugin)
return not plugin._.is_local and not plugin._.dirty return not plugin._.dirty
end, end,
run = function(self) run = function(self)
local docs = self.plugin.dir .. "/doc" local docs = self.plugin.dir .. "/doc/"
if Util.file_exists(docs) then if Util.file_exists(docs) then
self:log(vim.api.nvim_cmd({ cmd = "helptags", args = { docs } }, { output = true })) self.output = 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 .. "`")
end end
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 = { M._stats = {
-- startuptime in milliseconds till UIEnter -- startuptime in milliseconds till UIEnter
startuptime = 0, 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 -- 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. -- when false, startuptime is calculated based on a delta with a timestamp when lazy started.
real_cputime = false, real_cputime = false,
@ -21,8 +21,7 @@ M.C = nil
function M.on_ui_enter() function M.on_ui_enter()
M._stats.startuptime = M.track("UIEnter") M._stats.startuptime = M.track("UIEnter")
require("lazy.core.util").track({ start = "startuptime" }, M._stats.startuptime * 1e6) vim.cmd([[do User LazyVimStarted]])
vim.api.nvim_exec_autocmds("User", { pattern = "LazyVimStarted", modeline = false })
end end
function M.track(event) function M.track(event)
@ -51,11 +50,11 @@ function M.cputime()
local pnano = assert(ffi.new("nanotime[?]", 1)) local pnano = assert(ffi.new("nanotime[?]", 1))
local CLOCK_PROCESS_CPUTIME_ID = jit.os == "OSX" and 12 or 2 local CLOCK_PROCESS_CPUTIME_ID = jit.os == "OSX" and 12 or 2
ffi.C.clock_gettime(CLOCK_PROCESS_CPUTIME_ID, pnano) 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 end
local function fallback() local function fallback()
return (vim.uv.hrtime() - require("lazy")._start) / 1e6 return (vim.loop.hrtime() - require("lazy")._start) / 1e6
end end
local ok, ret = pcall(real) 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" ---@alias LazyPluginKind "normal"|"clean"|"disabled"
---@class LazyPluginState ---@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 loaded? {[string]:string}|{time:number}
---@field outdated? boolean ---@field installed boolean
---@field rtp_loaded? boolean
---@field tasks? LazyTask[] ---@field tasks? LazyTask[]
---@field dirty? boolean
---@field updated? {from:string, to:string} ---@field updated? {from:string, to:string}
---@field is_local boolean
---@field updates? {from:GitInfo, to:GitInfo} ---@field updates? {from:GitInfo, to:GitInfo}
---@field last_check? number ---@field cloned? boolean
---@field working? boolean ---@field kind? LazyPluginKind
---@field pkg? LazyPkg ---@field dep? boolean True if this plugin is only in the spec as a dependency
---@field cond? boolean
---@alias PluginOpts table|fun(self:LazyPlugin, opts:table):table? ---@field super? LazyPlugin
---@class LazyPluginHooks ---@class LazyPluginHooks
---@field init? fun(self:LazyPlugin) Will always be run ---@field init? fun(LazyPlugin) Will always be run
---@field deactivate? fun(self:LazyPlugin) Unload/Stop a plugin ---@field config? fun(LazyPlugin)|true|table Will be executed when loading the plugin
---@field config? fun(self:LazyPlugin, opts:table)|true Will be executed when loading the plugin ---@field build? string|fun(LazyPlugin)|(string|fun(LazyPlugin))[]
---@field build? false|string|async fun(self:LazyPlugin)|(string|async fun(self:LazyPlugin))[]
---@field opts? PluginOpts
---@class LazyPluginHandlers ---@class LazyPluginHandlers
---@field event? table<string,LazyEvent> ---@field event? string[]
---@field ft? table<string,LazyEvent> ---@field cmd? string[]
---@field keys? table<string,LazyKeys> ---@field ft? string[]
---@field cmd? table<string,string> ---@field keys? string[]
---@field module? false
---@class LazyPluginRef ---@class LazyPluginRef
---@field branch? string ---@field branch? string
---@field tag? string ---@field tag? string
---@field commit? string ---@field commit? string
---@field version? string|boolean ---@field version? string
---@field pin? boolean ---@field pin? boolean
---@field submodules? boolean Defaults to true
---@class LazyPluginBase ---@class LazyPluginBase
---@field [1] string? ---@field [1] string?
---@field name string display name and name used for plugin config files ---@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 url string?
---@field dir string ---@field dir string
---@field enabled? boolean|(fun():boolean) ---@field enabled? boolean|(fun():boolean)
---@field cond? 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 lazy? boolean
---@field priority? number Only useful for lazy=false plugins to force loading certain plugins first. Default priority is 50 ---@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 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 ---@class LazyPlugin: LazyPluginBase,LazyPluginHandlers,LazyPluginHooks,LazyPluginRef
---@field dependencies? string[] ---@field dependencies? string[]
---@field specs? string|string[]|LazyPluginSpec[]
---@field _ LazyPluginState ---@field _ LazyPluginState
---@class LazyPluginSpecHandlers ---@class LazyPluginSpecHandlers
---@field event? string[]|string|LazyEventSpec[]|fun(self:LazyPlugin, event:string[]):string[] ---@field event? string[]|string
---@field cmd? string[]|string|fun(self:LazyPlugin, cmd:string[]):string[] ---@field cmd? string[]|string
---@field ft? string[]|string|fun(self:LazyPlugin, ft:string[]):string[] ---@field ft? string[]|string
---@field keys? string|string[]|LazyKeysSpec[]|fun(self:LazyPlugin, keys:string[]):((string|LazyKeys)[]) ---@field keys? string|string[]|LazyKeys[]
---@field module? false ---@field module? false
---@class LazyPluginSpec: LazyPluginBase,LazyPluginSpecHandlers,LazyPluginHooks,LazyPluginRef ---@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 dependencies? string|string[]|LazyPluginSpec[]
---@field specs? string|string[]|LazyPluginSpec[]
---@alias LazySpec string|LazyPluginSpec|LazySpecImport|LazySpec[] ---@alias LazySpec string|LazyPluginSpec|LazySpecImport|LazySpec[]
---@class LazySpecImport ---@class LazySpecImport
---@field import string|(fun():LazyPluginSpec) spec module to import ---@field import string spec module to import
---@field name? string
---@field enabled? boolean|(fun():boolean) ---@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,33 @@
local M = setmetatable({}, { __index = require("lazy.core.util") }) local M = setmetatable({}, { __index = require("lazy.core.util") })
function M.file_exists(file) function M.file_exists(file)
return vim.uv.fs_stat(file) ~= nil return vim.loop.fs_stat(file) ~= nil
end end
---@param opts? LazyFloatOptions ---@param opts? LazyViewOptions
---@return LazyFloat
function M.float(opts) function M.float(opts)
opts = vim.tbl_deep_extend("force", {
win_opts = { zindex = 60, border = "none" },
margin = { top = 3, left = 2, right = 2 },
}, opts or {})
return require("lazy.view.float")(opts) return require("lazy.view.float")(opts)
end end
function M.wo(win, k, v) function M.open(uri)
if vim.api.nvim_set_option_value then if M.file_exists(uri) then
vim.api.nvim_set_option_value(k, v, { scope = "local", win = win }) return M.float({ win_opts = { style = "" }, file = uri })
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
return M.float({ style = "", file = uri })
end end
local Config = require("lazy.core.config") local Config = require("lazy.core.config")
local cmd local cmd
if not opts.system and Config.options.ui.browser then if Config.options.ui.browser then
cmd = { Config.options.ui.browser, uri } cmd = { Config.options.ui.browser, uri }
elseif vim.fn.has("win32") == 1 then elseif vim.fn.has("win32") == 1 then
cmd = { "explorer", uri } cmd = { "explorer", uri }
elseif vim.fn.has("macunix") == 1 then elseif vim.fn.has("macunix") == 1 then
cmd = { "open", uri } cmd = { "open", uri }
else else
if vim.fn.executable("xdg-open") == 1 then if vim.fn.executable("xdg-open") then
cmd = { "xdg-open", uri } cmd = { "xdg-open", uri }
elseif vim.fn.executable("wslview") == 1 then
cmd = { "wslview", uri }
else else
cmd = { "open", uri } cmd = { "open", uri }
end end
@ -73,118 +64,62 @@ end
---@param fn F ---@param fn F
---@return F ---@return F
function M.throttle(ms, fn) function M.throttle(ms, fn)
---@type Async local timer = vim.loop.new_timer()
local async local running = false
local pending = false local first = true
return function() return function(...)
if async and async:running() then local args = { ... }
pending = true local wrapped = function()
return fn(unpack(args))
end
if not running then
if first then
wrapped()
first = false
end end
---@async
async = require("lazy.async").new(function()
repeat
pending = false
fn()
async:sleep(ms)
until not pending timer:start(ms, 0, function()
running = false
vim.schedule(wrapped)
end) end)
running = true
end
end end
end end
--- Creates a weak reference to an object. ---@param cmd string[]
--- Calling the returned function will return the object if it has not been garbage collected. ---@param opts? {cwd:string, filetype:string, terminal?:boolean, close_on_exit?:boolean, enter?:boolean, float?:LazyViewOptions}
---@generic T: table function M.open_cmd(cmd, opts)
---@param obj T
---@return T|fun():T?
function M.weak(obj)
local weak = { _obj = obj }
---@return table<any, any>
local function get()
local ret = rawget(weak, "_obj")
return ret == nil and error("Object has been garbage collected", 2) or ret
end
local mt = {
__mode = "v",
__call = function(t)
return rawget(t, "_obj")
end,
__index = function(_, k)
return get()[k]
end,
__newindex = function(_, k, v)
get()[k] = v
end,
__pairs = function()
return pairs(get())
end,
}
return setmetatable(weak, mt)
end
---@class LazyCmdOptions: LazyFloatOptions
---@field cwd? string
---@field env? table<string,string>
---@field float? LazyFloatOptions
-- Opens a floating terminal (interactive by default)
---@param cmd? string[]|string
---@param opts? LazyCmdOptions|{interactive?:boolean}
function M.float_term(cmd, opts)
cmd = cmd or {}
if type(cmd) == "string" then
cmd = { cmd }
end
if #cmd == 0 then
cmd = { vim.o.shell }
end
opts = opts or {} opts = opts or {}
local float = M.float(opts) local float = M.float(opts.float)
vim.fn.termopen(cmd, vim.tbl_isempty(opts) and vim.empty_dict() or opts)
if opts.interactive ~= false then if opts.filetype then
vim.bo[float.buf].filetype = opts.filetype
end
if opts.terminal then
opts.terminal = nil
vim.fn.termopen(cmd, opts)
if opts.enter then
vim.cmd.startinsert() vim.cmd.startinsert()
end
if opts.close_on_exit then
vim.api.nvim_create_autocmd("TermClose", { vim.api.nvim_create_autocmd("TermClose", {
once = true, once = true,
buffer = float.buf, buffer = float.buf,
callback = function() callback = function()
float:close({ wipe = true }) float:close()
vim.cmd.checktime() vim.cmd.checktime()
end, end,
}) })
end end
return float else
end
--- Runs the command and shows it in a floating window
---@param cmd string[]
---@param opts? LazyCmdOptions|{filetype?:string}
function M.float_cmd(cmd, opts)
opts = opts or {}
local Process = require("lazy.manage.process") local Process = require("lazy.manage.process")
local lines, code = Process.exec(cmd, { cwd = opts.cwd }) local lines = 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
vim.api.nvim_buf_set_lines(float.buf, 0, -1, false, lines) vim.api.nvim_buf_set_lines(float.buf, 0, -1, false, lines)
vim.bo[float.buf].modifiable = false vim.bo[float.buf].modifiable = false
return float
end end
---@deprecated use float_term or float_cmd instead
function M.open_cmd()
M.warn([[`require("lazy.util").open_cmd()` is deprecated. Please use `float_term` instead. Check the docs]])
end end
---@return string? ---@return string?
@ -226,9 +161,9 @@ function M.markdown(msg, opts)
vim.tbl_deep_extend("force", { vim.tbl_deep_extend("force", {
title = "lazy.nvim", title = "lazy.nvim",
on_open = function(win) on_open = function(win)
M.wo(win, "conceallevel", 3) vim.wo[win].conceallevel = 3
M.wo(win, "concealcursor", "n") vim.wo[win].concealcursor = "n"
M.wo(win, "spell", false) vim.wo[win].spell = false
vim.treesitter.start(vim.api.nvim_win_get_buf(win), "markdown") vim.treesitter.start(vim.api.nvim_win_get_buf(win), "markdown")
end, end,
@ -242,25 +177,20 @@ function M._dump(value, result)
table.insert(result, tostring(value)) table.insert(result, tostring(value))
elseif t == "string" then elseif t == "string" then
table.insert(result, ("%q"):format(value)) table.insert(result, ("%q"):format(value))
elseif t == "table" and value._raw then
table.insert(result, value._raw)
elseif t == "table" then elseif t == "table" then
table.insert(result, "{") table.insert(result, "{")
for _, v in ipairs(value) do local i = 1
M._dump(v, result)
table.insert(result, ",")
end
---@diagnostic disable-next-line: no-unknown ---@diagnostic disable-next-line: no-unknown
for k, v in pairs(value) do for k, v in pairs(value) do
if type(k) == "string" then if k == i then
if k:match("^[a-zA-Z]+$") then elseif type(k) == "string" then
table.insert(result, ("%s="):format(k))
else
table.insert(result, ("[%q]="):format(k)) table.insert(result, ("[%q]="):format(k))
else
table.insert(result, k .. "=")
end end
M._dump(v, result) M._dump(v, result)
table.insert(result, ",") table.insert(result, ",")
end i = i + 1
end end
table.insert(result, "}") table.insert(result, "}")
else else
@ -277,14 +207,10 @@ end
---@generic V ---@generic V
---@param t table<string, V> ---@param t table<string, V>
---@param fn fun(key:string, value:V) ---@param fn fun(key:string, value:V)
---@param opts? {case_sensitive?:boolean} function M.foreach(t, fn)
function M.foreach(t, fn, opts)
---@type string[] ---@type string[]
local keys = vim.tbl_keys(t) local keys = vim.tbl_keys(t)
pcall(table.sort, keys, function(a, b) pcall(table.sort, keys, function(a, b)
if opts and opts.case_sensitive then
return a < b
end
return a:lower() < b:lower() return a:lower() < b:lower()
end) end)
for _, key in ipairs(keys) do for _, key in ipairs(keys) do

View file

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

View file

@ -1,8 +1,7 @@
local require = require("lazy.core.util").lazy_require local View = require("lazy.view")
local Config = require("lazy.core.config")
local Manage = require("lazy.manage") local Manage = require("lazy.manage")
local Util = require("lazy.util") local Util = require("lazy.util")
local View = require("lazy.view") local Config = require("lazy.core.config")
local ViewConfig = require("lazy.view.config") local ViewConfig = require("lazy.view.config")
local M = {} local M = {}
@ -34,21 +33,6 @@ M.commands = {
health = function() health = function()
vim.cmd.checkhealth("lazy") vim.cmd.checkhealth("lazy")
end, 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() home = function()
View.show("home") View.show("home")
end, end,
@ -66,17 +50,7 @@ M.commands = {
end, end,
---@param opts ManagerOpts ---@param opts ManagerOpts
load = function(opts) load = function(opts)
-- when a command is executed with a bang, wait will be set require("lazy.core.loader").load(opts.plugins, { cmd = "LazyLoad" })
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, end,
log = Manage.log, log = Manage.log,
build = Manage.build, build = Manage.build,
@ -89,14 +63,11 @@ M.commands = {
} }
function M.complete(cmd, prefix) 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 return
end end
---@type string[] ---@type string[]
local plugins = {} local plugins = {}
if cmd == "load" then
plugins[#plugins + 1] = "all"
end
for name, plugin in pairs(Config.plugins) do for name, plugin in pairs(Config.plugins) do
if cmd ~= "load" or not plugin._.loaded then if cmd ~= "load" or not plugin._.loaded then
plugins[#plugins + 1] = name plugins[#plugins + 1] = name
@ -114,9 +85,6 @@ function M.setup()
---@type ManagerOpts ---@type ManagerOpts
local opts = { wait = cmd.bang == true } local opts = { wait = cmd.bang == true }
local prefix, args = M.parse(cmd.args) 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 if #args > 0 then
---@param plugin string ---@param plugin string
opts.plugins = vim.tbl_map(function(plugin) opts.plugins = vim.tbl_map(function(plugin)
@ -125,7 +93,6 @@ function M.setup()
end end
M.cmd(prefix, opts) M.cmd(prefix, opts)
end, { end, {
bar = true,
bang = true, bang = true,
nargs = "?", nargs = "?",
desc = "Lazy", desc = "Lazy",
@ -137,7 +104,7 @@ function M.setup()
---@param key string ---@param key string
return vim.tbl_filter(function(key) 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, vim.tbl_keys(M.commands))
end, end,
}) })
@ -146,7 +113,7 @@ end
---@return string, string[] ---@return string, string[]
function M.parse(args) function M.parse(args)
local parts = vim.split(vim.trim(args), "%s+") 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) table.remove(parts, 1)
end end
if args:sub(-1) == " " then if args:sub(-1) == " " then

View file

@ -24,8 +24,6 @@ function M.get_commands()
return ret return ret
end end
M.dimmed_commits = { "bot", "build", "ci", "chore", "doc", "style", "test" }
M.keys = { M.keys = {
hover = "K", hover = "K",
diff = "d", diff = "d",
@ -34,8 +32,6 @@ M.keys = {
profile_sort = "<C-s>", profile_sort = "<C-s>",
profile_filter = "<C-f>", profile_filter = "<C-f>",
abort = "<C-c>", abort = "<C-c>",
next = "]]",
prev = "[[",
} }
---@type table<string,LazyViewCommand> ---@type table<string,LazyViewCommand>
@ -134,7 +130,7 @@ M.commands = {
id = 12, id = 12,
}, },
load = { load = {
desc = "Load a plugin that has not been loaded yet. Similar to `:packadd`. Like `:Lazy load foo.nvim`. Use `:Lazy! load` to skip `cond` checks.", desc = "Load a plugin that has not been loaded yet. Similar to `:packadd`. Like `:Lazy load foo.nvim`",
id = 13, id = 13,
plugins = true, plugins = true,
plugins_required = true, plugins_required = true,
@ -145,16 +141,10 @@ M.commands = {
}, },
build = { build = {
desc = "Rebuild a plugin", desc = "Rebuild a plugin",
id = 15, id = 13,
plugins = true, plugins = true,
plugins_required = true, plugins_required = true,
key_plugin = "gb", key_plugin = "b",
},
reload = {
desc = "Reload a plugin (experimental!!)",
plugins = true,
plugins_required = true,
id = 16,
}, },
} }

View file

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

View file

@ -1,31 +1,21 @@
local Config = require("lazy.core.config") local Config = require("lazy.core.config")
local Util = require("lazy.util")
local ViewConfig = require("lazy.view.config") local ViewConfig = require("lazy.view.config")
---@class LazyFloatOptions ---@class LazyViewOptions
---@field buf? number ---@field buf? number
---@field file? string ---@field file? string
---@field margin? {top?:number, right?:number, bottom?:number, left?:number} ---@field margin? {top?:number, right?:number, bottom?:number, left?:number}
---@field win_opts LazyViewWinOpts
---@field size? {width:number, height:number} ---@field size? {width:number, height:number}
---@field zindex? number local defaults = {
---@field style? "" | "minimal" win_opts = {},
---@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 ---@class LazyFloat
---@field buf number ---@field buf number
---@field win number ---@field win number
---@field opts LazyFloatOptions ---@field opts LazyViewOptions
---@field win_opts LazyWinOpts ---@overload fun(opts?:LazyViewOptions):LazyFloat
---@field backdrop_buf number
---@field backdrop_win number
---@field id number
---@overload fun(opts?:LazyFloatOptions):LazyFloat
local M = {} local M = {}
setmetatable(M, { setmetatable(M, {
@ -34,52 +24,19 @@ setmetatable(M, {
end, end,
}) })
local _id = 0 ---@param opts? LazyViewOptions
local function next_id()
_id = _id + 1
return _id
end
---@param opts? LazyFloatOptions
function M.new(opts) function M.new(opts)
local self = setmetatable({}, { __index = M }) local self = setmetatable({}, { __index = M })
return self:init(opts) return self:init(opts)
end end
---@param opts? LazyFloatOptions ---@param opts? LazyViewOptions
function M:init(opts) function M:init(opts)
require("lazy.view.colors").setup() self.opts = vim.tbl_deep_extend("force", defaults, opts or {})
self.id = next_id() self.opts.size = vim.tbl_extend("keep", self.opts.size or {}, Config.options.ui.size)
self.opts = vim.tbl_deep_extend("force", {
size = Config.options.ui.size,
style = "minimal",
border = Config.options.ui.border or "none",
backdrop = Config.options.ui.backdrop or 60,
zindex = 50,
}, opts or {})
---@class LazyWinOpts
---@field width number
---@field height number
---@field row number
---@field col number
self.win_opts = {
relative = "editor",
style = self.opts.style ~= "" and self.opts.style or nil,
border = self.opts.border,
zindex = self.opts.zindex,
noautocmd = self.opts.noautocmd,
title = self.opts.title,
title_pos = self.opts.title and self.opts.title_pos or nil,
}
self:mount() self:mount()
self:on("VimEnter", function() self:on_key(ViewConfig.keys.close, self.close)
vim.schedule(function() self:on({ "BufDelete", "BufLeave", "BufHidden" }, self.close, { once = true })
if not self:win_valid() then
self:close()
end
end)
end, { buffer = false })
return self return self
end end
@ -87,173 +44,116 @@ function M:layout()
local function size(max, value) local function size(max, value)
return value > 1 and math.min(value, max) or math.floor(max * value) return value > 1 and math.min(value, max) or math.floor(max * value)
end end
self.win_opts.width = size(vim.o.columns, self.opts.size.width) self.opts.win_opts.width = size(vim.o.columns, self.opts.size.width)
self.win_opts.height = size(vim.o.lines, self.opts.size.height) self.opts.win_opts.height = size(vim.o.lines, self.opts.size.height)
self.win_opts.row = math.floor((vim.o.lines - self.win_opts.height) / 2) self.opts.win_opts.row = math.floor((vim.o.lines - self.opts.win_opts.height) / 2)
self.win_opts.col = math.floor((vim.o.columns - self.win_opts.width) / 2) self.opts.win_opts.col = math.floor((vim.o.columns - self.opts.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 then
if self.opts.margin.top then if self.opts.margin.top then
self.win_opts.height = self.win_opts.height - self.opts.margin.top self.opts.win_opts.height = self.opts.win_opts.height - self.opts.margin.top
self.win_opts.row = self.win_opts.row + self.opts.margin.top self.opts.win_opts.row = self.opts.win_opts.row + self.opts.margin.top
end end
if self.opts.margin.right then if self.opts.margin.right then
self.win_opts.width = self.win_opts.width - self.opts.margin.right self.opts.win_opts.width = self.opts.win_opts.width - self.opts.margin.right
end end
if self.opts.margin.bottom then if self.opts.margin.bottom then
self.win_opts.height = self.win_opts.height - self.opts.margin.bottom self.opts.win_opts.height = self.opts.win_opts.height - self.opts.margin.bottom
end end
if self.opts.margin.left then if self.opts.margin.left then
self.win_opts.width = self.win_opts.width - self.opts.margin.left self.opts.win_opts.width = self.opts.win_opts.width - self.opts.margin.left
self.win_opts.col = self.win_opts.col + self.opts.margin.left self.opts.win_opts.col = self.opts.win_opts.col + self.opts.margin.left
end end
end end
end end
function M:mount() function M:mount()
if self:buf_valid() then if self.opts.file then
-- keep existing buffer
self.buf = self.buf
elseif self.opts.file then
self.buf = vim.fn.bufadd(self.opts.file) 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.fn.bufload(self.buf)
vim.bo[self.buf].modifiable = false vim.bo[self.buf].modifiable = false
elseif self.opts.buf then elseif self.opts.buf then
self.buf = self.opts.buf self.buf = self.opts.buf
else else
self.buf = vim.api.nvim_create_buf(false, true) self.buf = vim.api.nvim_create_buf(false, false)
end end
local normal, has_bg ---@class LazyViewWinOpts
if vim.fn.has("nvim-0.9.0") == 0 then ---@field width number
normal = vim.api.nvim_get_hl_by_name("Normal", true) ---@field height number
has_bg = normal and normal.background ~= nil ---@field row number
else ---@field col number
normal = vim.api.nvim_get_hl(0, { name = "Normal" }) local win_opts = {
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", relative = "editor",
width = vim.o.columns,
height = vim.o.lines,
row = 0,
col = 0,
style = "minimal", style = "minimal",
focusable = false, border = Config.options.ui.border,
zindex = self.opts.zindex - 1, noautocmd = true,
}) zindex = 50,
vim.api.nvim_set_hl(0, "LazyBackdrop", { bg = "#000000", default = true }) }
Util.wo(self.backdrop_win, "winhighlight", "Normal:LazyBackdrop") self.opts.win_opts = vim.tbl_extend("force", win_opts, self.opts.win_opts)
Util.wo(self.backdrop_win, "winblend", self.opts.backdrop) if self.opts.win_opts.style == "" then
vim.bo[self.backdrop_buf].buftype = "nofile" self.opts.win_opts.style = nil
vim.bo[self.backdrop_buf].filetype = "lazy_backdrop"
end end
self:layout() self:layout()
self.win = vim.api.nvim_open_win(self.buf, true, self.win_opts) self.win = vim.api.nvim_open_win(self.buf, true, self.opts.win_opts)
self:on("WinClosed", function()
self:close()
self:augroup(true)
end, { win = true })
self:focus() 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" vim.bo[self.buf].buftype = "nofile"
end
if vim.bo[self.buf].filetype == "" then if vim.bo[self.buf].filetype == "" then
vim.bo[self.buf].filetype = self.opts.ft or "lazy" vim.bo[self.buf].filetype = "lazy"
end end
vim.bo[self.buf].bufhidden = "wipe"
local function opts() vim.wo[self.win].conceallevel = 3
vim.bo[self.buf].bufhidden = self.opts.persistent and "hide" or "wipe" vim.wo[self.win].spell = false
Util.wo(self.win, "conceallevel", 3) vim.wo[self.win].wrap = true
Util.wo(self.win, "foldenable", false) vim.wo[self.win].winhighlight = "Normal:LazyNormal"
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.api.nvim_create_autocmd("VimResized", { vim.api.nvim_create_autocmd("VimResized", {
callback = function() callback = function()
if not (self.win and vim.api.nvim_win_is_valid(self.win)) then if not self.win then
return true return true
end end
self:layout() self:layout()
local config = {} local config = {}
for _, key in ipairs({ "relative", "width", "height", "col", "row" }) do for _, key in ipairs({ "relative", "width", "height", "col", "row" }) do
---@diagnostic disable-next-line: no-unknown ---@diagnostic disable-next-line: no-unknown
config[key] = self.win_opts[key] config[key] = self.opts.win_opts[key]
end end
config.style = self.opts.style ~= "" and self.opts.style or nil
vim.api.nvim_win_set_config(self.win, config) vim.api.nvim_win_set_config(self.win, config)
vim.cmd([[do User LazyFloatResized]])
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 })
end, end,
}) })
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 events string|string[]
---@param fn fun(self:LazyFloat, event:{buf:number}):boolean? ---@param fn fun(self?):boolean?
---@param opts? vim.api.keyset.create_autocmd | {buffer: false, win?:boolean} ---@param opts? table
function M:on(events, fn, opts) function M:on(events, fn, opts)
opts = opts or {} if type(events) == "string" then
if opts.win then events = { events }
opts.pattern = self.win .. ""
opts.win = nil
elseif opts.buffer == nil then
opts.buffer = self.buf
elseif opts.buffer == false then
opts.buffer = nil
end end
if opts.pattern then for _, e in ipairs(events) do
opts.buffer = nil 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 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 end
---@param key string ---@param key string
---@param fn fun(self?) ---@param fn fun(self?)
---@param desc? string ---@param desc? string
---@param mode? string[] function M:on_key(key, fn, desc)
function M:on_key(key, fn, desc, mode) vim.keymap.set("n", key, function()
vim.keymap.set(mode or "n", key, function()
fn(self) fn(self)
end, { end, {
nowait = true, nowait = true,
@ -262,77 +162,22 @@ function M:on_key(key, fn, desc, mode)
}) })
end end
---@param opts? {wipe:boolean} function M:close()
function M:close(opts)
self:augroup(true)
local buf = self.buf local buf = self.buf
local win = self.win local win = self.win
local wipe = opts and opts.wipe
if wipe == nil then
wipe = not self.opts.persistent
end
self.win = nil self.win = nil
if wipe then
self.buf = nil self.buf = nil
end vim.diagnostic.reset(Config.ns, buf)
local backdrop_buf = self.backdrop_buf
local backdrop_win = self.backdrop_win
self.backdrop_buf = nil
self.backdrop_win = nil
vim.schedule(function() 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 if win and vim.api.nvim_win_is_valid(win) then
vim.api.nvim_win_close(win, true) vim.api.nvim_win_close(win, true)
end end
if wipe and buf and vim.api.nvim_buf_is_valid(buf) then if buf and vim.api.nvim_buf_is_valid(buf) then
vim.diagnostic.reset(Config.ns, buf)
vim.api.nvim_buf_delete(buf, { force = true }) vim.api.nvim_buf_delete(buf, { force = true })
end end
vim.cmd.redraw()
end) end)
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() function M:focus()
vim.api.nvim_set_current_win(self.win) 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 Config = require("lazy.core.config")
local ViewConfig = require("lazy.view.config")
local Git = require("lazy.manage.git")
local Diff = require("lazy.view.diff") local Diff = require("lazy.view.diff")
local Float = require("lazy.view.float") 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 ---@class LazyViewState
---@field mode string ---@field mode string
@ -25,17 +25,13 @@ local M = {}
---@type LazyView ---@type LazyView
M.view = nil M.view = nil
function M.visible()
return M.view and M.view.win and vim.api.nvim_win_is_valid(M.view.win)
end
---@param mode? string ---@param mode? string
function M.show(mode) function M.show(mode)
if Config.headless() then if Config.headless then
return return
end end
M.view = M.visible() and M.view or M.create() M.view = (M.view and M.view.win and vim.api.nvim_win_is_valid(M.view.win)) and M.view or M.create()
if mode then if mode then
M.view.state.mode = mode M.view.state.mode = mode
end end
@ -50,44 +46,26 @@ end
function M.create() function M.create()
local self = setmetatable({}, { __index = setmetatable(M, { __index = Float }) }) local self = setmetatable({}, { __index = setmetatable(M, { __index = Float }) })
---@cast self LazyView ---@cast self LazyView
Float.init(self, { Float.init(self)
title = Config.options.ui.title,
title_pos = Config.options.ui.title_pos,
noautocmd = false,
})
if Config.options.ui.wrap then require("lazy.view.colors").setup()
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
self.state = vim.deepcopy(default_state) self.state = vim.deepcopy(default_state)
self.render = Render.new(self) self.render = Render.new(self)
local update = self.update self.update = Util.throttle(Config.options.ui.throttle, self.update)
self.update = Util.throttle(Config.options.ui.throttle, function()
update(self)
end)
for _, pattern in ipairs({ "LazyRender", "LazyFloatResized" }) do self:on({ "User LazyRender", "User LazyFloatResized" }, function()
self:on({ "User" }, function()
if not (self.buf and vim.api.nvim_buf_is_valid(self.buf)) then if not (self.buf and vim.api.nvim_buf_is_valid(self.buf)) then
return true return true
end end
self:update() self:update()
end, { pattern = pattern }) end)
end
vim.keymap.set("n", ViewConfig.keys.abort, function() vim.keymap.set("n", ViewConfig.keys.abort, function()
require("lazy.manage.process").abort() require("lazy.manage.process").abort()
require("lazy.async").abort() return "<c-c>"
return ViewConfig.keys.abort end, { silent = true, buffer = self.buf, expr = true })
end, { silent = true, buffer = self.buf, expr = true, desc = "Abort" })
vim.keymap.set("n", "gx", "K", { buffer = self.buf, remap = true })
-- plugin details -- plugin details
self:on_key(ViewConfig.keys.details, function() self:on_key(ViewConfig.keys.details, function()
@ -97,49 +75,17 @@ function M.create()
name = plugin.name, name = plugin.name,
kind = plugin._.kind, kind = plugin._.kind,
} }
self.state.plugin = not vim.deep_equal(self.state.plugin, selected) and selected or nil
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:update() self:update()
end end
end, "Details") end)
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")
self:on_key(ViewConfig.keys.profile_sort, function() self:on_key(ViewConfig.keys.profile_sort, function()
if self.state.mode == "profile" then if self.state.mode == "profile" then
self.state.profile.sort_time_taken = not self.state.profile.sort_time_taken self.state.profile.sort_time_taken = not self.state.profile.sort_time_taken
self:update() self:update()
end end
end, "Sort Profile") end)
self:on_key(ViewConfig.keys.profile_filter, function() self:on_key(ViewConfig.keys.profile_filter, function()
if self.state.mode == "profile" then if self.state.mode == "profile" then
@ -159,18 +105,16 @@ function M.create()
end end
end) end)
end end
end, "Filter Profile") end)
for lhs, rhs in pairs(Config.options.ui.custom_keys) do for key, handler in pairs(Config.options.ui.custom_keys) do
if rhs then if handler then
local handler = type(rhs) == "table" and rhs[1] or rhs self:on_key(key, function()
local desc = type(rhs) == "table" and rhs.desc or nil
self:on_key(lhs, function()
local plugin = self.render:get_plugin() local plugin = self.render:get_plugin()
if plugin then if plugin then
handler(plugin) handler(plugin)
end end
end, desc) end)
end end
end end
@ -181,7 +125,9 @@ end
function M:update() function M:update()
if self.buf and vim.api.nvim_buf_is_valid(self.buf) then if self.buf and vim.api.nvim_buf_is_valid(self.buf) then
vim.bo[self.buf].modifiable = true
self.render:update() self.render:update()
vim.bo[self.buf].modifiable = false
vim.cmd.redraw() vim.cmd.redraw()
end end
end end
@ -199,7 +145,7 @@ function M:open_url(path)
end end
function M:setup_patterns() 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, { self:on_pattern(ViewConfig.keys.hover, {
[commit_pattern] = function(hash) [commit_pattern] = function(hash)
self:diff({ commit = hash, browser = true }) self:diff({ commit = hash, browser = true })
@ -213,24 +159,21 @@ function M:setup_patterns()
Util.open(plugin.dir .. "/README.md") Util.open(plugin.dir .. "/README.md")
end end
end, end,
["|(%S-)|"] = function(h) ["|(%S-)|"] = vim.cmd.help, -- vim help links
vim.cmd.help(h)
self:close()
end,
["(https?://%S+)"] = function(url) ["(https?://%S+)"] = function(url)
Util.open(url) Util.open(url)
end, end,
}, self.hover, "Hover") }, self.hover)
self:on_pattern(ViewConfig.keys.diff, { self:on_pattern(ViewConfig.keys.diff, {
[commit_pattern] = function(hash) [commit_pattern] = function(hash)
self:diff({ commit = hash }) self:diff({ commit = hash })
end, end,
}, self.diff, "Diff") }, self.diff)
self:on_pattern(ViewConfig.commands.restore.key_plugin, { self:on_pattern(ViewConfig.commands.restore.key_plugin, {
[commit_pattern] = function(hash) [commit_pattern] = function(hash)
self:restore({ commit = hash }) self:restore({ commit = hash })
end, end,
}, self.restore, "Restore") }, self.restore)
end end
---@param opts? {commit:string} ---@param opts? {commit:string}
@ -248,13 +191,13 @@ function M:restore(opts)
end end
function M:hover() function M:hover()
if self:diff({ browser = true, hover = true }) then if self:diff({ browser = true }) then
return return
end end
self:open_url("") self:open_url("")
end end
---@param opts? {commit?:string, browser:boolean, hover:boolean} ---@param opts? {commit?:string, browser:boolean}
function M:diff(opts) function M:diff(opts)
opts = opts or {} opts = opts or {}
local plugin = self.render:get_plugin() local plugin = self.render:get_plugin()
@ -268,9 +211,6 @@ function M:diff(opts)
local info = assert(Git.info(plugin.dir)) local info = assert(Git.info(plugin.dir))
local target = assert(Git.get_target(plugin)) local target = assert(Git.get_target(plugin))
diff = { from = info.commit, to = target.commit } diff = { from = info.commit, to = target.commit }
if opts.hover and diff.from == diff.to then
return
end
end end
if not diff then if not diff then
@ -286,8 +226,6 @@ function M:diff(opts)
else else
Diff.handlers[Config.options.diff.cmd](plugin, diff) Diff.handlers[Config.options.diff.cmd](plugin, diff)
end end
return true
end end
end end
@ -295,8 +233,7 @@ end
---@param key string ---@param key string
---@param patterns table<string, fun(str:string)> ---@param patterns table<string, fun(str:string)>
---@param fallback? fun(self) ---@param fallback? fun(self)
---@param desc? string function M:on_pattern(key, patterns, fallback)
function M:on_pattern(key, patterns, fallback, desc)
self:on_key(key, function() self:on_key(key, function()
local line = vim.api.nvim_get_current_line() local line = vim.api.nvim_get_current_line()
local pos = vim.api.nvim_win_get_cursor(0) local pos = vim.api.nvim_win_get_cursor(0)
@ -318,7 +255,7 @@ function M:on_pattern(key, patterns, fallback, desc)
if fallback then if fallback then
fallback(self) fallback(self)
end end
end, desc) end)
end end
function M:setup_modes() function M:setup_modes()
@ -335,28 +272,11 @@ function M:setup_modes()
end end
if m.key_plugin and name ~= "restore" then if m.key_plugin and name ~= "restore" then
self:on_key(m.key_plugin, function() self:on_key(m.key_plugin, function()
local esc = vim.api.nvim_replace_termcodes("<esc>", true, true, true) local plugin = self.render:get_plugin()
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 if plugin then
plugins[plugin.name] = plugin Commands.cmd(name, { plugins = { plugin } })
end end
end end, m.desc_plugin)
plugins = vim.tbl_values(plugins)
else
plugins[1] = self.render:get_plugin()
end
if #plugins > 0 then
Commands.cmd(name, { plugins = plugins })
end
end, m.desc_plugin, { "n", "x" })
end end
end end
end end

View file

@ -1,11 +1,11 @@
local Config = require("lazy.core.config") 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 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 ViewConfig = require("lazy.view.config")
local Cache = require("lazy.core.cache")
local Text = require("lazy.view.text") local Text = require("lazy.view.text")
@ -26,7 +26,7 @@ function M.new(view)
local self = setmetatable({}, { __index = setmetatable(M, { __index = Text }) }) local self = setmetatable({}, { __index = setmetatable(M, { __index = Text }) })
self.view = view self.view = view
self.padding = 2 self.padding = 2
self.wrap = view.win_opts.width self.wrap = view.opts.win_opts.width
return self return self
end end
@ -51,7 +51,7 @@ function M:update()
if plugin._.tasks then if plugin._.tasks then
for _, task in ipairs(plugin._.tasks) do for _, task in ipairs(plugin._.tasks) do
self.progress.total = self.progress.total + 1 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 self.progress.done = self.progress.done + 1
end end
end end
@ -74,17 +74,7 @@ function M:update()
end end
self:trim() 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) 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( vim.diagnostic.set(
Config.ns, Config.ns,
self.view.buf, self.view.buf,
@ -94,16 +84,13 @@ function M:update()
diag.lnum = diag.row - 1 diag.lnum = diag.row - 1
return diag return diag
end, self._diagnostics), end, self._diagnostics),
{ signs = false, virtual_text = true, underline = false, virtual_lines = false } { signs = false }
) )
end end
---@param row? number ---@param row? number
---@return LazyPlugin? ---@return LazyPlugin?
function M:get_plugin(row) 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] row = row or vim.api.nvim_win_get_cursor(self.view.win)[1]
for _, loc in ipairs(self.locations) do for _, loc in ipairs(self.locations) do
if row >= loc.from and row <= loc.to then if row >= loc.from and row <= loc.to then
@ -122,49 +109,34 @@ function M:get_plugin(row)
end end
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() function M:title()
self:nl() self:nl():nl()
local modes = vim.tbl_filter(function(c) for _, mode in ipairs(ViewConfig.get_commands()) do
return c.button if mode.button then
end, ViewConfig.get_commands())
if Config.options.ui.pills then
self:nl()
for c, mode in ipairs(modes) do
local title = " " .. mode.name:sub(1, 1):upper() .. mode.name:sub(2) .. " (" .. mode.key .. ") " local title = " " .. mode.name:sub(1, 1):upper() .. mode.name:sub(2) .. " (" .. mode.key .. ") "
if mode.name == "home" then if mode.name == "home" then
if self.view.state.mode == "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
end end
if self.view.state.mode == mode.name then if self.view.state.mode == mode.name then
if mode.name == "home" then if mode.name == "home" then
self:append(title, "LazyH1", { wrap = true }) self:append(title, "LazyH1")
else else
self:append(title, "LazyButtonActive", { wrap = true }) self:append(title, "LazyButtonActive")
self:highlight({ ["%(.%)"] = "LazySpecial" }) self:highlight({ ["%(.%)"] = "LazySpecial" })
end end
else else
self:append(title, "LazyButton", { wrap = true }) self:append(title, "LazyButton")
self:highlight({ ["%(.%)"] = "LazySpecial" }) self:highlight({ ["%(.%)"] = "LazySpecial" })
end end
if c == #modes then
break
end
self:append(" ") self:append(" ")
end end
self:nl()
end end
self:nl()
if self.progress.done < self.progress.total then if self.progress.done < self.progress.total then
self:progressbar() self:progressbar()
end end
@ -185,7 +157,7 @@ end
function M:help() function M:help()
self:append("Help", "LazyH2"):nl():nl() 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() 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() :nl()
self:append("or the plugin was just updated. Otherwise the plugin webpage will open."):nl():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("<d>", "LazySpecial"):append(" on a commit or plugin to open the diff view"):nl()
self
:append("Use ")
:append("<]]>", "LazySpecial")
:append(" and ")
:append("<[[>", "LazySpecial")
:append(" to navigate between plugins")
:nl()
:nl()
self:nl() self:nl()
self:append("Keyboard Shortcuts", "LazyH2"):nl() self:append("Keyboard Shortcuts", "LazyH2"):nl()
@ -235,14 +199,6 @@ function M:help()
self:append(" " .. (mode.desc_plugin or mode.desc)):nl() self:append(" " .. (mode.desc_plugin or mode.desc)):nl()
end end
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 end
function M:progressbar() function M:progressbar()
@ -275,9 +231,6 @@ function M:section(section)
end, self.plugins) end, self.plugins)
local count = #section_plugins local count = #section_plugins
table.sort(section_plugins, function(a, b)
return a.name:lower() < b.name:lower()
end)
if count > 0 then if count > 0 then
self:append(section.title, "LazyH2"):append(" (" .. count .. ")", "LazyComment"):nl() self:append(section.title, "LazyH2"):append(" (" .. count .. ")", "LazyComment"):nl()
for _, plugin in ipairs(section_plugins) do for _, plugin in ipairs(section_plugins) do
@ -353,17 +306,23 @@ function M:reason(reason, opts)
end end
for _, key in ipairs(keys) do for _, key in ipairs(keys) do
local value = reason[key] local value = reason[key]
local skip = type(key) == "number" or key == "time" if type(key) == "number" then
if not skip then elseif key == "require" then
elseif key ~= "time" then
if first then if first then
first = false first = false
else else
self:append(" ") self:append(" ")
end 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 hl = "LazyReason" .. key:sub(1, 1):upper() .. key:sub(2)
local icon = Config.options.ui.icons[key] local icon = Config.options.ui.icons[key]
if icon then if icon then
icon = icon:gsub("%s*$", "")
self:append(icon .. " ", hl) self:append(icon .. " ", hl)
self:append(value, hl) self:append(value, hl)
else else
@ -379,37 +338,7 @@ end
---@param plugin LazyPlugin ---@param plugin LazyPlugin
function M:diagnostics(plugin) function M:diagnostics(plugin)
local skip = false if plugin._.updated then
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.from == plugin._.updated.to then if plugin._.updated.from == plugin._.updated.to then
self:diagnostic({ self:diagnostic({
message = "already up to date", message = "already up to date",
@ -438,32 +367,49 @@ function M:diagnostics(plugin)
}) })
end end
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 end
---@param plugin LazyPlugin ---@param plugin LazyPlugin
function M:plugin(plugin) function M:plugin(plugin)
local hl = plugin._.is_local and "LazyLocal" or "LazySpecial"
if plugin._.loaded then 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 elseif plugin._.cond == false then
self:append(" " .. Config.options.ui.icons.not_loaded .. " ", "LazyNoCond"):append(plugin.name) self:append(" " .. Config.options.ui.icons.not_loaded .. " ", "LazyNoCond"):append(plugin.name)
else 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 end
local plugin_start = self:row() local plugin_start = self:row()
if plugin._.loaded then if plugin._.loaded then
-- When the plugin is loaded, only show the loading reason
self:reason(plugin._.loaded) self:reason(plugin._.loaded)
else else
-- otherwise show all lazy handlers
self:append(" ") self:append(" ")
self:handlers(plugin) 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 for _, other in pairs(Config.plugins) do
if vim.tbl_contains(other.dependencies or {}, plugin.name) then if vim.tbl_contains(other.dependencies or {}, plugin.name) then
self:reason({ plugin = other.name }) reason.plugin = other.name
self:append(" ")
end end
end end
self:reason(reason)
end end
self:diagnostics(plugin) self:diagnostics(plugin)
self:nl() self:nl()
@ -476,22 +422,6 @@ function M:plugin(plugin)
{ name = plugin.name, from = plugin_start, to = self:row() - 1, kind = plugin._.kind } { name = plugin.name, from = plugin_start, to = self:row() - 1, kind = plugin._.kind }
end 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 ---@param plugin LazyPlugin
function M:tasks(plugin) function M:tasks(plugin)
for _, task in ipairs(plugin._.tasks or {}) do 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:append(" " .. math.floor((task:time()) * 100) / 100 .. "ms", "Bold")
self:nl() self:nl()
end end
if task.error then
if not task:has_warnings() and task.name == "log" then self:append(vim.trim(task.error), "LazyTaskError", { indent = 6 })
self:nl()
elseif task.name == "log" then
self:log(task) self:log(task)
else elseif self.view:is_selected(plugin) and task.output ~= "" and task.output ~= task.error then
local hls = { self:append(vim.trim(task.output), "LazyTaskOutput", { indent = 6 })
[vim.log.levels.ERROR] = "LazyError", self:nl()
[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
end end
end end
end end
---@param task LazyTask ---@param task LazyTask
function M:log(task) function M:log(task)
local log = vim.trim(task:output()) local log = vim.trim(task.output)
if log ~= "" then if log ~= "" then
local lines = vim.split(log, "\n") local lines = vim.split(log, "\n")
for _, line in ipairs(lines) do for _, line in ipairs(lines) do
local ref, msg, time = line:match("^(%w+) (.*) (%(.*%))$") local ref, msg, time = line:match("^(%w+) (.*) (%(.*%))$")
if msg then
if msg:find("^%S+!:") then if msg:find("^%S+!:") then
self:diagnostic({ message = "Breaking Changes", severity = vim.diagnostic.severity.WARN }) self:diagnostic({ message = "Breaking Changes", severity = vim.diagnostic.severity.WARN })
end end
self:append(ref:sub(1, 7) .. " ", "LazyCommit", { indent = 6 }) self:append(ref:sub(1, 7) .. " ", "LazyCommit", { indent = 6 })
self:append(vim.trim(msg)):highlight({
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", ["#%d+"] = "LazyCommitIssue",
["^%S+:"] = dimmed and "Bold" or "LazyCommitType", ["^%S+:"] = "LazyCommitType",
["^%S+(%(.*%))!?:"] = "LazyCommitScope", ["^%S+(%(.*%)):"] = "LazyCommitScope",
["`.-`"] = "@markup.raw.markdown_inline", ["`.-`"] = "@text.literal.markdown_inline",
["%*.-%*"] = "Italic", ["%*.-%*"] = "Italic",
["%*%*.-%*%*"] = "Bold", ["%*%*.-%*%*"] = "Bold",
}) })
-- string.gsub
self:append(" " .. time, "LazyComment") self:append(" " .. time, "LazyComment")
self:nl() self:nl()
-- else
-- self:append(line, "LazyTaskOutput", { indent = 6 }):nl()
end
end end
self:nl() self:nl()
end end
@ -575,15 +489,8 @@ function M:details(plugin)
if git.branch then if git.branch then
table.insert(props, { "branch", git.branch }) table.insert(props, { "branch", git.branch })
end end
if git.commit then
table.insert(props, { "commit", git.commit:sub(1, 7), "LazyCommit" }) table.insert(props, { "commit", git.commit:sub(1, 7), "LazyCommit" })
end end
end
local rocks = require("lazy.pkg.rockspec").deps(plugin)
if rocks then
table.insert(props, { "rocks", vim.inspect(rocks) })
end
if Util.file_exists(plugin.dir .. "/README.md") then if Util.file_exists(plugin.dir .. "/README.md") then
table.insert(props, { "readme", "README.md" }) table.insert(props, { "readme", "README.md" })
end end
@ -597,36 +504,24 @@ function M:details(plugin)
end end
end) end)
for handler in pairs(plugin._.handlers or {}) do for handler in pairs(Handler.types) do
if plugin[handler] then
table.insert(props, { table.insert(props, {
handler, handler,
function() function()
self:handlers(plugin, handler) for _, value in ipairs(plugin[handler]) do
self:reason({ [handler] = value })
self:append(" ")
end
end, end,
}) })
end end
end
self:props(props, { indent = 6 }) self:props(props, { indent = 6 })
self:nl() self:nl()
end 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}[] ---@alias LazyProps {[1]:string, [2]:string|fun(), [3]?:string}[]
---@param props LazyProps ---@param props LazyProps
---@param opts? {indent: number} ---@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("Based on the actual CPU time of the Neovim process till "):append("UIEnter", "LazySpecial")
self:append("."):nl() self:append("."):nl()
self:append("This is more accurate than ") 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(".") self:append(".")
else else
self:append("An accurate startuptime based on the actual CPU time of the Neovim process is not available."):nl() 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("Profile", "LazyH2"):nl():nl()
self self
:append("You can press ") :append("You can press ")
:append(ViewConfig.keys.profile_sort, "LazySpecial") :append("<C-s>", "LazySpecial")
:append(" to change sorting between chronological order & time taken.") :append(" to change sorting between chronological order & time taken.")
:nl() :nl()
self self
:append("Press ") :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") :append(" to filter profiling entries that took more time than a given threshold")
:nl() :nl()
@ -755,19 +650,12 @@ function M:debug()
Util.foreach(require("lazy.core.handler").handlers, function(handler_type, handler) Util.foreach(require("lazy.core.handler").handlers, function(handler_type, handler)
Util.foreach(handler.active, function(value, plugins) Util.foreach(handler.active, function(value, plugins)
value = type(value) == "table" and value[1] or value
if not vim.tbl_isempty(plugins) then if not vim.tbl_isempty(plugins) then
---@type string[] ---@type string[]
plugins = vim.tbl_values(plugins) plugins = vim.tbl_values(plugins)
table.sort(plugins) table.sort(plugins)
self:append(Config.options.ui.icons.debug, "LazySpecial", { indent = 2 }) self:append("", "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:reason({ [handler_type] = value }) self:reason({ [handler_type] = value })
for _, plugin in pairs(plugins) do for _, plugin in pairs(plugins) do
self:append(" ") self:append(" ")
@ -779,19 +667,33 @@ function M:debug()
end) end)
self:nl() self:nl()
Util.foreach(require("lazy.core.cache")._inspect(), function(name, stats) self:append("Cache.find()", "LazyH2"):nl()
self:append(name, "LazyH2"):nl() self:props({
local props = { { "total", Cache.stats.find.total, "Number" },
{ "total", stats.total or 0, "Number" }, { "time", self:ms(Cache.stats.find.time, 3), "Bold" },
{ "time", self:ms(stats.time or 0, 3), "Bold" }, { "avg time", self:ms(Cache.stats.find.time / Cache.stats.find.total, 3), "Bold" },
{ "avg time", self:ms((stats.time or 0) / (stats.total or 0), 3), "Bold" }, { "index", Cache.stats.find.index, "Number" },
} { "fs_stat", Cache.stats.find.stat, "Number" },
for k, v in pairs(stats) do { "not found", Cache.stats.find.not_found, "Number" },
if k ~= "total" and k ~= "time" then }, { indent = 2 })
props[#props + 1] = { k, v, "Number" } self:nl()
self:append("Cache.autoload()", "LazyH2"):nl()
self:props({
{ "total", Cache.stats.autoload.total, "Number" },
{ "time", self:ms(Cache.stats.autoload.time, 3), "Bold" },
{ "avg time", self:ms(Cache.stats.autoload.time / Cache.stats.autoload.total, 3), "Bold" },
}, { indent = 2 })
self:nl()
self:append("Cache", "LazyH2"):nl()
local Cache = require("lazy.core.cache")
Util.foreach(Cache.cache, function(modname, entry)
local kb = math.floor(#entry.chunk / 10.24) / 100
self:append("", "LazySpecial", { indent = 2 }):append(modname):append(" " .. kb .. "Kb", "Bold")
if entry.modpath ~= modname then
self:append(" " .. vim.fn.fnamemodify(entry.modpath, ":p:~:."), "LazyComment")
end end
end
self:props(props, { indent = 2 })
self:nl() self:nl()
end) end)
end end

View file

@ -17,35 +17,27 @@ return {
{ {
filter = function(plugin) filter = function(plugin)
return has_task(plugin, function(task) return has_task(plugin, function(task)
return task:has_errors() return task.error ~= nil
end) end)
end, end,
title = "Failed", title = "Failed",
}, },
{ {
filter = function(plugin) filter = function(plugin)
if plugin._.working then
return true
end
return has_task(plugin, function(task) return has_task(plugin, function(task)
return task:running() return task:is_running()
end) end)
end, end,
title = "Working", title = "Working",
}, },
{
filter = function(plugin)
return plugin._.build
end,
title = "Build",
},
{ {
filter = function(plugin) filter = function(plugin)
return has_task(plugin, function(task) return has_task(plugin, function(task)
if task.name ~= "log" then if task.name ~= "log" then
return return
end 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 if line:find("^%w+ %S+!:") then
return true return true
end end
@ -55,12 +47,14 @@ return {
title = "Breaking Changes", title = "Breaking Changes",
}, },
{ {
---@param plugin LazyPlugin
filter = function(plugin) filter = function(plugin)
return plugin._.updated and plugin._.updated.from ~= plugin._.updated.to return plugin._.updated and plugin._.updated.from ~= plugin._.updated.to
end, end,
title = "Updated", title = "Updated",
}, },
{ {
---@param plugin LazyPlugin
filter = function(plugin) filter = function(plugin)
return plugin._.cloned return plugin._.cloned
end, end,
@ -69,14 +63,14 @@ return {
{ {
---@param plugin LazyPlugin ---@param plugin LazyPlugin
filter = function(plugin) filter = function(plugin)
return plugin._.updates ~= nil return plugin._.updates
end, end,
title = "Updates", title = "Updates",
}, },
{ {
filter = function(plugin) filter = function(plugin)
return has_task(plugin, function(task) 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)
end, end,
title = "Log", title = "Log",
@ -95,13 +89,7 @@ return {
}, },
{ {
filter = function(plugin) filter = function(plugin)
return plugin._.outdated return plugin._.loaded
end,
title = "Outdated",
},
{
filter = function(plugin)
return plugin._.loaded ~= nil
end, end,
title = "Loaded", title = "Loaded",
}, },

View file

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

116407
manifest

File diff suppressed because it is too large Load diff

View file

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

View file

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

View file

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

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

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

View file

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

View file

@ -1,52 +1,18 @@
local Config = require("lazy.core.config") local Config = require("lazy.core.config")
local Handler = require("lazy.core.handler")
local Plugin = require("lazy.core.plugin") local Plugin = require("lazy.core.plugin")
local function inspect(obj) local assert = require("luassert")
return vim.inspect(obj):gsub("%s+", " ")
end
---@param plugin LazyPlugin Config.setup()
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
describe("plugin spec url/name", function() describe("plugin spec url/name", function()
local tests = { local tests = {
{ { dir = "~/foo" }, { name = "foo", dir = vim.fn.fnamemodify("~/foo", ":p") } }, { { dir = "~/foo" }, { name = "foo", dir = vim.fn.fnamemodify("~/foo", ":p") } },
{ { dir = "/tmp/foo" }, { dir = "/tmp/foo", name = "foo" } }, { { dir = "/tmp/foo" }, { dir = "/tmp/foo", name = "foo" } },
{ { "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" } },
{ { "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", 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://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" } }, { { 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" } },
{ { { { "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 for _, test in ipairs(tests) do
test[2]._ = {} test[2]._ = {}
it("parses " .. inspect(test[1]), function() it("parses " .. vim.inspect(test[1]):gsub("%s+", " "), function()
if not test[2].dir then if not test[2].dir then
test[2].dir = Config.options.root .. "/" .. test[2].name test[2].dir = Config.options.root .. "/" .. test[2].name
end end
local spec = Plugin.Spec.new(test[1]) local spec = Plugin.Spec.new(test[1])
local all = vim.deepcopy(spec.plugins) local plugins = vim.tbl_values(spec.plugins)
local plugins = vim.tbl_values(all) assert(#spec.notifs == 0)
plugins = vim.tbl_map(function(plugin) assert.equal(1, #plugins)
plugin._ = {}
return plugin
end, plugins)
local notifs = vim.tbl_filter(function(notif)
return notif.level > 3
end, spec.notifs)
assert(#notifs == 0, vim.inspect(spec.notifs))
assert.equal(1, #plugins, vim.inspect(all))
plugins[1]._.super = nil
assert.same(test[2], plugins[1]) assert.same(test[2], plugins[1])
end) end)
end 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() describe("plugin spec opt", function()
it("handles dependencies", function() it("handles dependencies", function()
Config.options.defaults.lazy = false Config.options.defaults.lazy = false
@ -158,7 +45,6 @@ describe("plugin spec opt", function()
local spec = Plugin.Spec.new(vim.deepcopy(test)) local spec = Plugin.Spec.new(vim.deepcopy(test))
assert(#spec.notifs == 0) assert(#spec.notifs == 0)
Config.plugins = spec.plugins Config.plugins = spec.plugins
Config.spec = spec
Plugin.update_state() Plugin.update_state()
assert(vim.tbl_count(spec.plugins) == 3) assert(vim.tbl_count(spec.plugins) == 3)
assert(#spec.plugins.bar.dependencies == 2) assert(#spec.plugins.bar.dependencies == 2)
@ -172,14 +58,16 @@ describe("plugin spec opt", function()
for _, plugin in pairs(spec.plugins) do for _, plugin in pairs(spec.plugins) do
plugin.dir = nil plugin.dir = nil
end end
assert.same({ assert.same(spec.plugins, {
bar = { bar = {
"foo/bar",
_ = {}, _ = {},
dependencies = { "dep1", "dep2" }, dependencies = { "dep1", "dep2" },
name = "bar", name = "bar",
url = "https://github.com/foo/bar.git", url = "https://github.com/foo/bar.git",
}, },
dep1 = { dep1 = {
"foo/dep1",
_ = { _ = {
dep = true, dep = true,
}, },
@ -187,27 +75,25 @@ describe("plugin spec opt", function()
url = "https://github.com/foo/dep1.git", url = "https://github.com/foo/dep1.git",
}, },
dep2 = { dep2 = {
"foo/dep2",
_ = { _ = {
dep = true, dep = true,
}, },
name = "dep2", name = "dep2",
url = "https://github.com/foo/dep2.git", url = "https://github.com/foo/dep2.git",
}, },
}, clean(spec.plugins)) })
end end
end) end)
describe("deps", function() describe("deps", function()
before_each(function() it("handles dep names", function()
Handler.init()
end)
Config.options.defaults.lazy = false Config.options.defaults.lazy = false
local tests = { local tests = {
{ { "foo/bar", dependencies = { { "dep1" }, "foo/dep2" } }, "foo/dep1" }, { { "foo/bar", dependencies = { { "dep1" }, "foo/dep2" } }, "foo/dep1" },
{ "foo/dep1", { "foo/bar", dependencies = { { "dep1" }, "foo/dep2" } } }, { "foo/dep1", { "foo/bar", dependencies = { { "dep1" }, "foo/dep2" } } },
} }
for _, test in ipairs(tests) do for _, test in ipairs(tests) do
it("handles dep names " .. inspect(test), function()
local spec = Plugin.Spec.new(vim.deepcopy(test)) local spec = Plugin.Spec.new(vim.deepcopy(test))
assert(#spec.notifs == 0) assert(#spec.notifs == 0)
Config.plugins = spec.plugins Config.plugins = spec.plugins
@ -216,74 +102,31 @@ describe("plugin spec opt", function()
for _, plugin in pairs(spec.plugins) do for _, plugin in pairs(spec.plugins) do
plugin.dir = nil plugin.dir = nil
end end
assert.same({ assert.same(spec.plugins, {
bar = { bar = {
"foo/bar",
_ = {}, _ = {},
dependencies = { "dep1", "dep2" }, dependencies = { "dep1", "dep2" },
name = "bar", name = "bar",
url = "https://github.com/foo/bar.git", url = "https://github.com/foo/bar.git",
}, },
dep1 = { dep1 = {
"foo/dep1",
_ = {}, _ = {},
name = "dep1", name = "dep1",
url = "https://github.com/foo/dep1.git", url = "https://github.com/foo/dep1.git",
}, },
dep2 = { dep2 = {
"foo/dep2",
_ = { _ = {
dep = true, dep = true,
}, },
name = "dep2", name = "dep2",
url = "https://github.com/foo/dep2.git", url = "https://github.com/foo/dep2.git",
}, },
}, clean(spec.plugins)) })
end
end) end)
end
Config.options.defaults.lazy = false
local tests = {
{
{ "foo/baz", name = "bar" },
{ "foo/fee", dependencies = { "foo/baz" } },
},
{
{ "foo/fee", dependencies = { "foo/baz" } },
{ "foo/baz", name = "bar" },
},
-- {
-- { "foo/baz", name = "bar" },
-- { "foo/fee", dependencies = { "baz" } },
-- },
{
{ "foo/baz", name = "bar" },
{ "foo/fee", dependencies = { "bar" } },
},
}
for _, test in ipairs(tests) do
it("handles dep names " .. inspect(test), function()
local spec = Plugin.Spec.new(vim.deepcopy(test))
assert(#spec.notifs == 0)
Config.plugins = spec.plugins
Plugin.update_state()
spec = Plugin.Spec.new(test)
spec.meta:rebuild()
for _, plugin in pairs(spec.plugins) do
plugin.dir = nil
end
assert.same({
bar = {
_ = {},
name = "bar",
url = "https://github.com/foo/baz.git",
},
fee = {
_ = {},
name = "fee",
url = "https://github.com/foo/fee.git",
dependencies = { "bar" },
},
}, clean(spec.plugins))
end)
end
it("handles opt from dep", function() it("handles opt from dep", function()
Config.options.defaults.lazy = false Config.options.defaults.lazy = false
@ -339,13 +182,10 @@ describe("plugin spec opt", function()
local spec = Plugin.Spec.new(test) local spec = Plugin.Spec.new(test)
assert(#spec.notifs == 0) assert(#spec.notifs == 0)
assert(vim.tbl_count(spec.plugins) == 1) assert(vim.tbl_count(spec.plugins) == 1)
Handler.resolve(spec.plugins.bar) assert(type(spec.plugins.bar.event) == "table")
-- vim.print(spec.plugins.bar._.handlers) assert(#spec.plugins.bar.event == 2)
local events = vim.tbl_keys(spec.plugins.bar._.handlers.event or {}) assert(vim.tbl_contains(spec.plugins.bar.event, "mod1"))
assert(type(events) == "table") assert(vim.tbl_contains(spec.plugins.bar.event, "mod2"))
assert(#events == 2)
assert(vim.tbl_contains(events, "mod1"))
assert(vim.tbl_contains(events, "mod2"))
end end
end) end)
end) end)
@ -401,16 +241,13 @@ describe("plugin spec opt", function()
{ { "foo/bar", event = "mod1" }, { "foo/bar", event = { "mod2" } } }, { { "foo/bar", event = "mod1" }, { "foo/bar", event = { "mod2" } } },
} }
for _, test in ipairs(tests) do for _, test in ipairs(tests) do
Handler.init()
local spec = Plugin.Spec.new(test) local spec = Plugin.Spec.new(test)
assert(#spec.notifs == 0) assert(#spec.notifs == 0)
assert(vim.tbl_count(spec.plugins) == 1) assert(vim.tbl_count(spec.plugins) == 1)
Handler.resolve(spec.plugins.bar) assert(type(spec.plugins.bar.event) == "table")
local events = spec.plugins.bar._.handlers.event assert(#spec.plugins.bar.event == 2)
assert(type(events) == "table") assert(vim.tbl_contains(spec.plugins.bar.event, "mod1"))
assert(vim.tbl_count(events) == 2) assert(vim.tbl_contains(spec.plugins.bar.event, "mod2"))
assert(events["mod1"])
assert(events["mod2"])
end end
end) end)
@ -423,6 +260,7 @@ describe("plugin spec opt", function()
} }
for test, ret in pairs(tests) do for test, ret in pairs(tests) do
local spec = Plugin.Spec.new(test) local spec = Plugin.Spec.new(test)
spec:fix_disabled()
assert(#spec.notifs == 0) assert(#spec.notifs == 0)
if ret then if ret then
assert(spec.plugins.bar) assert(spec.plugins.bar)
@ -433,94 +271,4 @@ describe("plugin spec opt", function()
end end
end 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) end)

View file

@ -1,23 +1,13 @@
local Util = require("lazy.util")
local Cache = require("lazy.core.cache") local Cache = require("lazy.core.cache")
local Helpers = require("tests.helpers") local Helpers = require("tests.helpers")
local Util = require("lazy.util")
describe("util", function() describe("util", function()
local rtp = vim.opt.rtp:get()
before_each(function() before_each(function()
---@type vim.Option
vim.opt.rtp = rtp
for k, v in pairs(package.loaded) do
if k:find("^foobar") then
package.loaded[k] = nil
end
end
Helpers.fs_rm("") Helpers.fs_rm("")
assert(not vim.uv.fs_stat(Helpers.path("")), "fs root should be deleted")
end) end)
it("lsmod lists all mods in dir", function() it("lsmod lists all mods in dir", function()
vim.opt.rtp:append(Helpers.path(""))
local tests = { local tests = {
{ {
root = "lua/foo", root = "lua/foo",
@ -45,6 +35,7 @@ describe("util", function()
}, },
} }
vim.opt.rtp:append(Helpers.path(""))
for t, test in ipairs(tests) do for t, test in ipairs(tests) do
local expected = vim.deepcopy(test.mods) local expected = vim.deepcopy(test.mods)
table.sort(expected) table.sort(expected)
@ -52,8 +43,10 @@ describe("util", function()
local files = Helpers.fs_create(test.files) local files = Helpers.fs_create(test.files)
-- test with empty cache -- test with empty cache
Cache.reset() Cache.cache = {}
local root = Util.find_root(test.mod) Cache.indexed = {}
Cache.indexed_rtp = false
local root = Cache.find_root(test.mod)
assert(root, "no root found for " .. test.mod .. " (test " .. t .. ")") assert(root, "no root found for " .. test.mod .. " (test " .. t .. ")")
assert.same(Helpers.path(test.root), root) assert.same(Helpers.path(test.root), root)
local mods = {} local mods = {}
@ -64,8 +57,13 @@ describe("util", function()
assert.same(expected, mods) assert.same(expected, mods)
-- fill the cache -- fill the cache
Cache.reset() Cache.cache = {}
root = Util.find_root(test.mod) 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(root, "no root found for " .. test.mod .. " (test " .. t .. ")")
assert.same(Helpers.path(test.root), root) assert.same(Helpers.path(test.root), root)
mods = {} mods = {}
@ -76,72 +74,4 @@ describe("util", function()
assert.same(expected, mods) assert.same(expected, mods)
end end
end) end)
it("find the correct root with dels", function()
Cache.reset()
vim.opt.rtp:append(Helpers.path("old"))
Helpers.fs_create({ "old/lua/foobar/init.lua" })
local root = Util.find_root("foobar")
assert(root, "foobar root not found")
assert.same(Helpers.path("old/lua/foobar"), root)
Helpers.fs_rm("old")
assert(not vim.uv.fs_stat(Helpers.path("old/lua/foobar")), "old/lua/foobar should not exist")
-- vim.opt.rtp = rtp
vim.opt.rtp:append(Helpers.path("new"))
Helpers.fs_create({ "new/lua/foobar/init.lua" })
root = Util.find_root("foobar")
assert(root, "foobar root not found")
assert.same(Helpers.path("new/lua/foobar"), root)
end)
it("merges correctly", function()
local tests = {
{
input = { { a = 1 }, { b = 2 } },
output = { a = 1, b = 2 },
},
{
input = { nil, { a = 1 }, { b = 2 } },
output = { a = 1, b = 2 },
},
{
input = { { a = 1 }, { b = 2 }, nil },
output = { a = 1, b = 2 },
},
{
input = { { a = 1 }, nil, { b = 2 } },
output = { a = 1, b = 2 },
},
{
input = { nil, { a = 1 }, nil, { b = 2 }, nil },
output = { a = 1, b = 2 },
},
{
input = { { a = 1 }, { a = 2 } },
output = { a = 2 },
},
{
input = { { a = { 1, 2 } }, { a = { 3 } } },
output = { a = { 3 } },
},
{
input = { { b = { 1, 2 } }, { a = { 3 }, b = vim.NIL } },
output = { a = { 3 } },
},
{
input = { { a = 1 }, { b = 2, a = vim.NIL } },
output = { b = 2 },
},
}
for _, test in ipairs(tests) do
local n = 0
for i in pairs(test.input) do
n = math.max(n, i)
end
assert.same(test.output, Util.merge(unpack(test.input, 1, n)))
end
end)
end) 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

@ -23,15 +23,15 @@ function M.fs_create(files)
end end
function M.fs_rm(dir) function M.fs_rm(dir)
dir = Util.norm(M.fs_root .. "/" .. dir) dir = Util.norm(M.fs_root .. dir)
Util.walk(dir, function(path, _, type) Util.walk(dir, function(path, _, type)
if type == "directory" then if type == "directory" then
vim.uv.fs_rmdir(path) vim.loop.fs_rmdir(path)
else else
vim.uv.fs_unlink(path) vim.loop.fs_unlink(path)
end end
end) end)
vim.uv.fs_rmdir(dir) vim.loop.fs_rmdir(dir)
end end
return M 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") local Runner = require("lazy.manage.runner")
describe("runner", function() describe("runner", function()
@ -31,11 +30,11 @@ describe("runner", function()
end, end,
} }
package.loaded["lazy.manage.task.test"]["async" .. i] = { package.loaded["lazy.manage.task.test"]["async" .. i] = {
---@async
---@param task LazyTask ---@param task LazyTask
run = function(task) run = function(task)
Async.yield() task:schedule(function()
table.insert(runs, { plugin = task.plugin.name, task = task.name }) table.insert(runs, { plugin = task.plugin.name, task = task.name })
end)
end, end,
} }
end end
@ -65,7 +64,7 @@ describe("runner", function()
local runner = Runner.new({ plugins = plugins, pipeline = { "test.test1", "test.skip", "test.test2" } }) local runner = Runner.new({ plugins = plugins, pipeline = { "test.test1", "test.skip", "test.test2" } })
runner:start() runner:start()
runner:wait() runner:wait()
assert.equal(4, #runs, runs) assert.equal(4, #runs)
end) end)
it("handles opts", function() 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" }, ["1.2.3+build"] = { major = 1, minor = 2, patch = 3, build = "build" },
} }
for input, output in pairs(tests) do for input, output in pairs(tests) do
output.input = input
it("correctly parses " .. input, function() it("correctly parses " .. input, function()
assert.same(output, v(input)) assert.same(output, v(input))
end) end)

View file

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