Showing preview only (279K chars total). Download the full file or copy to clipboard to get everything.
Repository: wbthomason/packer.nvim
Branch: master
Commit: ea0cc3c59f67
Files: 42
Total size: 266.3 KB
Directory structure:
gitextract_bz_3r55i/
├── .github/
│ ├── FUNDING.yml
│ ├── ISSUE_TEMPLATE/
│ │ ├── bug-report--packer-v1-.md
│ │ ├── bug-report--packer-v2-.md
│ │ └── feature_request.md
│ └── workflows/
│ ├── formatting.yaml
│ └── test.yaml
├── .gitignore
├── .lua-format
├── Dockerfile
├── LICENSE
├── Makefile
├── README.md
├── doc/
│ └── packer.txt
├── lua/
│ ├── packer/
│ │ ├── async.lua
│ │ ├── clean.lua
│ │ ├── compile.lua
│ │ ├── display.lua
│ │ ├── handlers.lua
│ │ ├── install.lua
│ │ ├── jobs.lua
│ │ ├── load.lua
│ │ ├── log.lua
│ │ ├── luarocks.lua
│ │ ├── plugin_types/
│ │ │ ├── git.lua
│ │ │ └── local.lua
│ │ ├── plugin_types.lua
│ │ ├── plugin_utils.lua
│ │ ├── result.lua
│ │ ├── snapshot.lua
│ │ ├── update.lua
│ │ └── util.lua
│ └── packer.lua
├── selene.toml
├── stylua.toml
├── tests/
│ ├── helpers.lua
│ ├── local_plugin_spec.lua
│ ├── minimal.vim
│ ├── packer_plugin_utils_spec.lua
│ ├── packer_use_spec.lua
│ ├── plugin_utils_spec.lua
│ └── snapshot_spec.lua
└── vim.toml
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/FUNDING.yml
================================================
# These are supported funding model platforms
github: wbthomason
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
================================================
FILE: .github/ISSUE_TEMPLATE/bug-report--packer-v1-.md
================================================
---
name: Bug report (packer v1)
about: Report a bug in packer.nvim, v1 (current master branch)
title: ''
labels: bug, v1
assignees: ''
---
<!-- Before creating an issue, please search the issue tracker and make sure packer.nvim is up to date -->
<!-- If your issue is a general usage question, please create a GitHub discussions thread: https://github.com/wbthomason/packer.nvim/discussions -->
- `nvim --version`:
- `git --version`:
- Operating system/version:
- Terminal name/version:
### Steps to reproduce
### Actual behaviour
### Expected behaviour
### packer files
<details>
<summary>Plugin specification file(s)</summary>
Post or link your plugin specification files here, if you aren't able to provide a minimal
reproducer
</details>
<details>
<summary>packer log file</summary>
Post the contents of ~/.cache/nvim/packer.nvim.log here
</details>
<details>
<summary>packer compiled file</summary>
Post the contents of `packer_compiled.vim` here
</details>
================================================
FILE: .github/ISSUE_TEMPLATE/bug-report--packer-v2-.md
================================================
---
name: Bug report (packer v2)
about: Report a bug in packer.nvim, v2 (currently in alpha)
title: ''
labels: bug, fix in v2
assignees: ''
---
<!-- Before creating an issue, please search the issue tracker and make sure packer.nvim is up to date -->
<!-- If your issue is a general usage question, please create a GitHub discussions thread: https://github.com/wbthomason/packer.nvim/discussions -->
<!-- Please do not report missing features that you would like added back using this template! -->
- `nvim --version`:
- `git --version`:
- Operating system/version:
- `packer` commit:
### Observed behaviour
### Expected behaviour
### Steps to reproduce
### packer files
<details>
<summary>Plugin specification table</summary>
Post or link to a file containing your table of plugin specifications here, if you aren't able to provide a minimal reproducer
</details>
<details>
<summary>packer log file</summary>
Post the contents of ~/.cache/nvim/packer.nvim.log here
</details>
================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.md
================================================
---
name: Feature request
about: Suggest a feature for packer.nvim
title: ''
labels: enhancement
assignees: ''
---
<!-- Before creating an issue, please search the issue tracker and make sure packer.nvim is up to date -->
<!-- If your issue is a general usage question, please create a GitHub discussions thread: https://github.com/wbthomason/packer.nvim/discussions -->
### Describe the feature
================================================
FILE: .github/workflows/formatting.yaml
================================================
name: formatting
on:
push:
paths-ignore:
- ".github/**"
- "*.md"
branches: ["master"]
jobs:
stylua:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: JohnnyMorganz/stylua-action@1.0.0
with:
token: ${{ secrets.GITHUB_TOKEN }}
args: --config-path=stylua.toml lua/
- name: Commit files
run: |
git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com"
git config --local user.name "github-actions[bot]"
if ! [[ -z $(git status -s) ]]; then
git commit -m "chore: format with stylua" lua/*
fi
- name: Push changes
uses: ad-m/github-push-action@master
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
branch: ${{ github.ref }}
================================================
FILE: .github/workflows/test.yaml
================================================
---
name: CI
on:
pull_request: ~
push:
branches:
- master
jobs:
build:
name: Run tests
runs-on: ubuntu-latest
strategy:
fail-fast: true
matrix:
neovim_branch: ["v0.5.0", "v0.6.1", "nightly"]
steps:
# Checkout packer
- uses: actions/checkout@v2
# Prepare taken from telescope
- name: Prepare
run: |
mkdir -p _neovim
curl -sL https://github.com/neovim/neovim/releases/download/${{ matrix.neovim_branch }}/nvim-linux64.tar.gz | tar xzf - --strip-components=1 -C "${PWD}/_neovim"
mkdir -p ~/.local/share/nvim/site/pack/vendor/start
git clone --depth 1 https://github.com/nvim-lua/plenary.nvim ~/.local/share/nvim/site/pack/vendor/start/plenary.nvim
ln -s $(pwd) ~/.local/share/nvim/site/pack/vendor/start
- name: Run tests
run: |
export PATH="${PWD}/_neovim/bin:${PATH}"
export VIM="${PWD}/_neovim/share/nvim/runtime"
nvim --version
make test
================================================
FILE: .gitignore
================================================
# Compiled Lua sources
luac.out
# luarocks build files
*.src.rock
*.zip
*.tar.gz
# Object files
*.o
*.os
*.ko
*.obj
*.elf
# Precompiled Headers
*.gch
*.pch
# Libraries
*.lib
*.a
*.la
*.lo
*.def
*.exp
# Shared objects (inc. Windows DLLs)
*.dll
*.so
*.so.*
*.dylib
# Executables
*.exe
*.out
*.app
*.i*86
*.x86_64
*.hex
# Vim swap files
*.swp
doc/tags
================================================
FILE: .lua-format
================================================
align_args: true
align_parameter: true
align_table_field: true
break_after_functioncall_lp: false
break_after_functiondef_lp: false
break_after_operator: false
break_after_table_lb: true
break_before_functioncall_rp: true
break_before_functiondef_rp: true
break_before_table_rb: true
chop_down_kv_table: true
chop_down_parameter: true
chop_down_table: false
column_limit: 100
column_table_limit: 100
continuation_indent_width: 2
double_quote_to_single_quote: false
extra_sep_at_table_end: false
indent_width: 2
keep_simple_control_block_one_line: true
keep_simple_function_one_line: true
single_quote_to_double_quote: false
spaces_before_call: 1
tab_width: 2
table_sep: ','
use_tab: false
================================================
FILE: Dockerfile
================================================
FROM archlinux:base-devel
WORKDIR /setup
RUN pacman -Sy git neovim python --noconfirm
RUN useradd -m test
USER test
RUN git clone --depth 1 https://github.com/nvim-lua/plenary.nvim ~/.local/share/nvim/site/pack/vendor/start/plenary.nvim
RUN mkdir -p /home/test/.cache/nvim/packer.nvim
RUN touch /home/test/.cache/nvim/packer.nvim/test_completion{,1,2,3}
USER test
RUN mkdir -p /home/test/.local/share/nvim/site/pack/packer/start/packer.nvim/
WORKDIR /home/test/.local/share/nvim/site/pack/packer/start/packer.nvim/
COPY . ./
USER root
RUN chmod 777 -R /home/test/.local/share/nvim/site/pack/packer/start/packer.nvim
RUN touch /home/test/.cache/nvim/packer.nvim/not_writeable
USER test
ENTRYPOINT make test
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2017 Wil Thomason
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: Makefile
================================================
test:
nvim --headless --noplugin -u tests/minimal.vim -c "PlenaryBustedDirectory tests/ { minimal_init = './tests/minimal.vim' }"
run:
docker build . -t neovim-stable:latest && docker run --rm -it --entrypoint bash neovim-stable:latest
run-test:
docker build . -t neovim-stable:latest && docker run --rm neovim-stable:latest
================================================
FILE: README.md
================================================
**NOTICE:**
This repository is currently unmaintained. For the time being (as of August, 2023), it is recommended to use one of the following plugin managers instead:
- [lazy.nvim](https://github.com/folke/lazy.nvim): Most stable and maintained plugin manager for Nvim.
- [pckr.nvim](https://github.com/lewis6991/pckr.nvim): Spiritual successor of packer.nvim. Functional but not as stable as lazy.nvim.
# packer.nvim
[](https://gitter.im/packer-nvim/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
[`use-package`](https://github.com/jwiegley/use-package) inspired plugin/package management for
Neovim.
Have questions? Start a [discussion](https://github.com/wbthomason/packer.nvim/discussions).
Have a problem or idea? Make an [issue](https://github.com/wbthomason/packer.nvim/issues) or a [PR](https://github.com/wbthomason/packer.nvim/pulls).
**Packer is built on native packages. You may wish to read `:h packages` before continuing**
## Table of Contents
1. [Features](#features)
2. [Requirements](#requirements)
3. [Quickstart](#quickstart)
4. [Bootstrapping](#bootstrapping)
5. [Usage](#usage)
1. [The startup function](#the-startup-function)
2. [Custom Initialization](#custom-initialization)
3. [Specifying Plugins](#specifying-plugins)
4. [Performing plugin management operations](#performing-plugin-management-operations)
5. [Extending packer](#extending-packer)
6. [Compiling Lazy-Loaders](#compiling-lazy-loaders)
7. [User autocommands](#user-autocommands)
8. [Using a floating window](#using-a-floating-window)
6. [Profiling](#profiling)
7. [Debugging](#debugging)
8. [Compatibility and known issues](#compatibility-and-known-issues)
9. [Contributors](#contributors)
## Features
- Declarative plugin specification
- Support for dependencies
- Support for Luarocks dependencies
- Expressive configuration and lazy-loading options
- Automatically compiles efficient lazy-loading code to improve startup time
- Uses native packages
- Extensible
- Written in Lua, configured in Lua
- Post-install/update hooks
- Uses jobs for async installation
- Support for `git` tags, branches, revisions, submodules
- Support for local plugins
## Requirements
- You need to be running **Neovim v0.5.0+**
- If you are on Windows 10, you need developer mode enabled in order to use local plugins (creating
symbolic links requires admin privileges on Windows - credit to @TimUntersberger for this note)
## Quickstart
To get started, first clone this repository to somewhere on your `packpath`, e.g.:
> Unix, Linux Installation
```shell
git clone --depth 1 https://github.com/wbthomason/packer.nvim\
~/.local/share/nvim/site/pack/packer/start/packer.nvim
```
If you use Arch Linux, there is also [an AUR
package](https://aur.archlinux.org/packages/nvim-packer-git/).
> Windows Powershell Installation
```shell
git clone https://github.com/wbthomason/packer.nvim "$env:LOCALAPPDATA\nvim-data\site\pack\packer\start\packer.nvim"
```
Then you can write your plugin specification in Lua, e.g. (in `~/.config/nvim/lua/plugins.lua`):
```lua
-- This file can be loaded by calling `lua require('plugins')` from your init.vim
-- Only required if you have packer configured as `opt`
vim.cmd [[packadd packer.nvim]]
return require('packer').startup(function(use)
-- Packer can manage itself
use 'wbthomason/packer.nvim'
-- Simple plugins can be specified as strings
use 'rstacruz/vim-closer'
-- Lazy loading:
-- Load on specific commands
use {'tpope/vim-dispatch', opt = true, cmd = {'Dispatch', 'Make', 'Focus', 'Start'}}
-- Load on an autocommand event
use {'andymass/vim-matchup', event = 'VimEnter'}
-- Load on a combination of conditions: specific filetypes or commands
-- Also run code after load (see the "config" key)
use {
'w0rp/ale',
ft = {'sh', 'zsh', 'bash', 'c', 'cpp', 'cmake', 'html', 'markdown', 'racket', 'vim', 'tex'},
cmd = 'ALEEnable',
config = 'vim.cmd[[ALEEnable]]'
}
-- Plugins can have dependencies on other plugins
use {
'haorenW1025/completion-nvim',
opt = true,
requires = {{'hrsh7th/vim-vsnip', opt = true}, {'hrsh7th/vim-vsnip-integ', opt = true}}
}
-- Plugins can also depend on rocks from luarocks.org:
use {
'my/supercoolplugin',
rocks = {'lpeg', {'lua-cjson', version = '2.1.0'}}
}
-- You can specify rocks in isolation
use_rocks 'penlight'
use_rocks {'lua-resty-http', 'lpeg'}
-- Local plugins can be included
use '~/projects/personal/hover.nvim'
-- Plugins can have post-install/update hooks
use {'iamcco/markdown-preview.nvim', run = 'cd app && yarn install', cmd = 'MarkdownPreview'}
-- Post-install/update hook with neovim command
use { 'nvim-treesitter/nvim-treesitter', run = ':TSUpdate' }
-- Post-install/update hook with call of vimscript function with argument
use { 'glacambre/firenvim', run = function() vim.fn['firenvim#install'](0) end }
-- Use specific branch, dependency and run lua file after load
use {
'glepnir/galaxyline.nvim', branch = 'main', config = function() require'statusline' end,
requires = {'kyazdani42/nvim-web-devicons'}
}
-- Use dependency and run lua function after load
use {
'lewis6991/gitsigns.nvim', requires = { 'nvim-lua/plenary.nvim' },
config = function() require('gitsigns').setup() end
}
-- You can specify multiple plugins in a single call
use {'tjdevries/colorbuddy.vim', {'nvim-treesitter/nvim-treesitter', opt = true}}
-- You can alias plugin names
use {'dracula/vim', as = 'dracula'}
end)
```
Note that if you get linter complaints about `use` being an undefined global, these errors are
spurious - `packer` injects `use` into the scope of the function passed to `startup`.
If these errors bother you, the easiest fix is to simply specify `use` as an argument to the
function you pass to `startup`, e.g.
```lua
packer.startup(function(use)
...your config...
end)
```
`packer` provides the following commands after you've run and configured `packer` with `require('packer').startup(...)`:
```
-- You must run this or `PackerSync` whenever you make changes to your plugin configuration
-- Regenerate compiled loader file
:PackerCompile
-- Remove any disabled or unused plugins
:PackerClean
-- Clean, then install missing plugins
:PackerInstall
-- Clean, then update and install plugins
-- supports the `--preview` flag as an optional first argument to preview updates
:PackerUpdate
-- Perform `PackerUpdate` and then `PackerCompile`
-- supports the `--preview` flag as an optional first argument to preview updates
:PackerSync
-- Show list of installed plugins
:PackerStatus
-- Loads opt plugin immediately
:PackerLoad completion-nvim ale
```
You can configure Neovim to automatically run `:PackerCompile` whenever `plugins.lua` is updated with
[an autocommand](https://neovim.io/doc/user/autocmd.html#:autocmd):
```
augroup packer_user_config
autocmd!
autocmd BufWritePost plugins.lua source <afile> | PackerCompile
augroup end
```
This autocommand can be placed in your `init.vim`, or any other startup file as per your setup.
Placing this in `plugins.lua` could look like this:
```lua
vim.cmd([[
augroup packer_user_config
autocmd!
autocmd BufWritePost plugins.lua source <afile> | PackerCompile
augroup end
]])
```
## Bootstrapping
If you want to automatically install and set up `packer.nvim` on any machine you clone your configuration to,
add the following snippet (which is due to @Iron-E and @khuedoan) somewhere in your config **before** your first usage of `packer`:
```lua
local ensure_packer = function()
local fn = vim.fn
local install_path = fn.stdpath('data')..'/site/pack/packer/start/packer.nvim'
if fn.empty(fn.glob(install_path)) > 0 then
fn.system({'git', 'clone', '--depth', '1', 'https://github.com/wbthomason/packer.nvim', install_path})
vim.cmd [[packadd packer.nvim]]
return true
end
return false
end
local packer_bootstrap = ensure_packer()
return require('packer').startup(function(use)
use 'wbthomason/packer.nvim'
-- My plugins here
-- use 'foo1/bar1.nvim'
-- use 'foo2/bar2.nvim'
-- Automatically set up your configuration after cloning packer.nvim
-- Put this at the end after all plugins
if packer_bootstrap then
require('packer').sync()
end
end)
```
You can also use the following command (with `packer` bootstrapped) to have `packer` setup your
configuration (or simply run updates) and close once all operations are completed:
```sh
$ nvim --headless -c 'autocmd User PackerComplete quitall' -c 'PackerSync'
```
## Usage
The above snippets give some examples of `packer` features and use. Examples include:
- My dotfiles:
- [Specification file](https://github.com/wbthomason/dotfiles/blob/linux/neovim/.config/nvim/lua/plugins.lua)
- [Loading file](https://github.com/wbthomason/dotfiles/blob/linux/neovim/.config/nvim/lua/plugins.lua)
- [Generated lazy-loader file](https://github.com/wbthomason/dotfiles/blob/linux/neovim/.config/nvim/plugin/packer_compiled.lua)
- An example using the `startup` method: [tjdevries](https://github.com/tjdevries/config_manager/blob/master/xdg_config/nvim/lua/tj/plugins.lua)
- Using this method, you do not require a "loading" file. You can simply `lua require('plugins')` from your `init.vim`
The following is a more in-depth explanation of `packer`'s features and use.
### The `startup` function
`packer` provides `packer.startup(spec)`, which is used in the above examples.
`startup` is a convenience function for simple setup and can be invoked as follows:
- `spec` can be a function: `packer.startup(function() use 'tjdevries/colorbuddy.vim' end)`
- `spec` can be a table with a function as its first element and config overrides as another element:
`packer.startup({function() use 'tjdevries/colorbuddy.vim' end, config = { ... }})`
- `spec` can be a table with a table of plugin specifications as its first element, config overrides as another element, and optional rock specifications as another element:
`packer.startup({{'tjdevries/colorbuddy.vim'}, config = { ... }, rocks = { ... }})`
### Custom Initialization
You are not required to use `packer.startup` if you prefer a more manual setup with finer control
over configuration and loading.
To take this approach, load `packer` like any other Lua module. You must call `packer.init()` before
performing any operations; it is recommended to call `packer.reset()` if you may be re-running your
specification code (e.g. by sourcing your plugin specification file with `luafile`).
You may pass a table of configuration values to `packer.init()` to customize its operation. The
default configuration values (and structure of the configuration table) are:
```lua
local packer = require('packer')
packer.util = require('packer.util')
packer.init({
ensure_dependencies = true, -- Should packer install plugin dependencies?
snapshot = nil, -- Name of the snapshot you would like to load at startup
snapshot_path = packer.util.join_paths(vim.fn.stdpath('cache'), 'packer.nvim'), -- Default save directory for snapshots
package_root = packer.util.join_paths(vim.fn.stdpath('data'), 'site', 'pack'),
compile_path = packer.util.join_paths(vim.fn.stdpath('config'), 'plugin', 'packer_compiled.lua'),
plugin_package = 'packer', -- The default package for plugins
max_jobs = nil, -- Limit the number of simultaneous jobs. nil means no limit
auto_clean = true, -- During sync(), remove unused plugins
compile_on_sync = true, -- During sync(), run packer.compile()
disable_commands = false, -- Disable creating commands
opt_default = false, -- Default to using opt (as opposed to start) plugins
transitive_opt = true, -- Make dependencies of opt plugins also opt by default
transitive_disable = true, -- Automatically disable dependencies of disabled plugins
auto_reload_compiled = true, -- Automatically reload the compiled file after creating it.
preview_updates = false, -- If true, always preview updates before choosing which plugins to update, same as `PackerUpdate --preview`.
git = {
cmd = 'git', -- The base command for git operations
subcommands = { -- Format strings for git subcommands
update = 'pull --ff-only --progress --rebase=false --force',
install = 'clone --depth %i --no-single-branch --progress',
fetch = 'fetch --depth 999999 --progress --force',
checkout = 'checkout %s --',
update_branch = 'merge --ff-only @{u}',
current_branch = 'branch --show-current',
diff = 'log --color=never --pretty=format:FMT --no-show-signature HEAD@{1}...HEAD',
diff_fmt = '%%h %%s (%%cr)',
get_rev = 'rev-parse --short HEAD',
get_msg = 'log --color=never --pretty=format:FMT --no-show-signature HEAD -n 1',
submodules = 'submodule update --init --recursive --progress'
},
depth = 1, -- Git clone depth
clone_timeout = 60, -- Timeout, in seconds, for git clones
default_url_format = 'https://github.com/%s' -- Lua format string used for "aaa/bbb" style plugins
},
display = {
non_interactive = false, -- If true, disable display windows for all operations
compact = false, -- If true, fold updates results by default
open_fn = nil, -- An optional function to open a window for packer's display
open_cmd = '65vnew \\[packer\\]', -- An optional command to open a window for packer's display
working_sym = '⟳', -- The symbol for a plugin being installed/updated
error_sym = '✗', -- The symbol for a plugin with an error in installation/updating
done_sym = '✓', -- The symbol for a plugin which has completed installation/updating
removed_sym = '-', -- The symbol for an unused plugin which was removed
moved_sym = '→', -- The symbol for a plugin which was moved (e.g. from opt to start)
header_sym = '━', -- The symbol for the header line in packer's display
show_all_info = true, -- Should packer show all update details automatically?
prompt_border = 'double', -- Border style of prompt popups.
keybindings = { -- Keybindings for the display window
quit = 'q',
toggle_update = 'u', -- only in preview
continue = 'c', -- only in preview
toggle_info = '<CR>',
diff = 'd',
prompt_revert = 'r',
}
},
luarocks = {
python_cmd = 'python' -- Set the python command to use for running hererocks
},
log = { level = 'warn' }, -- The default print log level. One of: "trace", "debug", "info", "warn", "error", "fatal".
profile = {
enable = false,
threshold = 1, -- integer in milliseconds, plugins which load faster than this won't be shown in profile output
},
autoremove = false, -- Remove disabled or unused plugins without prompting the user
})
```
### Specifying plugins
`packer` is based around declarative specification of plugins. You can declare a plugin using the
function `packer.use`, which I highly recommend locally binding to `use` for conciseness.
`use` takes either a string or a table. If a string is provided, it is treated as a plugin location
for a non-optional plugin with no additional configuration. Plugin locations may be specified as
1. Absolute paths to a local plugin
2. Full URLs (treated as plugins managed with `git`)
3. `username/repo` paths (treated as Github `git` plugins)
A table given to `use` can take two forms:
1. A list of plugin specifications (strings or tables)
2. A table specifying a single plugin. It must have a plugin location string as its first element,
and may additionally have a number of optional keyword elements, shown below:
```lua
use {
'myusername/example', -- The plugin location string
-- The following keys are all optional
disable = boolean, -- Mark a plugin as inactive
as = string, -- Specifies an alias under which to install the plugin
installer = function, -- Specifies custom installer. See "custom installers" below.
updater = function, -- Specifies custom updater. See "custom installers" below.
after = string or list, -- Specifies plugins to load before this plugin. See "sequencing" below
rtp = string, -- Specifies a subdirectory of the plugin to add to runtimepath.
opt = boolean, -- Manually marks a plugin as optional.
bufread = boolean, -- Manually specifying if a plugin needs BufRead after being loaded
branch = string, -- Specifies a git branch to use
tag = string, -- Specifies a git tag to use. Supports '*' for "latest tag"
commit = string, -- Specifies a git commit to use
lock = boolean, -- Skip updating this plugin in updates/syncs. Still cleans.
run = string, function, or table, -- Post-update/install hook. See "update/install hooks".
requires = string or list, -- Specifies plugin dependencies. See "dependencies".
rocks = string or list, -- Specifies Luarocks dependencies for the plugin
config = string or function, -- Specifies code to run after this plugin is loaded.
-- The setup key implies opt = true
setup = string or function, -- Specifies code to run before this plugin is loaded. The code is ran even if
-- the plugin is waiting for other conditions (ft, cond...) to be met.
-- The following keys all imply lazy-loading and imply opt = true
cmd = string or list, -- Specifies commands which load this plugin. Can be an autocmd pattern.
ft = string or list, -- Specifies filetypes which load this plugin.
keys = string or list, -- Specifies maps which load this plugin. See "Keybindings".
event = string or list, -- Specifies autocommand events which load this plugin.
fn = string or list -- Specifies functions which load this plugin.
cond = string, function, or list of strings/functions, -- Specifies a conditional test to load this plugin
module = string or list -- Specifies Lua module names for require. When requiring a string which starts
-- with one of these module names, the plugin will be loaded.
module_pattern = string/list -- Specifies Lua pattern of Lua module names for require. When
-- requiring a string which matches one of these patterns, the plugin will be loaded.
}
```
For the `cmd` option, the command may be a full command, or an autocommand pattern. If the command contains any
non-alphanumeric characters, it is assumed to be a pattern, and instead of creating a stub command, it creates
a CmdUndefined autocmd to load the plugin when a command that matches the pattern is invoked.
#### Checking plugin statuses
You can check whether or not a particular plugin is installed with `packer` as well as if that plugin is loaded.
To do this you can check for the plugin's name in the `packer_plugins` global table.
Plugins in this table are saved using only the last section of their names
e.g. `tpope/vim-fugitive` if installed will be under the key `vim-fugitive`.
```lua
if packer_plugins["vim-fugitive"] and packer_plugins["vim-fugitive"].loaded then
print("Vim fugitive is loaded")
-- other custom logic
end
```
**NOTE:** this table is only available *after* `packer_compiled.vim` is loaded so cannot be used till *after* plugins
have been loaded.
#### Luarocks support
You may specify that a plugin requires one or more Luarocks packages using the `rocks` key. This key
takes either a string specifying the name of a package (e.g. `rocks=lpeg`), or a list specifying one or more packages.
Entries in the list may either be strings, a list of strings or a table --- the latter case is used to specify arguments such as the
particular version of a package.
all supported luarocks keys are allowed except: `tree` and `local`. Environment variables for the luarocks command can also be
specified using the `env` key which takes a table as the value as shown below.
```lua
rocks = {'lpeg', {'lua-cjson', version = '2.1.0'}}
use_rocks {'lua-cjson', 'lua-resty-http'}
use_rocks {'luaformatter', server = 'https://luarocks.org/dev'}
use_rocks {'openssl' env = {OPENSSL_DIR = "/path/to/dir"}}
```
Currently, `packer` only supports equality constraints on package versions.
`packer` also provides the function `packer.luarocks.install_commands()`, which creates the
`PackerRocks <cmd> <packages...>` command. `<cmd>` must be one of "install" or "remove";
`<packages...>` is one or more package names (currently, version restrictions are not supported with
this command). Running `PackerRocks` will install or remove the given packages. You can use this
command even if you don't use `packer` to manage your plugins. However, please note that (1)
packages installed through `PackerRocks` **will** be removed by calls to `packer.luarocks.clean()`
(unless they are also part of a `packer` plugin specification), and (2) you will need to manually
invoke `packer.luarocks.setup_paths` (or otherwise modify your `package.path`) to ensure that Neovim
can find the installed packages.
Finally, `packer` provides the function `packer.use_rocks`, which takes a string or table specifying
one or more Luarocks packages as in the `rocks` key. You can use this to ensure that `packer`
downloads and manages some rocks which you want to use, but which are not associated with any
particular plugin.
#### Custom installers
You may specify a custom installer & updater for a plugin using the `installer` and `updater` keys.
Note that either both or none of these keys are required. These keys should be functions which take
as an argument a `display` object (from `lua/packer/display.lua`) and return an async function (per
`lua/packer/async.lua`) which (respectively) installs/updates the given plugin.
Providing the `installer`/`updater` keys overrides plugin type detection, but you still need to
provide a location string for the name of the plugin.
#### Update/install hooks
You may specify operations to be run after successful installs/updates of a plugin with the `run`
key. This key may either be a Lua function, which will be called with the `plugin` table for this
plugin (containing the information passed to `use` as well as output from the installation/update
commands, the installation path of the plugin, etc.), a string, or a table of functions and strings.
If an element of `run` is a string, then either:
1. If the first character of `run` is ":", it is treated as a Neovim command and executed.
2. Otherwise, `run` is treated as a shell command and run in the installation directory of the
plugin via `$SHELL -c '<run>'`.
#### Dependencies
Plugins may specify dependencies via the `requires` key. This key can be a string or a list (table).
If `requires` is a string, it is treated as specifying a single plugin. If a plugin with the name
given in `requires` is already known in the managed set, nothing happens. Otherwise, the string is
treated as a plugin location string and the corresponding plugin is added to the managed set.
If `requires` is a list, it is treated as a list of plugin specifications following the format given
above.
If `ensure_dependencies` is true, the plugins specified in `requires` will be installed.
Plugins specified in `requires` are removed when no active plugins require them.
#### Sequencing
You may specify a loading order for plugins using the `after` key. This key can be a string or a
list (table).
If `after` is a string, it must be the name of another plugin managed by `packer` (e.g. the final segment of a plugin's path - for a Github plugin `FooBar/Baz`, the name would be just `Baz`). If `after` is a table, it must be a list of plugin names. If a plugin has an alias (i.e. uses the `as` key), this alias is its name.
The set of plugins specified in a plugin's `after` key must **all** be loaded before the plugin
using `after` will be loaded. For example, in the specification
```lua
use {'FooBar/Baz', ft = 'bax'}
use {'Something/Else', after = 'Baz'}
```
the plugin `Else` will only be loaded after the plugin `Baz`, which itself is only loaded for files
with `bax` filetype.
#### Keybindings
Plugins may be lazy-loaded on the use of keybindings/maps. Individual keybindings are specified either as a string (in which case they are treated as normal mode maps) or a table in the format `{mode, map}`.
### Performing plugin management operations
`packer` exposes the following functions for common plugin management operations. In all of the
below, `plugins` is an optional table of plugin names; if not provided, the default is "all managed
plugins":
- `packer.install(plugins)`: Install the specified plugins if they are not already installed
- `packer.update(plugins)`: Update the specified plugins, installing any that are missing
- `packer.update(opts, plugins)`: First argument can be a table specifying options, such as `{preview_updates = true}` to preview potential changes before updating (same as `PackerUpdate --preview`).
- `packer.clean()`: Remove any disabled or no longer managed plugins
- `packer.sync(plugins)`: Perform a `clean` followed by an `update`.
- `packer.sync(opts, plugins)`: Can take same optional options as `update`.
- `packer.compile(path)`: Compile lazy-loader code and save to `path`.
- `packer.snapshot(snapshot_name, ...)`: Creates a snapshot file that will live under `config.snapshot_path/<snapshot_name>`. If `snapshot_name` is an absolute path, then that will be the location where the snapshot will be taken. Optionally, a list of plugins name can be provided to selectively choose the plugins to snapshot.
- `packer.rollback(snapshot_name, ...)`: Rollback plugins status a snapshot file that will live under `config.snapshot_path/<snapshot_name>`. If `snapshot_name` is an absolute path, then that will be the location where the snapshot will be taken. Optionally, a list of plugins name can be provided to selectively choose which plugins to revert.
- `packer.delete(snapshot_name)`: Deletes a snapshot file under `config.snapshot_path/<snapshot_name>`. If `snapshot_name` is an absolute path, then that will be the location where the snapshot will be deleted.
### Extending `packer`
You can add custom key handlers to `packer` by calling `packer.set_handler(name, func)` where `name`
is the key you wish to handle and `func` is a function with the signature `func(plugins, plugin,
value)` where `plugins` is the global table of managed plugins, `plugin` is the table for a specific
plugin, and `value` is the value associated with key `name` in `plugin`.
### Compiling Lazy-Loaders
To optimize startup time, `packer.nvim` compiles code to perform the lazy-loading operations you
specify. This means that you do not need to load `packer.nvim` unless you want to perform some
plugin management operations.
To generate the compiled code, call `packer.compile(path)`, where `path` is some file path on your
`runtimepath`, with a `.vim` extension. This will generate a blend of Lua and Vimscript to load and
configure all your lazy-loaded plugins (e.g. generating commands, autocommands, etc.) and save it to
`path`. Then, when you start vim, the file at `path` is loaded (because `path` must be on your
`runtimepath`), and lazy-loading works.
If `path` is not provided to `packer.compile`, the output file will default to the value of
`config.compile_path`.
The option `compile_on_sync`, which defaults to `true`, will run `packer.compile()` during
`packer.sync()`, if set to `true`. Note that otherwise, you **must** run `packer.compile` yourself
to generate the lazy-loader file!
**NOTE:** If you use a function value for `config` or `setup` keys in any plugin specifications, it
**must not** have any upvalues (i.e. captures). We currently use Lua's `string.dump` to compile
config/setup functions to bytecode, which has this limitation.
Additionally, if functions are given for these keys, the functions will be passed the plugin
name and information table as arguments.
### User autocommands
`packer` runs most of its operations asyncronously. If you would like to implement automations that
require knowing when the operations are complete, you can use the following `User` autocmds (see
`:help User` for more info on how to use):
- `PackerComplete`: Fires after install, update, clean, and sync asynchronous operations finish.
- `PackerCompileDone`: Fires after compiling (see [the section on compilation](#compiling-lazy-loaders))
### Using a floating window
You can configure Packer to use a floating window for command outputs by passing a utility
function to `packer`'s config:
```lua
packer.startup({function()
-- Your plugins here
end,
config = {
display = {
open_fn = require('packer.util').float,
}
}})
```
By default, this floating window will show doubled borders. If you want to customize the window
appearance, you can pass a configuration to `float`, which is the same configuration that would be
passed to `nvim_open_win`:
```lua
packer.startup({function()
-- Your plugins here
end,
config = {
display = {
open_fn = function()
return require('packer.util').float({ border = 'single' })
end
}
}})
```
## Profiling
Packer has built in functionality that can allow you to profile the time taken loading your plugins.
In order to use this functionality you must either enable profiling in your config, or pass in an argument
when running packer compile.
#### Setup via config
```lua
config = {
profile = {
enable = true,
threshold = 1 -- the amount in ms that a plugin's load time must be over for it to be included in the profile
}
}
```
#### Using the packer compile command
```vim
:PackerCompile profile=true
" or
:PackerCompile profile=false
```
#### Profiling usage
This will rebuild your `packer_compiled.vim` with profiling code included. In order to visualise the output of the profile
restart your neovim and run `PackerProfile`. This will open a window with the output of your profiling.
## Debugging
`packer.nvim` logs to `stdpath(cache)/packer.nvim.log`. Looking at this file is usually a good start
if something isn't working as expected.
## Compatibility and known issues
- **2021-07-31:** If you're on macOS, note that building Neovim with the version of `luv` from `homebrew` [will cause any `packer` command to crash](https://github.com/wbthomason/packer.nvim/issues/496#issuecomment-890371022). More about this issue at [neovim/neovim#15054](https://github.com/neovim/neovim/issues/15054).
- **2021-07-28:** `packer` will now highlight commits/plugin names with potentially breaking changes
(determined by looking for `breaking change` or `breaking_change`, case insensitive, in the update
commit bodies and headers) as `WarningMsg` in the status window.
- **2021-06-06**: Your Neovim must include https://github.com/neovim/neovim/pull/14659; `packer` uses the `noautocmd` key.
- **2021-04-19**: `packer` now provides built-in profiling for your config via the `packer_compiled`
file. Take a look at [the docs](#profiling) for more information!
- **2021-02-18**: Having trouble with Luarocks on macOS? See [this issue](https://github.com/wbthomason/packer.nvim/issues/180).
- **2021-01-19**: Basic Luarocks support has landed! Use the `rocks` key with a string or table to specify packages to install.
- **2020-12-10**: The `disable_commands` configuration flag now affects non-`startup` use as well. This means that, by default, `packer` will create commands for basic operations for you.
- **2020-11-13**: There is now a default implementation for a floating window `open_fn` in `packer.util`.
- **2020-09-04:** Due to changes to the Neovim `extmark` api (see: https://github.com/neovim/neovim/commit/3853276d9cacc99a2698117e904475dbf7033383), users will need to update to a version of Neovim **after** the aforementioned PR was merged. There are currently shims around the changed functions which should maintain support for earlier versions of Neovim, but these are intended to be temporary and will be removed by **2020-10-04**. Therefore Packer will not work with Neovim v0.4.4, which was released before the `extmark` change.
## Contributors
Many thanks to those who have contributed to the project! PRs and issues are always welcome. This
list is infrequently updated; please feel free to bug me if you're not listed here and you would
like to be.
- [@akinsho](https://github.com/akinsho)
- [@nanotee](https://github.com/nanotee)
- [@weilbith](https://github.com/weilbith)
- [@Iron-E](https://github.com/Iron-E)
- [@tjdevries](https://github.com/tjdevries)
- [@numToStr](https://github.com/numToStr)
- [@fsouza](https://github.com/fsouza)
- [@gbrlsnchs](https://github.com/gbrlsnchs)
- [@lewis6991](https://github.com/lewis6991)
- [@TimUntersberger](https://github.com/TimUntersberger)
- [@bfredl](https://github.com/bfredl)
- [@sunjon](https://github.com/sunjon)
- [@gwerbin](https://github.com/gwerbin)
- [@shadmansaleh](https://github.com/shadmansaleh)
- [@ur4ltz](https://github.com/ur4ltz)
- [@EdenEast](https://github.com/EdenEast)
- [@khuedoan](https://github.com/khuedoan)
- [@kevinhwang91](https://github.com/kevinhwang91)
- [@runiq](https://github.com/runiq)
- [@n3wborn](https://github.com/n3wborn)
- [@deathlyfrantic](https://github.com/deathlyfrantic)
- [@doctoromer](https://github.com/doctoromer)
- [@elianiva](https://github.com/elianiva)
- [@dundargoc](https://github.com/dundargoc)
- [@jdelkins](https://github.com/jdelkins)
- [@dsully](https://github.com/dsully)
================================================
FILE: doc/packer.txt
================================================
*packer.txt* A use-package inspired Neovim plugin manager
*packer.nvim*
Author: Wil Thomason <wil.thomason@gmail.com>
CONTENTS *packer-contents*
Introduction |packer-introduction|
Features |packer-intro-features|
Requirements |packer-intro-requirements|
Quickstart |packer-intro-quickstart|
Usage |packer-usage|
API |packer-api|
==============================================================================
INTRODUCTION *packer-introduction*
This is a Neovim plugin manager. It is written in Lua, uses the native
|packages| feature, and has features for declarative plugin configuration
inspired by the `use-package` library from Emacs.
==============================================================================
REQUIREMENTS *packer-intro-requirements*
- You need to be running Neovim v0.5.0+; `packer` makes use of extmarks and
other newly-added Neovim features.
- Your version of Neovim must be compiled with LuaJIT support; `packer` relies
on this to detect whether you are running Windows or a Unix-like OS (for path
separators)
- If you are on Windows 10, you need developer mode enabled in order to use
local plugins (creating symbolic links requires admin privileges on Windows
- credit to @TimUntersberger for this note)
==============================================================================
FEATURES *packer-intro-features*
- Declarative plugin specification
- Support for dependencies
- Support for Luarocks dependencies
- Expressive configuration and lazy-loading options
- Automatically compiles efficient lazy-loading code to improve startup time
- Uses native packages
- Extensible
- Written in Lua, configured in Lua
- Post-install/update hooks
- Uses jobs for async installation
- Support for `git` tags, branches, revisions, submodules
- Support for local plugins
- Support for saving/restoring snapshots for plugin versions (`git` only)
==============================================================================
QUICKSTART *packer-intro-quickstart*
To get started, first clone this repository to somewhere on your `packpath`, e.g.: >sh
git clone https://github.com/wbthomason/packer.nvim\
~/.local/share/nvim/site/pack/packer/opt/packer.nvim
Then you can write your plugin specification in Lua, e.g. (in `~/.config/nvim/lua/plugins.lua`): >lua
-- This file can be loaded by calling `lua require('plugins')` from your init.vim
-- Only required if you have packer in your `opt` pack
vim.cmd [[packadd packer.nvim]]
-- Only if your version of Neovim doesn't have https://github.com/neovim/neovim/pull/12632 merged
vim._update_package_paths()
return require('packer').startup(function()
-- Packer can manage itself as an optional plugin
use {'wbthomason/packer.nvim', opt = true}
-- Simple plugins can be specified as strings
use '9mm/vim-closer'
-- Lazy loading:
-- Load on specific commands
use {'tpope/vim-dispatch', opt = true, cmd = {'Dispatch', 'Make', 'Focus', 'Start'}}
-- Load on an autocommand event
use {'andymass/vim-matchup', event = 'VimEnter *'}
-- Load on a combination of conditions: specific filetypes or commands
-- Also run code after load (see the "config" key)
use {
'w0rp/ale',
ft = {'sh', 'zsh', 'bash', 'c', 'cpp', 'cmake', 'html', 'markdown', 'racket', 'vim', 'tex'},
cmd = 'ALEEnable',
config = 'vim.cmd[[ALEEnable]]'
}
-- Plugins can have dependencies on other plugins
use {
'haorenW1025/completion-nvim',
opt = true,
requires = {{'hrsh7th/vim-vsnip', opt = true}, {'hrsh7th/vim-vsnip-integ', opt = true}}
}
-- Local plugins can be included
use '~/projects/personal/hover.nvim'
-- Plugins can have post-install/update hooks
use {'iamcco/markdown-preview.nvim', run = 'cd app && yarn install', cmd = 'MarkdownPreview'}
-- You can specify multiple plugins in a single call
use {'tjdevries/colorbuddy.vim', {'nvim-treesitter/nvim-treesitter', opt = true}}
-- You can alias plugin names
use {'dracula/vim', as = 'dracula'}
end)
`packer` provides the following commands after you've run and configured `packer` with `require('packer').startup(...)`: *packer-default-commands* *packer-commands*
`PackerClean` *packer-commands-clean*
Remove any disabled or unused plugins.
`PackerCompile` *packer-commands-compile*
You must run this or `PackerSync` whenever you make changes to your plugin
configuration. Regenerate compiled loader file.
`PackerInstall` *packer-commands-install*
Clean, then install missing plugins.
`PackerUpdate` *packer-commands-update*
Clean, then update and install plugins.
Supports the `--preview` flag as an optional first argument to preview
updates.
`PackerSync` *packer-commands-sync*
Perform `PackerUpdate` and then `PackerCompile`.
Supports the `--preview` flag as an optional first argument to preview
updates.
`PackerLoad` *packer-commands-load*
Loads opt plugin immediately
`PackerSnapshot` *packer-commands-snapshot*
Snapshots your plugins to a file
`PackerSnapshotDelete` *packer-commands-delete*
Deletes a snapshot
`PackerSnapshotRollback` *packer-commands-rollback*
Rolls back plugins' commit specified by the snapshot
==============================================================================
USAGE *packer-usage*
Although the example in |packer-intro-quickstart| will be enough to get you
going for basic usage, `packer` has a number of other features and options
detailed in this section.
STARTUP *packer-startup*
The easiest way to use `packer` is via the |packer.startup()| function. In
short, `startup` is a convenience function for simple setup, and is invoked as
`packer.startup(spec)`, where:
- `spec` can be a function: >lua
packer.startup(function() use 'tjdevries/colorbuddy.vim' end)
- `spec` can be a table with a function as its first element and config
overrides as another element: >lua
packer.startup({
function() use 'tjdevries/colorbuddy.vim' end, config = { ... }
})
- `spec` can be a table with a table of plugin specifications as its first
element, config overrides as another element, and optional rock
specifications as another element: >lua
packer.startup({{'tjdevries/colorbuddy.vim'}, config = { ... }, rocks = { ... }})
See |packer-configuration| for the allowed configuration keys.
`startup` will handle calling |packer.init()| and |packer.reset()| for you, as
well as creating the commands given in |packer-commands|.
CUSTOM INITIALIZATION *packer-custom-initialization*
If you prefer a more manual setup with finer control over configuration and
loading, you may use custom initialization for `packer`. This approach has the
benefit of not requiring that the `packer` plugin be loaded unless you want to
perform plugin management operations, but it is more involved to use.
To take this approach, load `packer` like any other Lua module. You must call
|packer.init()| before performing any operations; it is recommended to call
|packer.reset()| if you may be re-running your specification code (e.g. by
sourcing your plugin specification file with `luafile`).
See |packer.init()| for more details on initialization; in short, `init` takes
a table of configuration values like that which can be passed to `startup`.
If you use custom initialization, you'll probably want to define commands to
load `packer` and perform common package management operations. The following
commands work well for this purpose: >vim
command! -nargs=* -complete=customlist,v:lua.require'packer'.plugin_complete PackerInstall lua require('packer').install(<f-args>)
command! -nargs=* -complete=customlist,v:lua.require'packer'.plugin_complete PackerUpdate lua require('packer').update(<f-args>)
command! -nargs=* -complete=customlist,v:lua.require'packer'.plugin_complete PackerSync lua require('packer').sync(<f-args>)
command! PackerClean packadd packer.nvim | lua require('plugins').clean()
command! PackerCompile packadd packer.nvim | lua require('plugins').compile('~/.config/nvim/plugin/packer_load.vim')
command! -bang -nargs=+ -complete=customlist,v:lua.require'packer'.loader_complete PackerLoad lua require('packer').loader(<f-args>, '<bang>')
CONFIGURATION *packer-configuration*
`packer` provides the following configuration variables, presented in the
structure of the `config` table expected by `startup` or `init`, with their
default values: >lua
{
ensure_dependencies = true, -- Should packer install plugin dependencies?
package_root = util.join_paths(vim.fn.stdpath('data'), 'site', 'pack'),
compile_path = util.join_paths(vim.fn.stdpath('config'), 'plugin', 'packer_compiled.lua'),
plugin_package = 'packer', -- The default package for plugins
max_jobs = nil, -- Limit the number of simultaneous jobs. nil means no limit
auto_clean = true, -- During sync(), remove unused plugins
compile_on_sync = true, -- During sync(), run packer.compile()
disable_commands = false, -- Disable creating commands
opt_default = false, -- Default to using opt (as opposed to start) plugins
transitive_opt = true, -- Make dependencies of opt plugins also opt by default
transitive_disable = true, -- Automatically disable dependencies of disabled plugins
auto_reload_compiled = true, -- Automatically reload the compiled file after creating it.
preview_updates = false, -- If true, always preview updates before choosing which plugins to update, same as `PackerUpdate --preview`.
git = {
cmd = 'git', -- The base command for git operations
subcommands = { -- Format strings for git subcommands
update = 'pull --ff-only --progress --rebase=false --force',
install = 'clone --depth %i --no-single-branch --progress',
fetch = 'fetch --depth 999999 --progress --force',
checkout = 'checkout %s --',
update_branch = 'merge --ff-only @{u}',
current_branch = 'branch --show-current',
diff = 'log --color=never --pretty=format:FMT --no-show-signature HEAD@{1}...HEAD',
diff_fmt = '%%h %%s (%%cr)',
get_rev = 'rev-parse --short HEAD',
get_msg = 'log --color=never --pretty=format:FMT --no-show-signature HEAD -n 1',
submodules = 'submodule update --init --recursive --progress'
},
depth = 1, -- Git clone depth
clone_timeout = 60, -- Timeout, in seconds, for git clones
default_url_format = 'https://github.com/%s' -- Lua format string used for "aaa/bbb" style plugins
},
log = { level = 'warn' }, -- The default print log level. One of: "trace", "debug", "info", "warn", "error", "fatal".
display = {
non_interactive = false, -- If true, disable display windows for all operations
compact = false, -- If true, fold updates results by default
open_fn = nil, -- An optional function to open a window for packer's display
open_cmd = '65vnew \\[packer\\]', -- An optional command to open a window for packer's display
working_sym = '⟳', -- The symbol for a plugin being installed/updated
error_sym = '✗', -- The symbol for a plugin with an error in installation/updating
done_sym = '✓', -- The symbol for a plugin which has completed installation/updating
removed_sym = '-', -- The symbol for an unused plugin which was removed
moved_sym = '→', -- The symbol for a plugin which was moved (e.g. from opt to start)
header_sym = '━', -- The symbol for the header line in packer's display
show_all_info = true, -- Should packer show all update details automatically?
prompt_border = 'double', -- Border style of prompt popups.
keybindings = { -- Keybindings for the display window
quit = 'q',
toggle_update = 'u', -- only in preview
continue = 'c', -- only in preview
toggle_info = '<CR>',
diff = 'd',
prompt_revert = 'r',
}
}
autoremove = false, -- Remove disabled or unused plugins without prompting the user
}
SPECIFYING PLUGINS *packer-specifying-plugins*
`packer` is based around declarative specification of plugins. You can declare
a plugin using the function |packer.use()|, which I highly recommend locally
binding to `use` for conciseness.
`use` takes either a string or a table. If a string is provided, it is treated
as a plugin location for a non-optional plugin with no additional
configuration. Plugin locations may be specified as:
1. Absolute paths to a local plugin
2. Full URLs (treated as plugins managed with `git`)
3. `username/repo` paths (treated as Github `git` plugins)
A table given to `use` can take two forms:
1. A list of plugin specifications (strings or tables)
2. A table specifying a single plugin. It must have a plugin location string
as its first element, and may additionally have a number of optional keyword
elements, detailed in |packer.use()|
CONFIGURING PLUGINS *packer-plugin-configuration*
`packer` allows you to configure plugins either before they are loaded (the
`setup` key described in |packer.use()|) or after they are loaded (the
`config` key described in |packer.use()|).
If functions are given for these keys, the functions will be passed the plugin
name and information table as arguments.
PLUGIN STATUSES *packer-plugin-status*
You can check whether or not a particular plugin is installed with `packer` as
well as if that plugin is loaded. To do this you can check for the plugin's
name in the `packer_plugins` global table. Plugins in this table are saved
using only the last section of their names e.g. `tpope/vim-fugitive` if
installed will be under the key `vim-fugitive`.
>lua
if packer_plugins["vim-fugitive"] and packer_plugins["vim-fugitive"].loaded then
print("Vim fugitive is loaded")
-- other custom logic
end
CUSTOM INSTALLERS *packer-custom-installers*
You may specify a custom installer & updater for a plugin using the
`installer` and `updater` keys in a plugin specification. Note that either
both or none of these keys are required. These keys should be functions which
take as an argument a `display` object (from `lua/packer/display.lua`) and
return an async function (per `lua/packer/async.lua`) which (respectively)
installs/updates the given plugin.
Providing the `installer`/`updater` keys overrides plugin type detection, but
you still need to provide a location string for the name of the plugin.
POST-UPDATE HOOKS *packer-plugin-hooks*
You may specify operations to be run after successful installs/updates of a
plugin with the `run` key. This key may either be a Lua function, which will be
called with the `plugin` table for this plugin (containing the information
passed to `use` as well as output from the installation/update commands, the
installation path of the plugin, etc.), a string, or a table of functions and
strings.
If an element of `run` is a string, then either:
1. If the first character of `run` is ":", it is treated as a Neovim command and
executed.
2. Otherwise, `run` is treated as a shell command and run in the installation
directory of the plugin via `$SHELL -c '<run>'`.
DEPENDENCIES *packer-plugin-dependencies*
Plugins may specify dependencies via the `requires` key in their specification
table. This key can be a string or a list (table).
If `requires` is a string, it is treated as specifying a single plugin. If a
plugin with the name given in `requires` is already known in the managed set,
nothing happens. Otherwise, the string is treated as a plugin location string
and the corresponding plugin is added to the managed set.
If `requires` is a list, it is treated as a list of plugin specifications
following the format given above.
If `ensure_dependencies` is true, the plugins specified in `requires` will be
installed.
Plugins specified in `requires` are removed when no active plugins require
them.
LUAROCKS *packer-plugin-luarocks*
You may specify that a plugin requires one or more Luarocks packages using the
`rocks` key. This key takes either a string specifying the name of a package
(e.g. `rocks=lpeg`), or a list specifying one or more packages. Entries in the
list may either be strings or lists --- the latter case is used to specify a
particular version of a package, e.g. `rocks = {'lpeg', {'lua-cjson',
'2.1.0'}}`.
Currently, `packer` only supports equality constraints on package versions.
`packer` also provides the function `packer.luarocks.install_commands()`, which
creates the `PackerRocks <cmd> <packages...>` command. `<cmd>` must be one of
"install" or "remove"; `<packages...>` is one or more package names (currently,
version restrictions are not supported with this command). Running `PackerRocks`
will install or remove the given packages. You can use this command even if you
don't use `packer` to manage your plugins. However, please note that (1)
packages installed through `PackerRocks` **will** be removed by calls to
`packer.luarocks.clean()` (unless they are also part of a `packer` plugin
specification), and (2) you will need to manually invoke
`packer.luarocks.setup_paths` (or otherwise modify your `package.path`) to
ensure that Neovim can find the installed packages.
Finally, `packer` provides the function `packer.use_rocks`, which takes a string
or table specifying one or more Luarocks packages as in the `rocks` key. You can
use this to ensure that `packer` downloads and manages some rocks which you want
to use, but which are not associated with any particular plugin.
SEQUENCING *packer-plugin-sequencing*
You may specify a loading order for plugins using the `after` key. This key can
be a string or a list (table).
If `after` is a string, it must be the name of another plugin managed by
`packer` (e.g. the final segment of a plugin's path - for a Github plugin
`FooBar/Baz`, the name would be just `Baz`). If `after` is a table, it must be a
list of plugin names. If a plugin has an alias (i.e. uses the `as` key), this
alias is its name.
The set of plugins specified in a plugin's `after` key must *all* be loaded
before the plugin using `after` will be loaded. For example, in the
specification >lua
use {'FooBar/Baz', ft = 'bax'}
use {'Something/Else', after = 'Baz'}
the plugin `Else` will only be loaded after the plugin `Baz`, which itself is
only loaded for files with `bax` filetype.
KEYBINDINGS *packer-plugin-keybindings*
Plugins may be lazy-loaded on the use of keybindings/maps. Individual
keybindings are specified under the `keys` key in a plugin specification
either as a string (in which case they are treated as normal mode maps) or a
table in the format `{mode, map}`.
LAZY-LOADING *packer-lazy-load*
To optimize startup time, `packer.nvim` compiles code to perform the
lazy-loading operations you specify. This means that you do not need to load
`packer.nvim` unless you want to perform some plugin management operations.
To generate the compiled code, call `packer.compile(path)`, where `path` is
some file path on your `runtimepath`, with a `.vim` extension. This will
generate a blend of Lua and Vimscript to load and configure all your
lazy-loaded plugins (e.g. generating commands, autocommands, etc.) and save it
to `path`. Then, when you start vim, the file at `path` is loaded (because
`path` must be on your `runtimepath`), and lazy-loading works.
If `path` is not provided to |packer.compile()|, the output file will default
to the value of `config.compile_path`.
The option `compile_on_sync`, which defaults to `true`, will run
`packer.compile()` during `packer.sync()`, if set to `true`.
Note that otherwise, you **must** run `packer.compile` yourself to generate
the lazy-loader file!
USING A FLOATING WINDOW *packer-floating-window*
You can configure Packer to use a floating window for command outputs by
passing a utility function to `packer`'s config: >lua
packer.startup({function()
-- Your plugins here
end,
config = {
display = {
open_fn = require('packer.util').float,
}
}})
<
By default, this floating window will show doubled borders. If you want to
customize the window appearance, you can pass a configuration to `float`,
which is the same configuration that would be passed to |nvim_open_win|: >lua
packer.startup({function()
-- Your plugins here
end,
config = {
display = {
open_fn = function()
return require('packer.util').float({ border = 'single' })
end
}
}})
<
PROFILING PLUGINS *packer-profiling*
You can measure how long it takes your plugins to load using packer's builtin
profiling functionality.
In order to use this functionality you must either enable profiling in your config, or pass in an argument
when running packer compile.
Setup via config >lua
config = {
profile = {
enable = true,
threshold = 1 -- the amount in ms that a plugin's load time must be over for it to be included in the profile
}
}
<
Using the packer compile command
>vim
:PackerCompile profile=true
" or
:PackerCompile profile=false
<
NOTE you can also set a `threshold` in your profile config which is a number
in `ms` above which plugin load times will be show e.g. if you set a threshold
value of `3` then any plugin that loads slower than `3ms` will not be included in
the output window.
This will rebuild your `packer_compiled.vim` with profiling code included. In order to visualise the output of the profile
Restart your neovim and run `PackerProfile`. This will open a window with the output of your profiling.
EXTENDING PACKER *packer-extending*
You can add custom key handlers to `packer` by calling
`packer.set_handler(name, func)` where `name` is the key you wish to handle
and `func` is a function with the signature `func(plugins, plugin, value)`
where `plugins` is the global table of managed plugins, `plugin` is the table
for a specific plugin, and `value` is the value associated with key `name` in
`plugin`.
RESULTS WINDOW KEYBINDINGS *packer-results-keybindings*
Once an operation completes, the results are shown in the display window.
`packer` sets up default keybindings for this window:
q close the display window
<CR> toggle information about a particular plugin
r revert an update
They can be configured by changing the value of `config.display.keybindings`
(see |packer-configuration|). Setting it to `false` will disable all keybindings.
Setting any of its keys to `false` will disable the corresponding keybinding.
USER AUTOCMDS *packer-user-autocmds*
`packer` runs most of its operations asyncronously. If you would like to
implement automations that require knowing when the operations are complete,
you can use the following User autocmds (see |User| for more info on how to
use):
`PackerComplete` Fires after install, update, clean, and sync
asynchronous operations finish.
`PackerCompileDone` Fires after compiling (see |packer-lazy-load|)
==============================================================================
API *packer-api*
clean() *packer.clean()*
`clean` scans for and removes all disabled or no longer managed plugins. It is
invoked without arguments.
compile() *packer.compile()*
`compile` builds lazy-loader code from your plugin specification and saves it
to either `config.compile_path` if it is invoked with no argument, or to the
path it is invoked with if it is given a single argument. This path should end
in `.vim` and be on your |runtimepath| in order for lazy-loading to work. You
**must** call `compile` to update lazy-loaders after your configuration
changes.
init() *packer.init()*
Initializes `packer`; must be called before any calls to any other `packer`
function. Takes an optional table of configuration values as described in
|packer-configuration|.
install() *packer.install()*
`install` installs any missing plugins, runs post-update hooks, and updates
rplugins (|remote-plugin|) and helptags.
It can be invoked with no arguments or with a list of plugin names to install.
These plugin names must already be managed by `packer` via a call to
|packer.use()|.
reset() *packer.reset()*
`reset` empties the set of managed plugins. Called with no arguments; used to
ensure plugin specifications are reinitialized if the specification file is
reloaded. Called by |packer.startup()| or manually before calling
|packer.use()|.
set_handler() *packer.set_handler()*
`set_handler` allows custom extension of `packer`. See |packer-extending| for
details.
startup() *packer.startup()*
`startup` is a convenience function for simple setup. See |packer-startup| for
details.
sync() *packer.sync()*
`sync` runs |packer.clean()| followed by |packer.update()|.
Supports options as the first argument, see |packer.update()|.
update() *packer.update()*
`update` installs any missing plugins, updates all installed plugins, runs
post-update hooks, and updates rplugins (|remote-plugin|) and helptags.
It can be invoked with no arguments or with a list of plugin names to update.
These plugin names must already be managed by `packer` via a call to
|packer.use()|.
Additionally, the first argument can be a table specifying options,
such as `update({preview_updates = true}, ...)` to preview potential changes before updating
(same as `PackerUpdate --preview`).
snapshot(snapshot_name, ...) *packer.snapshot()*
`snapshot` takes the rev of all the installed plugins and serializes them into a Lua table which will be saved under `config.snapshot_path` (which is the directory that will hold all the snapshots files) as `config.snapshot_path/<snapshot_name>` or an absolute path provided by the users.
Optionally plugins name can be specified so that only those plugins will be
snapshotted.
Snapshot files can be loaded manually via `dofile` which will return a table with the plugins name as keys the commit short hash as value.
delete(snapshot_name) *packer.delete()*
`delete` deletes a snapshot given the name or the absolute path.
rollback(snapshot_name, ...) *packer.rollback()*
`rollback` reverts all plugins or only the specified as extra arguments to the commit specified in the snapshot file
use() *packer.use()*
`use` allows you to add one or more plugins to the managed set. It can be
invoked as follows:
- With a single plugin location string, e.g. `use <STRING>`
- With a single plugin specification table, e.g. >lua
use {
'myusername/example', -- The plugin location string
-- The following keys are all optional
disable = boolean, -- Mark a plugin as inactive
as = string, -- Specifies an alias under which to install the plugin
installer = function, -- Specifies custom installer. See |packer-custom-installers|
updater = function, -- Specifies custom updater. See |packer-custom-installers|
after = string or list, -- Specifies plugins to load before this plugin.
rtp = string, -- Specifies a subdirectory of the plugin to add to runtimepath.
opt = boolean, -- Manually marks a plugin as optional.
bufread = boolean, -- Manually specifying if a plugin needs BufRead after being loaded
branch = string, -- Specifies a git branch to use
tag = string, -- Specifies a git tag to use. Supports '*' for "latest tag"
commit = string, -- Specifies a git commit to use
lock = boolean, -- Skip updating this plugin in updates/syncs. Still cleans.
run = string, function, or table -- Post-update/install hook. See |packer-plugin-hooks|
requires = string or list -- Specifies plugin dependencies. See |packer-plugin-dependencies|
config = string or function, -- Specifies code to run after this plugin is loaded.
rocks = string or list, -- Specifies Luarocks dependencies for the plugin
-- The following keys all imply lazy-loading
cmd = string or list, -- Specifies commands which load this plugin. Can be an autocmd pattern.
ft = string or list, -- Specifies filetypes which load this plugin.
keys = string or list, -- Specifies maps which load this plugin. See |packer-plugin-keybindings|
event = string or list, -- Specifies autocommand events which load this plugin.
fn = string or list -- Specifies functions which load this plugin.
cond = string, function, or list of strings/functions, -- Specifies a conditional test to load this plugin
setup = string or function, -- Specifies code to run before this plugin is loaded. The code is ran even if
-- the plugin is waiting for other conditions (ft, cond...) to be met.
module = string or list -- Specifies Lua module names for require. When requiring a string which starts
-- with one of these module names, the plugin will be loaded.
module_pattern = string/list -- Specifies Lua pattern of Lua module names for require. When requiring a string
-- which matches one of these patterns, the plugin will be loaded.
}
- With a list of plugins specified in either of the above two forms
For the *cmd* option, the command may be a full command, or an autocommand pattern. If the command contains any
non-alphanumeric characters, it is assumed to be a pattern, and instead of creating a stub command, it creates
a CmdUndefined autocmd to load the plugin when a command that matches the pattern is invoked.
vim:tw=78:ts=2:ft=help:norl:
================================================
FILE: lua/packer/async.lua
================================================
-- Adapted from https://ms-jpq.github.io/neovim-async-tutorial/
local log = require 'packer.log'
local yield = coroutine.yield
local resume = coroutine.resume
local thread_create = coroutine.create
local function EMPTY_CALLBACK() end
local function step(func, callback)
local thread = thread_create(func)
local tick = nil
tick = function(...)
local ok, val = resume(thread, ...)
if ok then
if type(val) == 'function' then
val(tick)
else
(callback or EMPTY_CALLBACK)(val)
end
else
log.error('Error in coroutine: ' .. val);
(callback or EMPTY_CALLBACK)(nil)
end
end
tick()
end
local function wrap(func)
return function(...)
local params = { ... }
return function(tick)
params[#params + 1] = tick
return func(unpack(params))
end
end
end
local function join(...)
local thunks = { ... }
local thunk_all = function(s)
if #thunks == 0 then
return s()
end
local to_go = #thunks
local results = {}
for i, thunk in ipairs(thunks) do
local callback = function(...)
results[i] = { ... }
if to_go == 1 then
s(unpack(results))
else
to_go = to_go - 1
end
end
thunk(callback)
end
end
return thunk_all
end
local function wait_all(...)
return yield(join(...))
end
local function pool(n, interrupt_check, ...)
local thunks = { ... }
return function(s)
if #thunks == 0 then
return s()
end
local remaining = { select(n + 1, unpack(thunks)) }
local results = {}
local to_go = #thunks
local make_callback = nil
make_callback = function(idx, left)
local i = (left == nil) and idx or (idx + left)
return function(...)
results[i] = { ... }
to_go = to_go - 1
if to_go == 0 then
s(unpack(results))
elseif not interrupt_check or not interrupt_check() then
if remaining and #remaining > 0 then
local next_task = table.remove(remaining)
next_task(make_callback(n, #remaining + 1))
end
end
end
end
for i = 1, math.min(n, #thunks) do
local thunk = thunks[i]
thunk(make_callback(i))
end
end
end
local function wait_pool(limit, ...)
return yield(pool(limit, false, ...))
end
local function interruptible_wait_pool(limit, interrupt_check, ...)
return yield(pool(limit, interrupt_check, ...))
end
local function main(f)
vim.schedule(f)
end
local M = {
--- Wrapper for functions that do not take a callback to make async functions
sync = wrap(step),
--- Alias for yielding to await the result of an async function
wait = yield,
--- Await the completion of a full set of async functions
wait_all = wait_all,
--- Await the completion of a full set of async functions, with a limit on how many functions can
-- run simultaneously
wait_pool = wait_pool,
--- Like wait_pool, but additionally checks at every function completion to see if a condition is
-- met indicating that it should keep running the remaining tasks
interruptible_wait_pool = interruptible_wait_pool,
--- Wrapper for functions that do take a callback to make async functions
wrap = wrap,
--- Convenience function to ensure a function runs on the main "thread" (i.e. for functions which
-- use Neovim functions, etc.)
main = main,
}
return M
================================================
FILE: lua/packer/clean.lua
================================================
local plugin_utils = require 'packer.plugin_utils'
local a = require 'packer.async'
local display = require 'packer.display'
local log = require 'packer.log'
local util = require 'packer.util'
local await = a.wait
local async = a.sync
local config
local PLUGIN_OPTIONAL_LIST = 1
local PLUGIN_START_LIST = 2
local function is_dirty(plugin, typ)
return (plugin.opt and typ == PLUGIN_START_LIST) or (not plugin.opt and typ == PLUGIN_OPTIONAL_LIST)
end
-- Find and remove any plugins not currently configured for use
local clean_plugins = function(_, plugins, fs_state, results)
return async(function()
log.debug 'Starting clean'
local dirty_plugins = {}
results = results or {}
results.removals = results.removals or {}
local opt_plugins = vim.deepcopy(fs_state.opt)
local start_plugins = vim.deepcopy(fs_state.start)
local missing_plugins = fs_state.missing
-- test for dirty / 'missing' plugins
for _, plugin_config in pairs(plugins) do
local path = plugin_config.install_path
local plugin_source = nil
if opt_plugins[path] then
plugin_source = PLUGIN_OPTIONAL_LIST
opt_plugins[path] = nil
elseif start_plugins[path] then
plugin_source = PLUGIN_START_LIST
start_plugins[path] = nil
end
-- We don't want to report paths which don't exist for removal; that will confuse people
local path_exists = false
if missing_plugins[plugin_config.short_name] or plugin_config.disable then
path_exists = vim.loop.fs_stat(path) ~= nil
end
local plugin_missing = path_exists and missing_plugins[plugin_config.short_name]
local disabled_but_installed = path_exists and plugin_config.disable
if plugin_missing or is_dirty(plugin_config, plugin_source) or disabled_but_installed then
dirty_plugins[#dirty_plugins + 1] = path
end
end
-- Any path which was not set to `nil` above will be set to dirty here
local function mark_remaining_as_dirty(plugin_list)
for path, _ in pairs(plugin_list) do
dirty_plugins[#dirty_plugins + 1] = path
end
end
mark_remaining_as_dirty(opt_plugins)
mark_remaining_as_dirty(start_plugins)
if next(dirty_plugins) then
local lines = {}
for _, path in ipairs(dirty_plugins) do
table.insert(lines, ' - ' .. path)
end
await(a.main)
if config.autoremove or await(display.ask_user('Removing the following directories. OK? (y/N)', lines)) then
results.removals = dirty_plugins
log.debug('Removed ' .. vim.inspect(dirty_plugins))
for _, path in ipairs(dirty_plugins) do
local result = vim.fn.delete(path, 'rf')
if result == -1 then
log.warn('Could not remove ' .. path)
end
end
else
log.warn 'Cleaning cancelled!'
end
else
log.info 'Already clean!'
end
end)
end
local function cfg(_config)
config = _config
end
local clean = setmetatable({ cfg = cfg }, { __call = clean_plugins })
return clean
================================================
FILE: lua/packer/compile.lua
================================================
-- Compiling plugin specifications to Lua for lazy-loading
local util = require 'packer.util'
local log = require 'packer.log'
local fmt = string.format
local luarocks = require 'packer.luarocks'
local config
local function cfg(_config)
config = _config.profile
end
local feature_guard = [[
if !has('nvim-0.5')
echohl WarningMsg
echom "Invalid Neovim version for packer.nvim!"
echohl None
finish
endif
packadd packer.nvim
try
]]
local feature_guard_lua = [[
if vim.api.nvim_call_function('has', {'nvim-0.5'}) ~= 1 then
vim.api.nvim_command('echohl WarningMsg | echom "Invalid Neovim version for packer.nvim! | echohl None"')
return
end
vim.api.nvim_command('packadd packer.nvim')
local no_errors, error_msg = pcall(function()
]]
local enter_packer_compile = [[
_G._packer = _G._packer or {}
_G._packer.inside_compile = true
]]
local exit_packer_compile = [[
_G._packer.inside_compile = false
if _G._packer.needs_bufread == true then
vim.cmd("doautocmd BufRead")
end
_G._packer.needs_bufread = false
]]
local catch_errors = [[
catch
echohl ErrorMsg
echom "Error in packer_compiled: " .. v:exception
echom "Please check your config for correctness"
echohl None
endtry
]]
local catch_errors_lua = [[
end)
if not no_errors then
error_msg = error_msg:gsub('"', '\\"')
vim.api.nvim_command('echohl ErrorMsg | echom "Error in packer_compiled: '..error_msg..'" | echom "Please check your config for correctness" | echohl None')
end
]]
---@param should_profile boolean
---@return string
local profile_time = function(should_profile)
return fmt(
[[
local time
local profile_info
local should_profile = %s
if should_profile then
local hrtime = vim.loop.hrtime
profile_info = {}
time = function(chunk, start)
if start then
profile_info[chunk] = hrtime()
else
profile_info[chunk] = (hrtime() - profile_info[chunk]) / 1e6
end
end
else
time = function(chunk, start) end
end
]],
vim.inspect(should_profile)
)
end
local profile_output = [[
local function save_profiles(threshold)
local sorted_times = {}
for chunk_name, time_taken in pairs(profile_info) do
sorted_times[#sorted_times + 1] = {chunk_name, time_taken}
end
table.sort(sorted_times, function(a, b) return a[2] > b[2] end)
local results = {}
for i, elem in ipairs(sorted_times) do
if not threshold or threshold and elem[2] > threshold then
results[i] = elem[1] .. ' took ' .. elem[2] .. 'ms'
end
end
if threshold then
table.insert(results, '(Only showing plugins that took longer than ' .. threshold .. ' ms ' .. 'to load)')
end
_G._packer.profile_output = results
end
]]
---@param threshold number
---@return string
local conditionally_output_profile = function(threshold)
if threshold then
return fmt(
[[
if should_profile then save_profiles(%d) end
]],
threshold
)
else
return [[
if should_profile then save_profiles() end
]]
end
end
local try_loadstring = [[
local function try_loadstring(s, component, name)
local success, result = pcall(loadstring(s), name, _G.packer_plugins[name])
if not success then
vim.schedule(function()
vim.api.nvim_notify('packer.nvim: Error running ' .. component .. ' for ' .. name .. ': ' .. result, vim.log.levels.ERROR, {})
end)
end
return result
end
]]
local module_loader = [[
local lazy_load_called = {['packer.load'] = true}
local function lazy_load_module(module_name)
local to_load = {}
if lazy_load_called[module_name] then return nil end
lazy_load_called[module_name] = true
for module_pat, plugin_name in pairs(module_lazy_loads) do
if not _G.packer_plugins[plugin_name].loaded and string.match(module_name, module_pat) then
to_load[#to_load + 1] = plugin_name
end
end
if #to_load > 0 then
require('packer.load')(to_load, {module = module_name}, _G.packer_plugins)
local loaded_mod = package.loaded[module_name]
if loaded_mod then
return function(modname) return loaded_mod end
end
end
end
if not vim.g.packer_custom_loader_enabled then
table.insert(package.loaders, 1, lazy_load_module)
vim.g.packer_custom_loader_enabled = true
end
]]
local function timed_chunk(chunk, name, output_table)
output_table = output_table or {}
output_table[#output_table + 1] = 'time([[' .. name .. ']], true)'
if type(chunk) == 'string' then
output_table[#output_table + 1] = chunk
else
vim.list_extend(output_table, chunk)
end
output_table[#output_table + 1] = 'time([[' .. name .. ']], false)'
return output_table
end
local function dump_loaders(loaders)
local result = vim.deepcopy(loaders)
for k, _ in pairs(result) do
if result[k].only_setup or result[k].only_sequence then
result[k].loaded = true
end
result[k].only_setup = nil
result[k].only_sequence = nil
end
return vim.inspect(result)
end
local function make_try_loadstring(item, chunk, name)
local bytecode = string.dump(item, true)
local executable_string = 'try_loadstring(' .. vim.inspect(bytecode) .. ', "' .. chunk .. '", "' .. name .. '")'
return executable_string, bytecode
end
local after_plugin_pattern = table.concat({ 'after', 'plugin', [[**/*.\(vim\|lua\)]] }, util.get_separator())
local function detect_after_plugin(name, plugin_path)
local path = plugin_path .. util.get_separator() .. after_plugin_pattern
local glob_ok, files = pcall(vim.fn.glob, path, false, true)
if not glob_ok then
if string.find(files, 'E77') then
return { path }
else
log.error('Error compiling ' .. name .. ': ' .. vim.inspect(files))
error(files)
end
elseif #files > 0 then
return files
end
return nil
end
local ftdetect_patterns = {
table.concat({ 'ftdetect', [[**/*.\(vim\|lua\)]] }, util.get_separator()),
table.concat({ 'after', 'ftdetect', [[**/*.\(vim\|lua\)]] }, util.get_separator()),
}
local function detect_ftdetect(name, plugin_path)
local paths = {
plugin_path .. util.get_separator() .. ftdetect_patterns[1],
plugin_path .. util.get_separator() .. ftdetect_patterns[2],
}
local source_paths = {}
for i = 1, 2 do
local path = paths[i]
local glob_ok, files = pcall(vim.fn.glob, path, false, true)
if not glob_ok then
if string.find(files, 'E77') then
source_paths[#source_paths + 1] = path
else
log.error('Error compiling ' .. name .. ': ' .. vim.inspect(files))
error(files)
end
elseif #files > 0 then
vim.list_extend(source_paths, files)
end
end
return source_paths
end
local source_dirs = { 'ftdetect', 'ftplugin', 'after/ftdetect', 'after/ftplugin' }
local function detect_bufread(plugin_path)
local path = plugin_path
for i = 1, 4 do
if #vim.fn.finddir(source_dirs[i], path) > 0 then
return true
end
end
return false
end
local function make_loaders(_, plugins, output_lua, should_profile)
local loaders = {}
local configs = {}
local rtps = {}
local setup = {}
local fts = {}
local events = {}
local condition_ids = {}
local commands = {}
local keymaps = {}
local after = {}
local fns = {}
local ftdetect_paths = {}
local module_lazy_loads = {}
for name, plugin in pairs(plugins) do
if not plugin.disable then
plugin.simple_load = true
local quote_name = "'" .. name .. "'"
if plugin.config and not plugin.executable_config then
plugin.simple_load = false
plugin.executable_config = {}
if type(plugin.config) ~= 'table' then
plugin.config = { plugin.config }
end
for i, config_item in ipairs(plugin.config) do
local executable_string = config_item
if type(config_item) == 'function' then
local bytecode
executable_string, bytecode = make_try_loadstring(config_item, 'config', name)
plugin.config[i] = bytecode
end
table.insert(plugin.executable_config, executable_string)
end
end
local path = plugin.install_path
if plugin.rtp then
path = util.join_paths(plugin.install_path, plugin.rtp)
table.insert(rtps, path)
end
loaders[name] = {
loaded = not plugin.opt,
config = plugin.config,
path = path,
only_sequence = plugin.manual_opt == nil,
only_setup = false,
}
if plugin.opt then
plugin.simple_load = false
loaders[name].after_files = detect_after_plugin(name, loaders[name].path)
if plugin.bufread ~= nil then
loaders[name].needs_bufread = plugin.bufread
else
loaders[name].needs_bufread = detect_bufread(loaders[name].path)
end
end
if plugin.setup then
plugin.simple_load = false
if type(plugin.setup) ~= 'table' then
plugin.setup = { plugin.setup }
end
for i, setup_item in ipairs(plugin.setup) do
if type(setup_item) == 'function' then
plugin.setup[i], _ = make_try_loadstring(setup_item, 'setup', name)
end
end
loaders[name].only_setup = plugin.manual_opt == nil
setup[name] = plugin.setup
end
-- Keep this as first opt loader to maintain only_cond ?
if plugin.cond ~= nil then
plugin.simple_load = false
loaders[name].only_sequence = false
loaders[name].only_setup = false
loaders[name].only_cond = true
if type(plugin.cond) ~= 'table' then
plugin.cond = { plugin.cond }
end
for _, condition in ipairs(plugin.cond) do
loaders[name].cond = {}
if type(condition) == 'function' then
_, condition = make_try_loadstring(condition, 'condition', name)
elseif type(condition) == 'string' then
condition = 'return ' .. condition
end
condition_ids[condition] = condition_ids[condition] or {}
table.insert(loaders[name].cond, condition)
table.insert(condition_ids[condition], name)
end
end
-- Add the git URL for displaying in PackerStatus and PackerSync. https://github.com/wbthomason/packer.nvim/issues/542
loaders[name].url = plugin.url
if plugin.ft then
plugin.simple_load = false
loaders[name].only_sequence = false
loaders[name].only_setup = false
loaders[name].only_cond = false
vim.list_extend(ftdetect_paths, detect_ftdetect(name, loaders[name].path))
if type(plugin.ft) == 'string' then
plugin.ft = { plugin.ft }
end
for _, ft in ipairs(plugin.ft) do
fts[ft] = fts[ft] or {}
table.insert(fts[ft], quote_name)
end
end
if plugin.event then
plugin.simple_load = false
loaders[name].only_sequence = false
loaders[name].only_setup = false
loaders[name].only_cond = false
if type(plugin.event) == 'string' then
if not plugin.event:find '%s' then
plugin.event = { plugin.event .. ' *' }
else
plugin.event = { plugin.event }
end
end
for _, event in ipairs(plugin.event) do
if event:sub(#event, -1) ~= '*' and not event:find '%s' then
event = event .. ' *'
end
events[event] = events[event] or {}
table.insert(events[event], quote_name)
end
end
if plugin.cmd then
plugin.simple_load = false
loaders[name].only_sequence = false
loaders[name].only_setup = false
loaders[name].only_cond = false
if type(plugin.cmd) == 'string' then
plugin.cmd = { plugin.cmd }
end
loaders[name].commands = {}
for _, command in ipairs(plugin.cmd) do
commands[command] = commands[command] or {}
table.insert(loaders[name].commands, command)
table.insert(commands[command], quote_name)
end
end
if plugin.keys then
plugin.simple_load = false
loaders[name].only_sequence = false
loaders[name].only_setup = false
loaders[name].only_cond = false
if type(plugin.keys) == 'string' then
plugin.keys = { plugin.keys }
end
loaders[name].keys = {}
for _, keymap in ipairs(plugin.keys) do
if type(keymap) == 'string' then
keymap = { '', keymap }
end
keymaps[keymap] = keymaps[keymap] or {}
table.insert(loaders[name].keys, keymap)
table.insert(keymaps[keymap], quote_name)
end
end
if plugin.after then
plugin.simple_load = false
loaders[name].only_setup = false
if type(plugin.after) == 'string' then
plugin.after = { plugin.after }
end
for _, other_plugin in ipairs(plugin.after) do
after[other_plugin] = after[other_plugin] or {}
table.insert(after[other_plugin], name)
end
end
if plugin.wants then
plugin.simple_load = false
if type(plugin.wants) == 'string' then
plugin.wants = { plugin.wants }
end
loaders[name].wants = plugin.wants
end
if plugin.fn then
plugin.simple_load = false
loaders[name].only_sequence = false
loaders[name].only_setup = false
if type(plugin.fn) == 'string' then
plugin.fn = { plugin.fn }
end
for _, fn in ipairs(plugin.fn) do
fns[fn] = fns[fn] or {}
table.insert(fns[fn], quote_name)
end
end
if plugin.module or plugin.module_pattern then
plugin.simple_load = false
loaders[name].only_sequence = false
loaders[name].only_setup = false
loaders[name].only_cond = false
if plugin.module then
if type(plugin.module) == 'string' then
plugin.module = { plugin.module }
end
for _, module_name in ipairs(plugin.module) do
module_lazy_loads['^' .. vim.pesc(module_name)] = name
end
else
if type(plugin.module_pattern) == 'string' then
plugin.module_pattern = { plugin.module_pattern }
end
for _, module_pattern in ipairs(plugin.module_pattern) do
module_lazy_loads[module_pattern] = name
end
end
end
if plugin.config and (not plugin.opt or loaders[name].only_setup) then
plugin.simple_load = false
plugin.only_config = true
configs[name] = plugin.executable_config
end
end
end
local ft_aucmds = {}
for ft, names in pairs(fts) do
table.insert(
ft_aucmds,
fmt(
'vim.cmd [[au FileType %s ++once lua require("packer.load")({%s}, { ft = "%s" }, _G.packer_plugins)]]',
ft,
table.concat(names, ', '),
ft
)
)
end
local event_aucmds = {}
for event, names in pairs(events) do
table.insert(
event_aucmds,
fmt(
'vim.cmd [[au %s ++once lua require("packer.load")({%s}, { event = "%s" }, _G.packer_plugins)]]',
event,
table.concat(names, ', '),
event:gsub([[\]], [[\\]])
)
)
end
local config_lines = {}
for name, plugin_config in pairs(configs) do
local lines = { '-- Config for: ' .. name }
timed_chunk(plugin_config, 'Config for ' .. name, lines)
vim.list_extend(config_lines, lines)
end
local rtp_line = ''
for _, rtp in ipairs(rtps) do
rtp_line = rtp_line .. ' .. ",' .. vim.fn.escape(rtp, '\\,') .. '"'
end
if rtp_line ~= '' then
rtp_line = 'vim.o.runtimepath = vim.o.runtimepath' .. rtp_line
end
local setup_lines = {}
for name, plugin_setup in pairs(setup) do
local lines = { '-- Setup for: ' .. name }
timed_chunk(plugin_setup, 'Setup for ' .. name, lines)
if loaders[name].only_setup then
timed_chunk('vim.cmd [[packadd ' .. name .. ']]', 'packadd for ' .. name, lines)
end
vim.list_extend(setup_lines, lines)
end
local conditionals = {}
for _, names in pairs(condition_ids) do
for _, name in ipairs(names) do
if loaders[name].only_cond then
timed_chunk(
fmt(' require("packer.load")({"%s"}, {}, _G.packer_plugins)', name),
'Conditional loading of ' .. name,
conditionals
)
end
end
end
local command_defs = {}
for command, names in pairs(commands) do
local command_line
if string.match(command, '^%w+$') then
-- Better command completions here are due to @folke and @lewis6991
command_line = fmt(
[[pcall(vim.api.nvim_create_user_command, '%s', function(cmdargs)
require('packer.load')({%s}, { cmd = '%s', l1 = cmdargs.line1, l2 = cmdargs.line2, bang = cmdargs.bang, args = cmdargs.args, mods = cmdargs.mods }, _G.packer_plugins)
end,
{nargs = '*', range = true, bang = true, complete = function()
require('packer.load')({%s}, {}, _G.packer_plugins)
return vim.fn.getcompletion('%s ', 'cmdline')
end})]],
command,
table.concat(names, ', '),
command,
table.concat(names, ', '),
command,
command
)
else
command_line = fmt(
'pcall(vim.cmd, [[au CmdUndefined %s ++once lua require"packer.load"({%s}, {}, _G.packer_plugins)]])',
command,
table.concat(names, ', ')
)
end
command_defs[#command_defs + 1] = command_line
end
local keymap_defs = {}
for keymap, names in pairs(keymaps) do
local prefix = nil
if keymap[1] ~= 'i' then
prefix = ''
end
local escaped_map_lt = string.gsub(keymap[2], '<', '<lt>')
local escaped_map = string.gsub(escaped_map_lt, '([\\"])', '\\%1')
local keymap_line = fmt(
'vim.cmd [[%snoremap <silent> %s <cmd>lua require("packer.load")({%s}, { keys = "%s"%s }, _G.packer_plugins)<cr>]]',
keymap[1],
keymap[2],
table.concat(names, ', '),
escaped_map,
prefix == nil and '' or (', prefix = "' .. prefix .. '"')
)
table.insert(keymap_defs, keymap_line)
end
local sequence_loads = {}
for pre, posts in pairs(after) do
if plugins[pre] == nil then
error(string.format('Dependency %s for %s not found', pre, vim.inspect(posts)))
end
if plugins[pre].opt then
loaders[pre].after = posts
elseif plugins[pre].only_config then
loaders[pre].after = posts
loaders[pre].only_sequence = true
loaders[pre].only_config = true
end
if plugins[pre].simple_load or plugins[pre].opt or plugins[pre].only_config then
for _, name in ipairs(posts) do
loaders[name].load_after = {}
sequence_loads[name] = sequence_loads[name] or {}
table.insert(sequence_loads[name], pre)
end
end
end
local fn_aucmds = {}
for fn, names in pairs(fns) do
table.insert(
fn_aucmds,
fmt(
'vim.cmd[[au FuncUndefined %s ++once lua require("packer.load")({%s}, {}, _G.packer_plugins)]]',
fn,
table.concat(names, ', ')
)
)
end
local sequence_lines = {}
local graph = {}
for name, precedents in pairs(sequence_loads) do
graph[name] = graph[name] or { in_links = {}, out_links = {} }
for _, pre in ipairs(precedents) do
graph[pre] = graph[pre] or { in_links = {}, out_links = {} }
graph[name].in_links[pre] = true
table.insert(graph[pre].out_links, name)
end
end
local frontier = {}
for name, links in pairs(graph) do
if next(links.in_links) == nil then
table.insert(frontier, name)
end
end
while next(frontier) ~= nil do
local plugin = table.remove(frontier)
if loaders[plugin].only_sequence and not (loaders[plugin].only_setup or loaders[plugin].only_config) then
table.insert(sequence_lines, 'vim.cmd [[ packadd ' .. plugin .. ' ]]')
if plugins[plugin].config then
local lines = { '', '-- Config for: ' .. plugin }
vim.list_extend(lines, plugins[plugin].executable_config)
table.insert(lines, '')
vim.list_extend(sequence_lines, lines)
end
end
for _, name in ipairs(graph[plugin].out_links) do
if not loaders[plugin].only_sequence then
loaders[name].only_sequence = false
loaders[name].load_after[plugin] = true
end
graph[name].in_links[plugin] = nil
if next(graph[name].in_links) == nil then
table.insert(frontier, name)
end
end
graph[plugin] = nil
end
if next(graph) then
log.warn 'Cycle detected in sequenced loads! Load order may be incorrect'
-- TODO: This should actually just output the cycle, then continue with toposort. But I'm too
-- lazy to do that right now, so.
for plugin, _ in pairs(graph) do
table.insert(sequence_lines, 'vim.cmd [[ packadd ' .. plugin .. ' ]]')
if plugins[plugin].config then
local lines = { '-- Config for: ' .. plugin }
vim.list_extend(lines, plugins[plugin].config)
vim.list_extend(sequence_lines, lines)
end
end
end
-- Output everything:
-- First, the Lua code
local result = { (output_lua and '--' or '"') .. ' Automatically generated packer.nvim plugin loader code\n' }
if output_lua then
table.insert(result, feature_guard_lua)
else
table.insert(result, feature_guard)
table.insert(result, 'lua << END')
end
table.insert(result, enter_packer_compile)
table.insert(result, profile_time(should_profile))
table.insert(result, profile_output)
timed_chunk(luarocks.generate_path_setup(), 'Luarocks path setup', result)
timed_chunk(try_loadstring, 'try_loadstring definition', result)
timed_chunk(fmt('_G.packer_plugins = %s\n', dump_loaders(loaders)), 'Defining packer_plugins', result)
-- Then the runtimepath line
if rtp_line ~= '' then
table.insert(result, '-- Runtimepath customization')
timed_chunk(rtp_line, 'Runtimepath customization', result)
end
-- Then the module lazy loads
if next(module_lazy_loads) then
table.insert(result, 'local module_lazy_loads = ' .. vim.inspect(module_lazy_loads))
table.insert(result, module_loader)
end
-- Then setups, configs, and conditionals
if next(setup_lines) then
vim.list_extend(result, setup_lines)
end
if next(config_lines) then
vim.list_extend(result, config_lines)
end
if next(conditionals) then
table.insert(result, '-- Conditional loads')
vim.list_extend(result, conditionals)
end
-- The sequenced loads
if next(sequence_lines) then
table.insert(result, '-- Load plugins in order defined by `after`')
timed_chunk(sequence_lines, 'Sequenced loading', result)
end
-- The command and keymap definitions
if next(command_defs) then
table.insert(result, '\n-- Command lazy-loads')
timed_chunk(command_defs, 'Defining lazy-load commands', result)
table.insert(result, '')
end
if next(keymap_defs) then
table.insert(result, '-- Keymap lazy-loads')
timed_chunk(keymap_defs, 'Defining lazy-load keymaps', result)
table.insert(result, '')
end
-- The filetype, event and function autocommands
local some_ft = next(ft_aucmds) ~= nil
local some_event = next(event_aucmds) ~= nil
local some_fn = next(fn_aucmds) ~= nil
if some_ft or some_event or some_fn then
table.insert(result, 'vim.cmd [[augroup packer_load_aucmds]]\nvim.cmd [[au!]]')
end
if some_ft then
table.insert(result, ' -- Filetype lazy-loads')
timed_chunk(ft_aucmds, 'Defining lazy-load filetype autocommands', result)
end
if some_event then
table.insert(result, ' -- Event lazy-loads')
timed_chunk(event_aucmds, 'Defining lazy-load event autocommands', result)
end
if some_fn then
table.insert(result, ' -- Function lazy-loads')
timed_chunk(fn_aucmds, 'Defining lazy-load function autocommands', result)
end
if some_ft or some_event or some_fn then
table.insert(result, 'vim.cmd("augroup END")')
end
if next(ftdetect_paths) then
table.insert(result, 'vim.cmd [[augroup filetypedetect]]')
for _, path in ipairs(ftdetect_paths) do
local escaped_path = vim.fn.escape(path, ' ')
timed_chunk('vim.cmd [[source ' .. escaped_path .. ']]', 'Sourcing ftdetect script at: ' .. escaped_path, result)
end
table.insert(result, 'vim.cmd("augroup END")')
end
table.insert(result, exit_packer_compile)
table.insert(result, conditionally_output_profile(config.threshold))
if output_lua then
table.insert(result, catch_errors_lua)
else
table.insert(result, 'END\n')
table.insert(result, catch_errors)
end
return table.concat(result, '\n')
end
local compile = setmetatable({ cfg = cfg }, { __call = make_loaders })
compile.opt_keys = { 'after', 'cmd', 'ft', 'keys', 'event', 'cond', 'setup', 'fn', 'module', 'module_pattern' }
return compile
================================================
FILE: lua/packer/display.lua
================================================
local api = vim.api
local log = require 'packer.log'
local a = require 'packer.async'
local plugin_utils = require 'packer.plugin_utils'
local fmt = string.format
local function interactive(config)
return not config.non_interactive and #api.nvim_list_uis() > 0
end
-- Temporary wrappers to compensate for the updated extmark API, until most people have updated to
-- the latest HEAD (2020-09-04)
local function set_extmark(buf, ns, id, line, col)
if not api.nvim_buf_is_valid(buf) then
return
end
local opts = { id = id }
local result, mark_id = pcall(api.nvim_buf_set_extmark, buf, ns, line, col, opts)
if result then
return mark_id
end
-- We must be in an older version of Neovim
if not id then
id = 0
end
return api.nvim_buf_set_extmark(buf, ns, id, line, col, {})
end
local function get_extmark_by_id(buf, ns, id)
local result, line, col = pcall(api.nvim_buf_get_extmark_by_id, buf, ns, id, {})
if result then
return line, col
else
log.error('Failed to get extmark: ' .. line)
end
-- We must be in an older version of Neovim
return api.nvim_buf_get_extmark_by_id(buf, ns, id)
end
local function strip_newlines(raw_lines)
local lines = {}
for _, line in ipairs(raw_lines) do
for _, chunk in ipairs(vim.split(line, '\n')) do
table.insert(lines, chunk)
end
end
return lines
end
local function unpack_config_value(value, value_type, formatter)
if value_type == 'string' then
return { value }
elseif value_type == 'table' then
local result = {}
for _, k in ipairs(value) do
local item = formatter and formatter(k) or k
table.insert(result, fmt(' - %s', item))
end
return result
end
return ''
end
local function format_keys(value)
local value_type = type(value)
local mapping = value_type == 'string' and value or value[2]
local mode = value[1] ~= '' and 'mode: ' .. value[1] or ''
local line = fmt('"%s", %s', mapping, mode)
return line
end
local function format_cmd(value)
return fmt('"%s"', value)
end
---format a configuration value of unknown type into a string or list of strings
---@param key string
---@param value any
---@return string|string[]
local function format_values(key, value)
local value_type = type(value)
if key == 'path' then
local is_opt = value:match 'opt' ~= nil
return { fmt('"%s"', vim.fn.fnamemodify(value, ':~')), fmt('opt: %s', vim.inspect(is_opt)) }
elseif key == 'url' then
return fmt('"%s"', value)
elseif key == 'keys' then
return unpack_config_value(value, value_type, format_keys)
elseif key == 'commands' then
return unpack_config_value(value, value_type, format_cmd)
else
return vim.inspect(value)
end
end
local status_keys = {
'path',
'url',
'commands',
'keys',
'module',
'as',
'ft',
'event',
'rocks',
'branch',
'commit',
'tag',
'lock',
}
local config = nil
local keymaps = {
quit = { rhs = '<cmd>lua require"packer.display".quit()<cr>', action = 'quit' },
diff = { rhs = '<cmd>lua require"packer.display".diff()<cr>', action = 'show the diff' },
toggle_update = { rhs = '<cmd>lua require"packer.display".toggle_update()<cr>', action = 'toggle update' },
continue = { rhs = '<cmd>lua require"packer.display".continue()<cr>', action = 'continue with updates' },
toggle_info = {
rhs = '<cmd>lua require"packer.display".toggle_info()<cr>',
action = 'show more info',
},
prompt_revert = {
rhs = '<cmd>lua require"packer.display".prompt_revert()<cr>',
action = 'revert an update',
},
retry = {
rhs = '<cmd>lua require"packer.display".retry()<cr>',
action = 'retry failed operations',
},
}
--- The order of the keys in a dict-like table isn't guaranteed, meaning the display window can
--- potentially show the keybindings in a different order every time
local default_keymap_display_order = {
'quit',
'toggle_info',
'diff',
'prompt_revert',
'retry',
}
--- Utility function to prompt a user with a question in a floating window
local function prompt_user(headline, body, callback)
if not interactive(config) then
callback(true)
return
end
local buf = api.nvim_create_buf(false, true)
local longest_line = 0
for _, line in ipairs(body) do
local line_length = string.len(line)
if line_length > longest_line then
longest_line = line_length
end
end
local width = math.min(longest_line + 2, math.floor(0.9 * vim.o.columns))
local height = #body + 3
local x = (vim.o.columns - width) / 2.0
local y = (vim.o.lines - height) / 2.0
local pad_width = math.max(math.floor((width - string.len(headline)) / 2.0), 0)
api.nvim_buf_set_lines(
buf,
0,
-1,
true,
vim.list_extend({
string.rep(' ', pad_width) .. headline .. string.rep(' ', pad_width),
'',
}, body)
)
api.nvim_buf_set_option(buf, 'modifiable', false)
local opts = {
relative = 'editor',
width = width,
height = height,
col = x,
row = y,
focusable = false,
style = 'minimal',
border = config.prompt_border,
noautocmd = true,
}
local win = api.nvim_open_win(buf, false, opts)
local check = vim.loop.new_prepare()
local prompted = false
vim.loop.prepare_start(
check,
vim.schedule_wrap(function()
if not api.nvim_win_is_valid(win) then
return
end
vim.loop.prepare_stop(check)
if not prompted then
prompted = true
local ans = string.lower(vim.fn.input 'OK to remove? [y/N] ') == 'y'
api.nvim_win_close(win, true)
callback(ans)
end
end)
)
end
local make_update_msg = function(symbol, status, plugin_name, plugin)
return fmt(
' %s %s %s: %s..%s',
symbol,
status,
plugin_name,
plugin.revs[1],
plugin.revs[2]
)
end
local display = {}
local display_mt = {
--- Check if we have a valid display window
valid_display = function(self)
return self and self.interactive and api.nvim_buf_is_valid(self.buf) and api.nvim_win_is_valid(self.win)
end,
--- Update the text of the display buffer
set_lines = function(self, start_idx, end_idx, lines)
if not self:valid_display() then
return
end
api.nvim_buf_set_option(self.buf, 'modifiable', true)
api.nvim_buf_set_lines(self.buf, start_idx, end_idx, true, lines)
api.nvim_buf_set_option(self.buf, 'modifiable', false)
end,
get_lines = function(self, start_idx, end_idx)
if not self:valid_display() then
return
end
return api.nvim_buf_get_lines(self.buf, start_idx, end_idx, true)
end,
get_current_line = function(self)
if not self:valid_display() then
return
end
return api.nvim_get_current_line()
end,
--- Start displaying a new task
task_start = vim.schedule_wrap(function(self, plugin, message)
if not self:valid_display() then
return
end
if self.marks[plugin] then
self:task_update(plugin, message)
return
end
display.status.running = true
self:set_lines(config.header_lines, config.header_lines, {
fmt(' %s %s: %s', config.working_sym, plugin, message),
})
self.marks[plugin] = set_extmark(self.buf, self.ns, nil, config.header_lines, 0)
end),
--- Decrement the count of active operations in the headline
decrement_headline_count = vim.schedule_wrap(function(self)
if not self:valid_display() then
return
end
local headline = api.nvim_buf_get_lines(self.buf, 0, 1, false)[1]
local count_start, count_end = string.find(headline, '%d+')
local count = tonumber(string.sub(headline, count_start, count_end))
local updated_headline = string.sub(headline, 1, count_start - 1)
.. tostring(count - 1)
.. string.sub(headline, count_end + 1)
api.nvim_buf_set_option(self.buf, 'modifiable', true)
api.nvim_buf_set_lines(self.buf, 0, 1, false, { updated_headline })
api.nvim_buf_set_option(self.buf, 'modifiable', false)
end),
--- Update a task as having successfully completed
task_succeeded = vim.schedule_wrap(function(self, plugin, message)
if not self:valid_display() then
return
end
local line, _ = get_extmark_by_id(self.buf, self.ns, self.marks[plugin])
self:set_lines(line[1], line[1] + 1, { fmt(' %s %s: %s', config.done_sym, plugin, message) })
api.nvim_buf_del_extmark(self.buf, self.ns, self.marks[plugin])
self.marks[plugin] = nil
self:decrement_headline_count()
end),
--- Update a task as having unsuccessfully failed
task_failed = vim.schedule_wrap(function(self, plugin, message)
if not self:valid_display() then
return
end
local line, _ = get_extmark_by_id(self.buf, self.ns, self.marks[plugin])
self:set_lines(line[1], line[1] + 1, { fmt(' %s %s: %s', config.error_sym, plugin, message) })
api.nvim_buf_del_extmark(self.buf, self.ns, self.marks[plugin])
self.marks[plugin] = nil
self:decrement_headline_count()
end),
--- Update the status message of a task in progress
task_update = vim.schedule_wrap(function(self, plugin, message)
if not self:valid_display() then
return
end
if not self.marks[plugin] then
return
end
local line, _ = get_extmark_by_id(self.buf, self.ns, self.marks[plugin])
self:set_lines(line[1], line[1] + 1, { fmt(' %s %s: %s', config.working_sym, plugin, message) })
set_extmark(self.buf, self.ns, self.marks[plugin], line[1], 0)
end),
open_preview = function(_, commit, lines)
if not lines or #lines < 1 then
return log.warn 'No diff available'
end
vim.cmd('pedit ' .. commit)
vim.cmd [[wincmd P]]
vim.wo.previewwindow = true
vim.bo.buftype = 'nofile'
vim.bo.buflisted = false
vim.api.nvim_buf_set_lines(0, 0, -1, false, lines)
vim.api.nvim_buf_set_keymap(0, 'n', 'q', '<cmd>close!<CR>', { silent = true, noremap = true, nowait = true })
vim.bo.filetype = 'git'
end,
--- Update the text of the headline message
update_headline_message = vim.schedule_wrap(function(self, message)
if not self:valid_display() then
return
end
local headline = config.title .. ' - ' .. message
local width = api.nvim_win_get_width(self.win) - 2
local pad_width = math.max(math.floor((width - string.len(headline)) / 2.0), 0)
self:set_lines(0, config.header_lines - 1, { string.rep(' ', pad_width) .. headline })
end),
--- Setup new syntax group links for the status window
setup_status_syntax = function(self)
local highlights = {
'hi def link packerStatus Type',
'hi def link packerStatusCommit Constant',
'hi def link packerStatusSuccess Constant',
'hi def link packerStatusFail ErrorMsg',
'hi def link packerPackageName Label',
'hi def link packerPackageNotLoaded Comment',
'hi def link packerString String',
'hi def link packerBool Boolean',
'hi def link packerBreakingChange WarningMsg',
}
for _, c in ipairs(highlights) do
vim.cmd(c)
end
end,
setup_profile_syntax = function(_)
local highlights = {
'hi def link packerTimeHigh ErrorMsg',
'hi def link packerTimeMedium WarningMsg',
'hi def link packerTimeLow String',
'hi def link packerTimeTrivial Comment',
}
for _, c in ipairs(highlights) do
vim.cmd(c)
end
end,
status = vim.schedule_wrap(function(self, plugins)
if not self:valid_display() then
return
end
self:setup_status_syntax()
self:update_headline_message(fmt('Total plugins: %d', vim.tbl_count(plugins)))
local plugs = {}
local lines = {}
local padding = string.rep(' ', 3)
local rtps = api.nvim_list_runtime_paths()
for plug_name, plug_conf in pairs(plugins) do
local load_state = plug_conf.loaded and ''
or vim.tbl_contains(rtps, plug_conf.path) and ' (manually loaded)'
or ' (not loaded)'
local header_lines = { fmt(' %s %s', config.item_sym, plug_name) .. load_state }
local config_lines = {}
for key, value in pairs(plug_conf) do
if vim.tbl_contains(status_keys, key) then
local details = format_values(key, value)
if type(details) == 'string' then
-- insert a position one so that one line details appear above multiline ones
table.insert(config_lines, 1, fmt('%s%s: %s', padding, key, details))
else
details = vim.tbl_map(function(line)
return padding .. line
end, details)
vim.list_extend(config_lines, { fmt('%s%s: ', padding, key), unpack(details) })
end
plugs[plug_name] = { lines = config_lines, displayed = false }
end
end
vim.list_extend(lines, header_lines)
end
table.sort(lines)
self.items = plugs
self:set_lines(config.header_lines, -1, lines)
end),
is_previewing = function(self)
local opts = self.opts or {}
return opts.preview_updates
end,
has_changes = function(self, plugin)
if plugin.type ~= plugin_utils.git_plugin_type or plugin.revs[1] == plugin.revs[2] then
return false
end
if self:is_previewing() and plugin.commit ~= nil then
return false
end
return true
end,
--- Display the final results of an operation
final_results = vim.schedule_wrap(function(self, results, time, opts)
self.opts = opts
if not self:valid_display() then
return
end
local keymap_display_order = {}
vim.list_extend(keymap_display_order, default_keymap_display_order)
self.results = results
self:setup_status_syntax()
display.status.running = false
time = tonumber(time)
self:update_headline_message(fmt('finished in %.3fs', time))
local raw_lines = {}
local item_order = {}
local rocks_items = {}
if results.removals then
for _, plugin_dir in ipairs(results.removals) do
table.insert(item_order, plugin_dir)
table.insert(raw_lines, fmt(' %s Removed %s', config.removed_sym, plugin_dir))
end
end
if results.moves then
for plugin, result in pairs(results.moves) do
table.insert(item_order, plugin)
table.insert(
raw_lines,
fmt(
' %s %s %s: %s %s %s',
result.result.ok and config.done_sym or config.error_sym,
result.result.ok and 'Moved' or 'Failed to move',
plugin,
result.from,
config.moved_sym,
result.to
)
)
end
end
display.status.any_failed_install = false
display.status.failed_update_list = {}
if results.installs then
for plugin, result in pairs(results.installs) do
table.insert(item_order, plugin)
table.insert(
raw_lines,
fmt(
' %s %s %s',
result.ok and config.done_sym or config.error_sym,
result.ok and 'Installed' or 'Failed to install',
plugin
)
)
display.status.any_failed_install = display.status.any_failed_install or not result.ok
end
end
if results.updates then
local status_msg = 'Updated'
if self:is_previewing() then
status_msg = 'Can update'
table.insert(keymap_display_order, 1, 'continue')
table.insert(keymap_display_order, 2, 'toggle_update')
end
for plugin_name, result in pairs(results.updates) do
local plugin = results.plugins[plugin_name]
local message = {}
local actual_update = true
local failed_update = false
if result.ok then
if self:has_changes(plugin) then
table.insert(item_order, plugin_name)
table.insert(
message,
make_update_msg(config.done_sym, status_msg, plugin_name, plugin)
)
else
actual_update = false
table.insert(message, fmt(' %s %s is already up to date', config.done_sym, plugin_name))
end
else
failed_update = true
actual_update = false
table.insert(display.status.failed_update_list, plugin.short_name)
table.insert(item_order, plugin_name)
table.insert(message, fmt(' %s Failed to update %s', config.error_sym, plugin_name))
end
plugin.actual_update = actual_update
if actual_update or failed_update then
vim.list_extend(raw_lines, message)
end
end
end
if results.luarocks then
if results.luarocks.installs then
for package, result in pairs(results.luarocks.installs) do
if result.err then
rocks_items[package] = { lines = strip_newlines(result.err.output.data.stderr) }
end
table.insert(
raw_lines,
fmt(
' %s %s %s',
result.ok and config.done_sym or config.error_sym,
result.ok and 'Installed' or 'Failed to install',
package
)
)
display.status.any_failed_install = display.status.any_failed_install or not result.ok
end
end
if results.luarocks.removals then
for package, result in pairs(results.luarocks.removals) do
if result.err then
rocks_items[package] = { lines = strip_newlines(result.err.output.data.stderr) }
end
table.insert(
raw_lines,
fmt(
' %s %s %s',
result.ok and config.done_sym or config.error_sym,
result.ok and 'Removed' or 'Failed to remove',
package
)
)
end
end
end
if #raw_lines == 0 then
table.insert(raw_lines, ' Everything already up to date!')
end
table.insert(raw_lines, '')
local show_retry = display.status.any_failed_install or #display.status.failed_update_list > 0
for _, keymap in ipairs(keymap_display_order) do
if keymaps[keymap].lhs then
if not (keymap == 'retry') or show_retry then
table.insert(raw_lines, fmt(" Press '%s' to %s", keymaps[keymap].lhs, keymaps[keymap].action))
end
end
end
-- Ensure there are no newlines
local lines = strip_newlines(raw_lines)
self:set_lines(config.header_lines, -1, lines)
local plugins = {}
for plugin_name, plugin in pairs(results.plugins) do
local plugin_data = { displayed = false, lines = {}, spec = plugin }
if plugin.output then
if plugin.output.err and #plugin.output.err > 0 then
table.insert(plugin_data.lines, ' Errors:')
for _, line in ipairs(plugin.output.err) do
line = vim.trim(line)
if line:find '\n' then
for sub_line in line:gmatch '[^\r\n]+' do
table.insert(plugin_data.lines, ' ' .. sub_line)
end
else
table.insert(plugin_data.lines, ' ' .. line)
end
end
end
end
if plugin.messages and #plugin.messages > 0 then
table.insert(plugin_data.lines, fmt(' URL: %s', plugin.url))
table.insert(plugin_data.lines, ' Commits:')
for _, msg in ipairs(plugin.messages) do
for _, line in ipairs(vim.split(msg, '\n')) do
table.insert(plugin_data.lines, string.rep(' ', 4) .. line)
end
end
table.insert(plugin_data.lines, '')
end
if plugin.breaking_commits and #plugin.breaking_commits > 0 then
vim.cmd('syntax match packerBreakingChange "' .. plugin_name .. '" containedin=packerStatusSuccess')
for _, commit_hash in ipairs(plugin.breaking_commits) do
log.warn('Potential breaking change in commit ' .. commit_hash .. ' of ' .. plugin_name)
vim.cmd('syntax match packerBreakingChange "' .. commit_hash .. '" containedin=packerHash')
end
end
plugins[plugin_name] = plugin_data
end
self.items = vim.tbl_extend('keep', plugins, rocks_items)
self.item_order = item_order
if config.show_all_info then
self:show_all_info()
end
end),
--- Toggle the display of detailed information for all plugins in the final results display
show_all_info = function(self)
if not self:valid_display() then
return
end
if next(self.items) == nil then
log.info 'Operations are still running; plugin info is not ready yet'
return
end
local line = config.header_lines + 1
for _, plugin_name in pairs(self.item_order) do
local plugin_data = self.items[plugin_name]
if plugin_data and plugin_data.spec.actual_update and #plugin_data.lines > 0 then
local next_line
if config.compact then
next_line = line + 1
plugin_data.displayed = false
else
self:set_lines(line, line, plugin_data.lines)
next_line = line + #plugin_data.lines + 1
plugin_data.displayed = true
end
self.marks[plugin_name] = {
start = set_extmark(self.buf, self.ns, nil, line - 1, 0),
end_ = set_extmark(self.buf, self.ns, nil, next_line - 1, 0),
}
line = next_line
else
line = line + 1
end
end
end,
profile_output = function(self, output)
self:setup_profile_syntax()
local result = {}
for i, line in ipairs(output) do
result[i] = string.rep(' ', 2) .. line
end
self:set_lines(config.header_lines, -1, result)
end,
--- Toggle the display of detailed information for a plugin in the final results display
toggle_info = function(self)
if not self:valid_display() then
return
end
if self.items == nil or next(self.items) == nil then
log.info 'Operations are still running; plugin info is not ready yet'
return
end
local plugin_name, cursor_pos = self:find_nearest_plugin()
if plugin_name == nil then
log.warn 'No plugin selected!'
return
end
local plugin_data = self.items[plugin_name]
if plugin_data.displayed then
self:set_lines(cursor_pos[1], cursor_pos[1] + #plugin_data.lines, {})
plugin_data.displayed = false
elseif #plugin_data.lines > 0 then
self:set_lines(cursor_pos[1], cursor_pos[1], plugin_data.lines)
plugin_data.displayed = true
else
log.info('No further information for ' .. plugin_name)
end
api.nvim_win_set_cursor(0, cursor_pos)
end,
diff = function(self)
if not self:valid_display() then
return
end
if next(self.items) == nil then
log.info 'Operations are still running; plugin info is not ready yet'
return
end
local plugin_name, _ = self:find_nearest_plugin()
if plugin_name == nil then
log.warn 'No plugin selected!'
return
end
if not self.items[plugin_name] or not self.items[plugin_name].spec then
log.warn 'Plugin not available!'
return
end
local plugin_data = self.items[plugin_name].spec
local current_line = self:get_current_line()
local commit_pattern = [[[0-9a-f]\{7,9}]]
local commit_single_pattern = string.format([[\<%s\>]], commit_pattern)
local commit_range_pattern = string.format([[\<%s\.\.%s\>]], commit_pattern, commit_pattern)
local commit = vim.fn.matchstr(current_line, commit_range_pattern)
if commit == '' then
commit = vim.fn.matchstr(current_line, commit_single_pattern)
end
if commit == '' then
log.warn 'Unable to find the diff for this line'
return
end
plugin_data.diff(commit, function(lines, err)
if err then
return log.warn 'Unable to get diff!'
end
vim.schedule(function()
self:open_preview(commit, lines)
end)
end)
end,
toggle_update = function(self)
if not self:is_previewing() then
return
end
local plugin_name, _ = self:find_nearest_plugin()
local plugin = self.items[plugin_name]
if not plugin then
log.warn 'Plugin not available!'
return
end
local plugin_data = plugin.spec
if not plugin_data.actual_update then
return
end
plugin_data.ignore_update = not plugin_data.ignore_update
self:toggle_plugin_text(plugin_name, plugin_data)
end,
toggle_plugin_text = function(self, plugin_name, plugin_data)
local mark_ids = self.marks[plugin_name]
local start_idx = get_extmark_by_id(self.buf, self.ns, mark_ids.start)[1]
local symbol
local status_msg
if plugin_data.ignore_update then
status_msg = [[Won't update]]
symbol = config.item_sym
else
status_msg = 'Can update'
symbol = config.done_sym
end
self:set_lines(
start_idx,
start_idx + 1,
{make_update_msg(symbol, status_msg, plugin_name, plugin_data)}
)
-- NOTE we need to reset the mark
self.marks[plugin_name].start = set_extmark(self.buf, self.ns, nil, start_idx, 0)
end,
continue = function(self)
if not self:is_previewing() then
return
end
local plugins = {}
for plugin_name, _ in pairs(self.results.updates) do
local plugin_data = self.items[plugin_name].spec
if plugin_data.actual_update and not plugin_data.ignore_update then
table.insert(plugins, plugin_data.short_name)
end
end
if #plugins > 0 then
require('packer').update({pull_head = true, preview_updates = false}, unpack(plugins))
else
log.warn 'No plugins selected!'
end
end,
--- Prompt a user to revert the latest update for a plugin
prompt_revert = function(self)
if not self:valid_display() then
return
end
if next(self.items) == nil then
log.info 'Operations are still running; plugin info is not ready yet'
return
end
local plugin_name, _ = self:find_nearest_plugin()
if plugin_name == nil then
log.warn 'No plugin selected!'
return
end
local plugin_data = self.items[plugin_name].spec
if plugin_data.actual_update then
prompt_user('Revert update for ' .. plugin_name .. '?', {
'Do you want to revert '
.. plugin_name
.. ' from '
.. plugin_data.revs[2]
.. ' to '
.. plugin_data.revs[1]
.. '?',
}, function(ans)
if ans then
local r = plugin_data.revert_last()
if r.ok then
log.info('Reverted update for ' .. plugin_name)
else
log.error('Reverting update for ' .. plugin_name .. ' failed!')
end
end
end)
else
log.warn(plugin_name .. " wasn't updated; can't revert!")
end
end,
is_plugin_line = function(self, line)
for _, sym in pairs { config.item_sym, config.done_sym, config.working_sym, config.error_sym } do
if string.find(line, sym, 1, true) then
return true
end
end
return false
end,
--- Heuristically find the plugin nearest to the cursor for displaying detailed information
find_nearest_plugin = function(self)
if not self:valid_display() then
return
end
local current_cursor_pos = api.nvim_win_get_cursor(0)
local nb_lines = api.nvim_buf_line_count(0)
local cursor_pos_y = math.max(current_cursor_pos[1], config.header_lines + 1)
if cursor_pos_y > nb_lines then
return
end
for i = cursor_pos_y, 1, -1 do
local curr_line = api.nvim_buf_get_lines(0, i - 1, i, true)[1]
if self:is_plugin_line(curr_line) then
for name, _ in pairs(self.items) do
if string.find(curr_line, name, 1, true) then
return name, { i, 0 }
end
end
end
end
end,
}
display_mt.__index = display_mt
local function look_back(str)
return string.format([[\(%s\)\@%d<=]], str, #str)
end
-- TODO: Option for no colors
local function make_filetype_cmds(working_sym, done_sym, error_sym)
return {
-- Adapted from https://github.com/kristijanhusak/vim-packager
'setlocal buftype=nofile bufhidden=wipe nobuflisted nolist noswapfile nowrap nospell nonumber norelativenumber nofoldenable signcolumn=no',
'syntax clear',
'syn match packerWorking /^ ' .. working_sym .. '/',
'syn match packerSuccess /^ ' .. done_sym .. '/',
'syn match packerFail /^ ' .. error_sym .. '/',
'syn match packerStatus /^+.*—\\zs\\s.*$/',
'syn match packerStatusSuccess /' .. look_back('^ ' .. done_sym) .. '\\s.*$/',
'syn match packerStatusFail /' .. look_back('^ ' .. error_sym) .. '\\s.*$/',
'syn match packerStatusCommit /^\\*.*—\\zs\\s.*$/',
'syn match packerHash /\\(\\s\\)[0-9a-f]\\{7,8}\\(\\s\\)/',
'syn match packerRelDate /([^)]*)$/',
'syn match packerProgress /\\[\\zs[\\=]*/',
'syn match packerOutput /\\(Output:\\)\\|\\(Commits:\\)\\|\\(Errors:\\)/',
[[syn match packerTimeHigh /\d\{3\}\.\d\+ms/]],
[[syn match packerTimeMedium /\d\{2\}\.\d\+ms/]],
[[syn match packerTimeLow /\d\.\d\+ms/]],
[[syn match packerTimeTrivial /0\.\d\+ms/]],
[[syn match packerPackageNotLoaded /(not loaded)$/]],
[[syn match packerString /\v(''|""|(['"]).{-}[^\\]\2)/]],
[[syn match packerBool /\<\(false\|true\)\>/]],
[[syn match packerPackageName /^\ • \zs[^ ]*/]],
'hi def link packerWorking SpecialKey',
'hi def link packerSuccess Question',
'hi def link packerFail ErrorMsg',
'hi def link packerHash Identifier',
'hi def link packerRelDate Comment',
'hi def link packerProgress Boolean',
'hi def link packerOutput Type',
}
end
display.cfg = function(_config)
config = _config.display
if config.keybindings then
for name, lhs in pairs(config.keybindings) do
if keymaps[name] then
keymaps[name].lhs = lhs
end
end
end
config.filetype_cmds = make_filetype_cmds(config.working_sym, config.done_sym, config.error_sym)
end
--- Utility to make the initial display buffer header
local function make_header(disp)
local width = api.nvim_win_get_width(0)
local pad_width = math.floor((width - string.len(config.title)) / 2.0)
api.nvim_buf_set_lines(disp.buf, 0, 1, true, {
string.rep(' ', pad_width) .. config.title,
' ' .. string.rep(config.header_sym, width - 2),
})
end
--- Initialize options, settings, and keymaps for display windows
local function setup_window(disp)
api.nvim_buf_set_option(disp.buf, 'filetype', 'packer')
api.nvim_buf_set_name(disp.buf, '[packer]')
for _, m in pairs(keymaps) do
if m.lhs then
api.nvim_buf_set_keymap(disp.buf, 'n', m.lhs, m.rhs, { nowait = true, silent = true })
end
end
for _, c in ipairs(config.filetype_cmds) do
vim.cmd(c)
end
end
--- Open a new display window
-- Takes either a string representing a command or a function returning a (window, buffer) pair.
display.open = function(opener)
if display.status.disp then
if api.nvim_win_is_valid(display.status.disp.win) then
api.nvim_win_close(display.status.disp.win, true)
end
display.status.disp = nil
end
local disp = setmetatable({}, display_mt)
disp.marks = {}
disp.plugins = {}
disp.interactive = interactive(config)
if disp.interactive then
if type(opener) == 'string' then
vim.cmd(opener)
disp.win = api.nvim_get_current_win()
disp.buf = api.nvim_get_current_buf()
else
local status, win, buf = opener '[packer]'
if not status then
log.error('Failure running opener function: ' .. vim.inspect(win))
error(win)
end
disp.win = win
disp.buf = buf
end
disp.ns = api.nvim_create_namespace ''
make_header(disp)
setup_window(disp)
display.status.disp = disp
end
return disp
end
display.status = { running = false, disp = nil }
--- Close a display window and signal that any running operations should terminate
display.quit = function()
display.status.running = false
vim.fn.execute('q!', 'silent')
end
display.toggle_info = function()
if display.status.disp then
display.status.disp:toggle_info()
end
end
display.diff = function()
if display.status.disp then
display.status.disp:diff()
end
end
display.toggle_update = function()
if display.status.disp then
display.status.disp:toggle_update()
end
end
display.continue = function()
if display.status.disp then
display.status.disp:continue()
end
end
display.prompt_revert = function()
if display.status.disp then
display.status.disp:prompt_revert()
end
end
display.retry = function()
if display.status.any_failed_install then
require('packer').install()
elseif #display.status.failed_update_list > 0 then
require('packer').update(unpack(display.status.failed_update_list))
end
end
--- Async prompt_user
display.ask_user = a.wrap(prompt_user)
return display
================================================
FILE: lua/packer/handlers.lua
================================================
local config = nil
local function cfg(_config)
config = _config
end
local handlers = {
cfg = cfg,
}
return handlers
================================================
FILE: lua/packer/install.lua
================================================
local a = require 'packer.async'
local log = require 'packer.log'
local util = require 'packer.util'
local display = require 'packer.display'
local plugin_utils = require 'packer.plugin_utils'
local fmt = string.format
local async = a.sync
local await = a.wait
local config = nil
local function install_plugin(plugin, display_win, results)
local plugin_name = util.get_plugin_full_name(plugin)
return async(function()
display_win:task_start(plugin_name, 'installing...')
-- TODO: If the user provided a custom function as an installer, we would like to use pcall
-- here. Need to figure out how that integrates with async code
local r = await(plugin.installer(display_win))
r = r:and_then(await, plugin_utils.post_update_hook(plugin, display_win))
if r.ok then
display_win:task_succeeded(plugin_name, 'installed')
log.debug('Installed ' .. plugin_name)
else
display_win:task_failed(plugin_name, 'failed to install')
log.debug(fmt('Failed to install %s: %s', plugin_name, vim.inspect(r.err)))
end
results.installs[plugin_name] = r
results.plugins[plugin_name] = plugin
end)
end
local function do_install(_, plugins, missing_plugins, results)
results = results or {}
results.installs = results.installs or {}
results.plugins = results.plugins or {}
local display_win = nil
local tasks = {}
if #missing_plugins > 0 then
display_win = display.open(config.display.open_fn or config.display.open_cmd)
for _, v in ipairs(missing_plugins) do
if not plugins[v].disable then
table.insert(tasks, install_plugin(plugins[v], display_win, results))
end
end
end
return tasks, display_win
end
local function cfg(_config)
config = _config
end
local install = setmetatable({ cfg = cfg }, { __call = do_install })
return install
================================================
FILE: lua/packer/jobs.lua
================================================
-- Interface with Neovim job control and provide a simple job sequencing structure
local split = vim.split
local loop = vim.loop
local a = require 'packer.async'
local log = require 'packer.log'
local result = require 'packer.result'
--- Utility function to make a "standard" logging callback for a given set of tables
-- Arguments:
-- - err_tbl: table to which err messages will be logged
-- - data_tbl: table to which data (non-err messages) will be logged
-- - pipe: the pipe for which this callback will be used. Passed in so that we can make sure all
-- output flushes before finishing reading
-- - disp: optional packer.display object for updating task status. Requires `name`
-- - name: optional string name for a current task. Used to update task status
local function make_logging_callback(err_tbl, data_tbl, pipe, disp, name)
return function(err, data)
if err then
table.insert(err_tbl, vim.trim(err))
end
if data ~= nil then
local trimmed = vim.trim(data)
table.insert(data_tbl, trimmed)
if disp then
disp:task_update(name, split(trimmed, '\n')[1])
end
else
loop.read_stop(pipe)
loop.close(pipe)
end
end
end
--- Utility function to make a table for capturing output with "standard" structure
local function make_output_table()
return { err = { stdout = {}, stderr = {} }, data = { stdout = {}, stderr = {} } }
end
--- Utility function to merge stdout and stderr from two tables with "standard" structure (either
-- the err or data subtables, specifically)
local function extend_output(to, from)
vim.list_extend(to.stdout, from.stdout)
vim.list_extend(to.stderr, from.stderr)
return to
end
--- Wrapper for vim.loop.spawn. Takes a command, options, and callback just like vim.loop.spawn, but
-- (1) makes an async function and (2) ensures that all output from the command has been flushed
-- before calling the callback
local spawn = a.wrap(function(cmd, options, callback)
local handle = nil
local timer = nil
handle, pid = loop.spawn(cmd, options, function(exit_code, signal)
handle:close()
if timer ~= nil then
timer:stop()
timer:close()
end
loop.close(options.stdio[1])
local check = loop.new_check()
loop.check_start(check, function()
for _, pipe in pairs(options.stdio) do
if not loop.is_closing(pipe) then
return
end
end
loop.check_stop(check)
callback(exit_code, signal)
end)
end)
if options.stdio then
for i, pipe in pairs(options.stdio) do
if options.stdio_callbacks[i] then
loop.read_start(pipe, options.stdio_callbacks[i])
end
end
end
if handle == nil then
-- pid is an error string in this case
log.error(string.format("Failed spawning command: %s because %s", cmd, pid))
callback(-1, pid)
return
end
if options.timeout then
timer = loop.new_timer()
timer:start(options.timeout, 0, function()
timer:stop()
timer:close()
if loop.is_active(handle) then
log.warn('Killing ' .. cmd .. ' due to timeout!')
loop.process_kill(handle, 'sigint')
handle:close()
for _, pipe in pairs(options.stdio) do
loop.close(pipe)
end
callback(-9999, 'sigint')
end
end)
end
end)
--- Utility function to perform a common check for process success and return a result object
local function was_successful(r)
if r.exit_code == 0 and (not r.output or not r.output.err or #r.output.err == 0) then
return result.ok(r)
else
return result.err(r)
end
end
--- Main exposed function for the jobs module. Takes a task and options and returns an async
-- function that will run the task with the given opts via vim.loop.spawn
-- Arguments:
-- - task: either a string or table. If string, split, and the first component is treated as the
-- command. If table, first element is treated as the command. All subsequent elements are passed
-- as args
-- - opts: table of options. Can include the keys "options" (like the options table passed to
-- vim.loop.spawn), "success_test" (a function, called like `was_successful` (above)),
-- "capture_output" (either a boolean, in which case default output capture is set up and the
-- resulting tables are included in the result, or a set of tables, in which case output is logged
-- to the given tables)
local run_job = function(task, opts)
return a.sync(function()
local options = opts.options or { hide = true }
local stdout = nil
local stderr = nil
local job_result = { exit_code = -1, signal = -1 }
local success_test = opts.success_test or was_successful
local uv_err
local output = make_output_table()
local callbacks = {}
local output_valid = false
if opts.capture_output then
if type(opts.capture_output) == 'boolean' then
stdout, uv_err = loop.new_pipe(false)
if uv_err then
log.error('Failed to open stdout pipe: ' .. uv_err)
return result.err()
end
stderr, uv_err = loop.new_pipe(false)
if uv_err then
log.error('Failed to open stderr pipe: ' .. uv_err)
return job_result
end
callbacks.stdout = make_logging_callback(output.err.stdout, output.data.stdout, stdout)
callbacks.stderr = make_logging_callback(output.err.stderr, output.data.stderr, stderr)
output_valid = true
elseif type(opts.capture_output) == 'table' then
if opts.capture_output.stdout then
stdout, uv_err = loop.new_pipe(false)
if uv_err then
log.error('Failed to open stdout pipe: ' .. uv_err)
return job_result
end
callbacks.stdout = function(err, data)
if data ~= nil then
opts.capture_output.stdout(err, data)
else
loop.read_stop(stdout)
loop.close(stdout)
end
end
end
if opts.capture_output.stderr then
stderr, uv_err = loop.new_pipe(false)
if uv_err then
log.error('Failed to open stderr pipe: ' .. uv_err)
return job_result
end
callbacks.stderr = function(err, data)
if data ~= nil then
opts.capture_output.stderr(err, data)
else
loop.read_stop(stderr)
loop.close(stderr)
end
end
end
end
end
if type(task) == 'string' then
local split_pattern = '%s+'
task = split(task, split_pattern)
end
local cmd = task[1]
if opts.timeout then
options.timeout = 1000 * opts.timeout
end
options.cwd = opts.cwd
local stdin = loop.new_pipe(false)
options.args = { unpack(task, 2) }
options.stdio = { stdin, stdout, stderr }
options.stdio_callbacks = { nil, callbacks.stdout, callbacks.stderr }
local exit_code, signal = a.wait(spawn(cmd, options))
job_result = { exit_code = exit_code, signal = signal }
if output_valid then
job_result.output = output
end
return success_test(job_result)
end)
end
local jobs = {
run = run_job,
logging_callback = make_logging_callback,
output_table = make_output_table,
extend_output = extend_output,
}
return jobs
================================================
FILE: lua/packer/load.lua
================================================
local packer_load = nil
local cmd = vim.api.nvim_command
local fmt = string.format
local function verify_conditions(conds, name)
if conds == nil then
return true
end
for _, cond in ipairs(conds) do
local success, result
if type(cond) == 'boolean' then
result = cond
elseif type(cond) == 'string' then
success, result = pcall(loadstring(cond))
if not success then
vim.schedule(function()
vim.api.nvim_notify(
'packer.nvim: Error running cond for ' .. name .. ': ' .. result,
vim.log.levels.ERROR,
{}
)
end)
return false
end
end
if result == false then
return false
end
end
return true
end
local function loader_clear_loaders(plugin)
if plugin.commands then
for _, del_cmd in ipairs(plugin.commands) do
cmd('silent! delcommand ' .. del_cmd)
end
end
if plugin.keys then
for _, key in ipairs(plugin.keys) do
cmd(fmt('silent! %sunmap %s', key[1], key[2]))
end
end
end
local function loader_apply_config(plugin, name)
if plugin.config then
for _, config_line in ipairs(plugin.config) do
local success, err = pcall(loadstring(config_line), name, plugin)
if not success then
vim.schedule(function()
vim.api.nvim_notify('packer.nvim: Error running config for ' .. name .. ': ' .. err, vim.log.levels.ERROR, {})
end)
end
end
end
end
local function loader_apply_wants(plugin, plugins)
if plugin.wants then
for _, wanted_name in ipairs(plugin.wants) do
packer_load({ wanted_name }, {}, plugins)
end
end
end
local function loader_apply_after(plugin, plugins, name)
if plugin.after then
for _, after_name in ipairs(plugin.after) do
local after_plugin = plugins[after_name]
after_plugin.load_after[name] = nil
if next(after_plugin.load_after) == nil then
packer_load({ after_name }, {}, plugins)
end
end
end
end
local function apply_cause_side_effects(cause)
if cause.cmd then
local lines = cause.l1 == cause.l2 and '' or (cause.l1 .. ',' .. cause.l2)
-- This is a hack to deal with people who haven't recompiled after updating to the new command
-- creation logic
local bang = ''
if type(cause.bang) == 'string' then
bang = cause.bang
elseif type(cause.bang) == 'boolean' and cause.bang then
bang = '!'
end
cmd(fmt('%s %s%s%s %s', cause.mods or '', lines, cause.cmd, bang, cause.args))
elseif cause.keys then
local extra = ''
while true do
local c = vim.fn.getchar(0)
if c == 0 then
break
end
extra = extra .. vim.fn.nr2char(c)
end
if cause.prefix then
local prefix = vim.v.count ~= 0 and vim.v.count or ''
prefix = prefix .. '"' .. vim.v.register .. cause.prefix
if vim.fn.mode 'full' == 'no' then
if vim.v.operator == 'c' then
prefix = '' .. prefix
end
prefix = prefix .. vim.v.operator
end
vim.fn.feedkeys(prefix, 'n')
end
local escaped_keys = vim.api.nvim_replace_termcodes(cause.keys .. extra, true, true, true)
vim.api.nvim_feedkeys(escaped_keys, 'm', true)
elseif cause.event then
cmd(fmt('doautocmd <nomodeline> %s', cause.event))
elseif cause.ft then
cmd(fmt('doautocmd <nomodeline> %s FileType %s', 'filetypeplugin', cause.ft))
cmd(fmt('doautocmd <nomodeline> %s FileType %s', 'filetypeindent', cause.ft))
cmd(fmt('doautocmd <nomodeline> %s FileType %s', 'syntaxset', cause.ft))
end
end
packer_load = function(names, cause, plugins, force)
local some_unloaded = false
local needs_bufread = false
local num_names = #names
for i = 1, num_names do
local plugin = plugins[names[i]]
if not plugin then
local err_message = 'Error: attempted to load ' .. names[i] .. ' which is not present in plugins table!'
vim.notify(err_message, vim.log.levels.ERROR, { title = 'packer.nvim' })
error(err_message)
end
if not plugin.loaded then
loader_clear_loaders(plugin)
if force or verify_conditions(plugin.cond, names[i]) then
-- Set the plugin as loaded before config is run in case something in the config tries to load
-- this same plugin again
plugin.loaded = true
some_unloaded = true
needs_bufread = needs_bufread or plugin.needs_bufread
loader_apply_wants(plugin, plugins)
cmd('packadd ' .. names[i])
if plugin.after_files then
for _, file in ipairs(plugin.after_files) do
cmd('silent source ' .. file)
end
end
loader_apply_config(plugin, names[i])
loader_apply_after(plugin, plugins, names[i])
end
end
end
if not some_unloaded then
return
end
if needs_bufread then
if _G._packer and _G._packer.inside_compile == true then
-- delaying BufRead to end of packer_compiled
_G._packer.needs_bufread = true
else
cmd 'doautocmd BufRead'
end
end
-- Retrigger cmd/keymap...
apply_cause_side_effects(cause)
end
local function load_wrapper(names, cause, plugins, force)
local success, err_msg = pcall(packer_load, names, cause, plugins, force)
if not success then
vim.cmd 'echohl ErrorMsg'
vim.cmd('echomsg "Error in packer_compiled: ' .. vim.fn.escape(err_msg, '"') .. '"')
vim.cmd 'echomsg "Please check your config for correctness"'
vim.cmd 'echohl None'
end
end
return load_wrapper
================================================
FILE: lua/packer/log.lua
================================================
-- log.lua
--
-- Inspired by rxi/log.lua
-- Modified by tjdevries and can be found at github.com/tjdevries/vlog.nvim
--
-- This library is free software; you can redistribute it and/or modify it
-- under the terms of the MIT license. See LICENSE for details.
-- User configuration section
local default_config = {
-- Name of the plugin. Prepended to log messages
plugin = 'packer.nvim',
-- Should print the output to neovim while running
use_console = true,
-- Should highlighting be used in console (using echohl)
highlights = true,
-- Should write to a file
use_file = true,
-- Any messages above this level will be logged.
level = 'debug',
-- Level configuration
modes = {
{ name = 'trace', hl = 'Comment' },
{ name = 'debug', hl = 'Comment' },
{ name = 'info', hl = 'None' },
{ name = 'warn', hl = 'WarningMsg' },
{ name = 'error', hl = 'ErrorMsg' },
{ name = 'fatal', hl = 'ErrorMsg' },
},
-- Which levels should be logged?
active_levels = { [1] = true, [2] = true, [3] = true, [4] = true, [5] = true, [6] = true },
-- Can limit the number of decimals displayed for floats
float_precision = 0.01,
}
-- {{{ NO NEED TO CHANGE
local log = {}
local unpack = unpack or table.unpack
local level_ids = { trace = 1, debug = 2, info = 3, warn = 4, error = 5, fatal = 6 }
log.cfg = function(_config)
local min_active_level = level_ids[_config.log.level]
local config = { active_levels = {} }
if min_active_level then
for i = min_active_level, 6 do
config.active_levels[i] = true
end
end
log.new(config, true)
end
log.new = function(config, standalone)
config = vim.tbl_deep_extend('force', default_config, config)
local outfile = string.format('%s/%s.log', vim.fn.stdpath 'cache', config.plugin)
vim.fn.mkdir(vim.fn.stdpath 'cache', 'p')
local obj
if standalone then
obj = log
else
obj = {}
end
local levels = {}
for i, v in ipairs(config.modes) do
levels[v.name] = i
end
local round = function(x, increment)
increment = increment or 1
x = x / increment
return (x > 0 and math.floor(x + 0.5) or math.ceil(x - 0.5)) * increment
end
local make_string = function(...)
local t = {}
for i = 1, select('#', ...) do
local x = select(i, ...)
if type(x) == 'number' and config.float_precision then
x = tostring(round(x, config.float_precision))
elseif type(x) == 'table' then
x = vim.inspect(x)
else
x = tostring(x)
end
t[#t + 1] = x
end
return table.concat(t, ' ')
end
local console_output = vim.schedule_wrap(function(level_config, info, nameupper, msg)
local console_lineinfo = vim.fn.fnamemodify(info.short_src, ':t') .. ':' .. info.currentline
local console_string = string.format('[%-6s%s] %s: %s', nameupper, os.date '%H:%M:%S', console_lineinfo, msg)
-- Heuristic to check for nvim-notify
local is_fancy_notify = type(vim.notify) == 'table'
vim.notify(
string.format([[%s%s]], is_fancy_notify and '' or ('[' .. config.plugin .. '] '), console_string),
vim.log.levels[level_config.name:upper()],
{ title = config.plugin }
)
end)
local log_at_level = function(level, level_config, message_maker, ...)
-- Return early if we're below the config.level
if level < levels[config.level] then
return
end
local nameupper = level_config.name:upper()
local msg = message_maker(...)
local info = debug.getinfo(2, 'Sl')
local lineinfo = info.short_src .. ':' .. info.currentline
-- Output to console
if config.use_console and config.active_levels[level] then
console_output(level_config, info, nameupper, msg)
end
-- Output to log file
if config.use_file and config.active_levels[level] then
local fp, err = io.open(outfile, 'a')
if not fp then
print(err)
return
end
local str = string.format('[%-6s%s %s] %s: %s\n', nameupper, os.date(), vim.loop.hrtime(), lineinfo, msg)
fp:write(str)
fp:close()
end
end
for i, x in ipairs(config.modes) do
obj[x.name] = function(...)
return log_at_level(i, x, make_string, ...)
end
obj[('fmt_%s'):format(x.name)] = function()
return log_at_level(i, x, function(...)
local passed = { ... }
local fmt = table.remove(passed, 1)
local inspected = {}
for _, v in ipairs(passed) do
table.insert(inspected, vim.inspect(v))
end
return string.format(fmt, unpack(inspected))
end)
end
end
end
log.new(default_config, true)
-- }}}
return log
================================================
FILE: lua/packer/luarocks.lua
================================================
-- Add support for installing and cleaning Luarocks dependencies
-- Based off of plenary/neorocks/init.lua in https://github.com/nvim-lua/plenary.nvim
local a = require 'packer.async'
local jobs = require 'packer.jobs'
local log = require 'packer.log'
local result = require 'packer.result'
local util = require 'packer.util'
local fmt = string.format
local async = a.sync
local await = a.wait
local config = nil
local function cfg(_config)
config = _config.luarocks
end
local function warn_need_luajit()
log.error 'LuaJIT is required for Luarocks functionality!'
end
local lua_version = nil
if jit then
local jit_version = string.gsub(jit.version, 'LuaJIT ', '')
lua_version = { lua = string.gsub(_VERSION, 'Lua ', ''), jit = jit_version, dir = jit_version }
else
return {
handle_command = warn_need_luajit,
install_commands = warn_need_luajit,
list = warn_need_luajit,
install_hererocks = warn_need_luajit,
setup_paths = warn_need_luajit,
uninstall = warn_need_luajit,
clean = warn_need_luajit,
install = warn_need_luajit,
ensure = warn_need_luajit,
generate_path_setup = function()
return ''
end,
cfg = cfg,
}
end
local cache_path = vim.fn.stdpath 'cache'
local rocks_path = util.join_paths(cache_path, 'packer_hererocks')
local hererocks_file = util.join_paths(rocks_path, 'hererocks.py')
local hererocks_install_dir = util.join_paths(rocks_path, lua_version.dir)
local shell_hererocks_dir = vim.fn.shellescape(hererocks_install_dir)
local _hererocks_setup_done = false
local function hererocks_is_setup()
if _hererocks_setup_done then
return true
end
local path_info = vim.loop.fs_stat(util.join_paths(hererocks_install_dir, 'lib'))
_hererocks_setup_done = (path_info ~= nil) and (path_info['type'] == 'directory')
return _hererocks_setup_done
end
local function hererocks_installer(disp)
return async(function()
local hererocks_url = 'https://raw.githubusercontent.com/luarocks/hererocks/master/hererocks.py'
local hererocks_cmd
await(a.main)
vim.fn.mkdir(rocks_path, 'p')
if vim.fn.executable 'curl' > 0 then
hererocks_cmd = 'curl ' .. hererocks_url .. ' -o ' .. hererocks_file
elseif vim.fn.executable 'wget' > 0 then
hererocks_cmd = 'wget ' .. hererocks_url .. ' -O ' .. hererocks_file .. ' --verbose'
else
return result.err '"curl" or "wget" is required to install hererocks'
end
if disp ~= nil then
disp:task_start('luarocks-hererocks', 'installing hererocks...')
end
local output = jobs.output_table()
local callbacks = {
stdout = jobs.logging_callback(output.err.stdout, output.data.stdout, nil, disp, 'luarocks-hererocks'),
stderr = jobs.logging_callback(output.err.stderr, output.data.stderr),
}
local opts = { capture_output = callbacks }
local r = await(jobs.run(hererocks_cmd, opts)):map_err(function(err)
return { msg = 'Error installing hererocks', data = err, output = output }
end)
local luarocks_cmd = config.python_cmd
.. ' '
.. hererocks_file
.. ' --verbose -j '
.. lua_version.jit
.. ' -r latest '
.. hererocks_install_dir
r:and_then(await, jobs.run(luarocks_cmd, opts))
:map_ok(function()
if disp then
disp:task_succeeded('luarocks-hererocks', 'installed hererocks!')
end
end)
:map_err(function(err)
if disp then
disp:task_failed('luarocks-hererocks', 'failed to install hererocks!')
end
log.error('Failed to install hererocks: ' .. vim.inspect(err))
return { msg = 'Error installing luarocks', data = err, output = output }
end)
return r
end)
end
local function package_patterns(dir)
local sep = util.get_separator()
return fmt('%s%s?.lua;%s%s?%sinit.lua', dir, sep, dir, sep, sep)
end
local package_paths = (function()
local install_path = util.join_paths(hererocks_install_dir, 'lib', 'luarocks', fmt('rocks-%s', lua_version.lua))
local share_path = util.join_paths(hererocks_install_dir, 'share', 'lua', lua_version.lua)
return package_patterns(share_path) .. ';' .. package_patterns(install_path)
end)()
local nvim_paths_are_setup = false
local function setup_nvim_paths()
if not hererocks_is_setup() then
log.warn 'Tried to setup Neovim Lua paths before hererocks was setup!'
return
end
if nvim_paths_are_setup then
log.warn 'Tried to setup Neovim Lua paths redundantly!'
return
end
if not string.find(package.path, package_paths, 1, true) then
package.path = package.path .. ';' .. package_paths
end
local install_cpath = util.join_paths(hererocks_install_dir, 'lib', 'lua', lua_version.lua)
local install_cpath_pattern = fmt('%s%s?.so', install_cpath, util.get_separator())
if not string.find(package.cpath, install_cpath_pattern, 1, true) then
package.cpath = package.cpath .. ';' .. install_cpath_pattern
end
nvim_paths_are_setup = true
end
local function generate_path_setup_code()
local package_path_str = vim.inspect(package_paths)
local install_cpath = util.join_paths(hererocks_install_dir, 'lib', 'lua', lua_version.lua)
local install_cpath_pattern = fmt('"%s%s?.so"', install_cpath, util.get_separator())
install_cpath_pattern = vim.fn.escape(install_cpath_pattern, [[\]])
return [[
local package_path_str = ]] .. package_path_str .. [[
local install_cpath_pattern = ]] .. install_cpath_pattern .. [[
if not string.find(package.path, package_path_str, 1, true) then
package.path = package.path .. ';' .. package_path_str
end
if not string.find(package.cpath, install_cpath_pattern, 1, true) then
package.cpath = package.cpath .. ';' .. install_cpath_pattern
end
]]
end
local function activate_hererocks_cmd(install_path)
local activate_file = 'activate'
local user_shell = os.getenv 'SHELL'
local shell = user_shell:gmatch '([^/]*)$'()
if shell == 'fish' then
activate_file = 'activate.fish'
elseif shell == 'csh' then
activate_file = 'activate.csh'
end
return fmt('source %s', util.join_paths(install_path, 'bin', activate_file))
end
local function run_luarocks(args, disp, operation_name)
local cmd = {
os.getenv 'SHELL',
'-c',
fmt('%s && luarocks --tree=%s %s', activate_hererocks_cmd(hererocks_install_dir), shell_hererocks_dir, args),
}
return async(function()
local output = jobs.output_table()
local callbacks = {
stdout = jobs.logging_callback(output.err.stdout, output.data.stdout, nil, disp, operation_name),
stderr = jobs.logging_callback(output.err.stderr, output.data.stderr),
}
local opts = { capture_output = callbacks }
return await(jobs.run(cmd, opts))
:map_err(function(err)
return { msg = fmt('Error running luarocks %s', args), data = err, output = output }
end)
:map_ok(function(data)
return { data = data, output = output }
end)
end)
end
local luarocks_keys = { only_server = 'only-server', only_source = 'only-sources' }
local function is_valid_luarock_key(key)
return not (key == 'tree' or key == 'local')
end
local function format_luarocks_args(package)
if type(package) ~= 'table' then
return ''
end
local args = {}
for key, value in pairs(package) do
if type(key) == 'string' and is_valid_luarock_key(key) then
local luarock_key = luarocks_keys[key] and luarocks_keys[key] or key
if luarock_key and type(value) == 'string' then
table.insert(args, string.format('--%s=%s', key, value))
elseif key == 'env' and type(value) == 'table' then
for name, env_value in pairs(value) do
table.insert(args, string.format('%s=%s', name, env_value))
end
end
end
end
return ' ' .. table.concat(args, ' ')
end
local function luarocks_install(package, results, disp)
return async(function()
local package_name = type(package) == 'table' and package[1] or package
if disp then
disp:task_update('luarocks-install', 'installing ' .. package_name)
end
local args = format_luarocks_args(package)
local version = package.version and ' ' .. package.version .. ' ' or ''
local install_result = await(run_luarocks('install ' .. package_name .. version .. args, disp, 'luarocks-install'))
if results then
results.luarocks.installs[package_name] = install_result
end
return install_result
end)
end
local function install_packages(packages, results, disp)
return async(function()
local r = result.ok()
if not hererocks_is_setup() then
r:and_then(await, hererocks_installer(disp))
end
if disp then
disp:task_start('luarocks-install', 'installing rocks...')
end
if results then
results.luarocks.installs = {}
end
for _, package in ipairs(packages) do
r:and_then(await, luarocks_install(package, results, disp))
end
r:map_ok(function()
if disp then
disp:task_succeeded('luarocks-install', 'rocks installed!')
end
end):map_err(function()
if disp then
disp:task_failed('luarocks-install', 'installing rocks failed!')
end
end)
return r
end)
end
--- Install the packages specified with `packages` synchronously
local function install_sync(packages)
return async(function()
return await(install_packages(packages))
end)()
end
local function chunk_output(output)
-- Merge the output to a single line, then split again. Helps to deal with inconsistent
-- chunking in the output collection
local res = table.concat(output, '\n')
return vim.split(res, '\n')
end
local function luarocks_list(disp)
return async(function()
local r = result.ok()
if not hererocks_is_setup() then
r:and_then(await, hererocks_installer(disp))
end
r:and_then(await, run_luarocks 'list --porcelain')
return r:map_ok(function(data)
local results = {}
local output = chunk_output(data.output.data.stdout)
for _, line in ipairs(output) do
for l_package, version, status, install_path in string.gmatch(line, '([^\t]+)\t([^\t]+)\t([^\t]+)\t([^\t]+)') do
table.insert(results, {
name = l_package,
version = version,
status = status,
install_path = install_path,
})
end
end
return results
end)
end)
end
local function luarocks_show(package, disp)
return async(function()
local r = result.ok()
if not hererocks_is_setup() then
r:and_then(await, hererocks_installer(disp))
end
r:and_then(await, run_luarocks('show --porcelain ' .. package))
return r:map_ok(function(data)
local output = chunk_output(data.output.data.stdout)
local dependencies = {}
for _, line in ipairs(output) do
local components = {}
for component in string.gmatch(line, '([^%s]+)') do
components[#components + 1] = component
end
if (components[1] == 'dependency' or components[1] == 'indirect_dependency') and (components[2] ~= 'lua') then
dependencies[components[2]] = components[2]
end
end
return dependencies
end)
end)
end
local function luarocks_remove(package, results, disp)
return async(function()
if disp then
disp:task_update('luarocks-remove', 'removing ' .. package)
end
local remove_result = await(run_luarocks('remove ' .. package, disp, 'luarocks-remove'))
if results then
results.luarocks.removals[package] = remove_result
end
return remove_result
end)
end
local function uninstall_packages(packages, results, disp)
return async(function()
local r = result.ok()
if not hererocks_is_setup() then
r:and_then(await, hererocks_installer(disp))
end
if disp then
disp:task_start('luarocks-remove', 'uninstalling rocks...')
end
if results then
results.luarocks.removals = {}
end
for _, package in ipairs(packages) do
local name = type(package) == 'table' and package[1] or package
r:and_then(await, luarocks_remove(name, results, disp))
end
r:map_ok(function()
if disp then
disp:task_succeeded('luarocks-remove', 'rocks cleaned!')
end
end):map_err(function()
if disp then
disp:task_failed('luarocks-remove', 'cleaning rocks failed!')
end
end)
return r
end)
end
--- Uninstall the packages specified with `packages` synchronously
local function uninstall_sync(packages)
return async(function()
return await(uninstall_packages(packages))
end)()
end
local function clean_rocks(rocks, results, disp)
return async(function()
local r = result.ok()
if not hererocks_is_setup() then
return r
end
r:and_then(await, luarocks_list(disp))
local installed_packages
if r.ok then
installed_packages = r.ok
else
return r
end
local dependency_info = {}
for _, package in ipairs(installed_packages) do
r:and_then(await, luarocks_show(package.name, disp))
if r.ok then
dependency_info[package.name] = r.ok
end
end
r = r:map_ok(function()
local to_remove = {}
for _, package in ipairs(installed_packages) do
to_remove[package.name] = package
end
for _, rock in pairs(rocks) do
if type(rock) == 'table' then
if to_remove[rock[1]] and (not rock.version or to_remove[rock[1]].version == rock.version) then
to_remove[rock[1]] = nil
end
else
to_remove[rock] = nil
end
end
for rock, dependencies in pairs(dependency_info) do
if rocks[rock] ~= nil then
for _, dependency in pairs(dependencies) do
to_remove[dependency] = nil
end
end
end
-- Toposort to ensure that we remove packages before their dependencies
local removal_order = {}
local frontier = {}
for rock, _ in pairs(to_remove) do
if next(dependency_info[rock]) == nil then
frontier[#frontier + 1] = rock
dependency_info[rock] = nil
end
end
local inverse_dependencies = {}
for rock, depends in pairs(dependency_info) do
for d, _ in pairs(depends) do
inverse_dependencies[d] = inverse_dependencies[d] or {}
inverse_dependencies[d][rock] = true
end
end
while #frontier > 0 do
local rock = table.remove(frontier)
removal_order[#removal_order + 1] = rock
local inv_depends = inverse_dependencies[rock]
if inv_depends ~= nil then
for depends, _ in pairs(inverse_dependencies[rock]) do
table.remove(dependency_info[depends])
if #dependency_info[depends] == 0 then
frontier[#frontier + 1] = depends
end
end
end
end
local reverse_order = {}
for i = #removal_order, 1, -1 do
reverse_order[#reverse_order + 1] = removal_order[i]
end
return reverse_order
end)
if results ~= nil then
results.luarocks = results.luarocks or {}
end
return r:and_then(await, uninstall_packages(r.ok, results, disp))
end)
end
local function ensure_rocks(rocks, results, disp)
return async(function()
local to_install = {}
for _, rock in pairs(rocks) do
if type(rock) == 'table' then
to_install[rock[1]:lower()] = rock
else
to_install[rock:lower()] = true
end
end
local r = result.ok()
if next(to_install) == nil then
return r
end
if disp == nil then
disp = require('packer.display').open(config.display.open_fn or config.display.open_cmd)
end
if not hererocks_is_setup() then
r = r:and_then(await, hererocks_installer(disp))
end
r:and_then(await, luarocks_list(disp))
r:map_ok(function(installed_packages)
for _, package in ipairs(installed_packages) do
local spec = to_install[package.name]
if spec then
if type(spec) == 'table' then
-- if the package is on the system and the spec has no version
-- or it has a version and that is the version on the system do not install it again
if not spec.version or (spec.version and spec.version == package.version) then
to_install[package.name] = nil
end
else
to_install[package.name] = nil
end
end
end
local package_specs = {}
for name, spec in pairs(to_install) do
if type(spec) == 'table' then
table.insert(package_specs, spec)
else
table.insert(package_specs, { name })
end
end
return package_specs
end)
results.luarocks = results.luarocks or {}
return r:and_then(await, install_packages(r.ok, results, disp))
end)
end
local function handle_command(cmd, ...)
local task
local packages = { ... }
if cmd == 'install' then
task = install_packages(packages)
elseif cmd == 'remove' then
task = uninstall_packages(packages)
else
log.warn 'Unrecognized command!'
return result.err 'Unrecognized command'
end
return async(function()
local r = await(task)
await(a.main)
local package_names = vim.fn.escape(vim.inspect(packages), '"')
return r:map_ok(function(data)
local operation_name = cmd:sub(1, 1):upper() .. cmd:sub(2)
log.info(fmt('%sed packages %s', operation_name, package_names))
return data
end):map_err(function(err)
log.error(fmt('Failed to %s packages %s: %s', cmd, package_names, vim.fn.escape(vim.inspect(err), '"\n')))
return err
end)
end)()
end
local function make_commands()
vim.cmd [[ command! -nargs=+ PackerRocks lua require('packer.luarocks').handle_command(<f-args>) ]]
end
return {
handle_command = handle_command,
install_commands = make_commands,
list = luarocks_list,
install_hererocks = hererocks_installer,
setup_paths = setup_nvim_paths,
uninstall = uninstall_sync,
clean = clean_rocks,
install = install_sync,
ensure = ensure_rocks,
generate_path_setup = generate_path_setup_code,
cfg = cfg,
}
================================================
FILE: lua/packer/plugin_types/git.lua
================================================
local util = require 'packer.util'
local jobs = require 'packer.jobs'
local a = require 'packer.async'
local result = require 'packer.result'
local log = require 'packer.log'
local await = a.wait
local async = a.sync
local fmt = string.format
local vim = vim
local git = {}
local blocked_env_vars = {
GIT_DIR = true,
GIT_INDEX_FILE = true,
GIT_OBJECT_DIRECTORY = true,
GIT_TERMINAL_PROMPT = true,
GIT_WORK_TREE = true,
GIT_COMMON_DIR = true,
}
local function ensure_git_env()
if git.job_env == nil then
local job_env = {}
for k, v in pairs(vim.fn.environ()) do
if not blocked_env_vars[k] then
table.insert(job_env, k .. '=' .. v)
end
end
table.insert(job_env, 'GIT_TERMINAL_PROMPT=0')
git.job_env = job_env
end
end
local function has_wildcard(tag)
if not tag then
return false
end
return string.match(tag, '*') ~= nil
end
local break_tag_pattern = [=[[bB][rR][eE][aA][kK]!?:]=]
local breaking_change_pattern = [=[[bB][rR][eE][aA][kK][iI][nN][gG][ _][cC][hH][aA][nN][gG][eE]]=]
local type_exclam_pattern = [=[[a-zA-Z]+!:]=]
local type_scope_exclam_pattern = [=[[a-zA-Z]+%([^)]+%)!:]=]
local function mark_breaking_commits(plugin, commit_bodies)
local commits = vim.gsplit(table.concat(commit_bodies, '\n'), '===COMMIT_START===', true)
for commit in commits do
local commit_parts = vim.split(commit, '===BODY_START===')
local body = commit_parts[2]
local lines = vim.split(commit_parts[1], '\n')
local is_breaking = (
body ~= nil
and (
(string.match(body, breaking_change_pattern) ~= nil)
or (string.match(body, break_tag_pattern) ~= nil)
or (string.match(body, type_exclam_pattern) ~= nil)
or (string.match(body, type_scope_exclam_pattern) ~= nil)
)
)
or (
lines[2] ~= nil
and (
(string.match(lines[2], breaking_change_pattern) ~= nil)
or (string.match(lines[2], break_tag_pattern) ~= nil)
or (string.match(lines[2], type_exclam_pattern) ~= nil)
or (string.match(lines[2], type_scope_exclam_pattern) ~= nil)
)
)
if is_breaking then
plugin.breaking_commits[#plugin.breaking_commits + 1] = lines[1]
end
end
end
local config = nil
git.cfg = function(_config)
config = _config.git
config.base_dir = _config.package_root
config.default_base_dir = util.join_paths(config.base_dir, _config.plugin_package)
config.exec_cmd = config.cmd .. ' '
ensure_git_env()
end
---Resets a git repo `dest` to `commit`
---@param dest string @ path to the local git repo
---@param commit string @ commit hash
---@return function @ async function
local function reset(dest, commit)
local reset_cmd = fmt(config.exec_cmd .. config.subcommands.revert_to, commit)
local opts = { capture_output = true, cwd = dest, options = { env = git.job_env } }
return async(function()
return await(jobs.run(reset_cmd, opts))
end)
end
local handle_checkouts = function(plugin, dest, disp, opts)
local plugin_name = util.get_plugin_full_name(plugin)
return async(function()
if disp ~= nil then
disp:task_update(plugin_name, 'fetching reference...')
end
local output = jobs.output_table()
local callbacks = {
stdout = jobs.logging_callback(output.err.stdout, output.data.stdout, nil, disp, plugin_name),
stderr = jobs.logging_callback(output.err.stderr, output.data.stderr),
}
local job_opts = { capture_output = callbacks, cwd = dest, options = { env = git.job_env } }
local r = result.ok()
if plugin.tag and has_wildcard(plugin.tag) then
disp:task_update(plugin_name, fmt('getting tag for wildcard %s...', plugin.tag))
local fetch_tags = config.exec_cmd .. fmt(config.subcommands.tags_expand_fmt, plugin.tag)
r:and_then(await, jobs.run(fetch_tags, job_opts))
local data = output.data.stdout[1]
if data then
plugin.tag = vim.split(data, '\n')[1]
else
log.warn(
fmt('Wildcard expansion did not found any tag for plugin %s: defaulting to latest commit...', plugin.name)
)
plugin.tag = nil -- Wildcard is not found, then we bypass the tag
end
end
if plugin.branch or (plugin.tag and not opts.preview_updates) then
local branch_or_tag = plugin.branch and plugin.branch or plugin.tag
if disp ~= nil then
disp:task_update(plugin_name, fmt('checking out %s %s...', plugin.branch and 'branch' or 'tag', branch_or_tag))
end
r:and_then(await, jobs.run(config.exec_cmd .. fmt(config.subcommands.checkout, branch_or_tag), job_opts))
:map_err(function(err)
return {
msg = fmt(
'Error checking out %s %s for %s',
plugin.branch and 'branch' or 'tag',
branch_or_tag,
plugin_name
),
data = err,
output = output,
}
end)
end
if plugin.commit then
if disp ~= nil then
disp:task_update(plugin_name, fmt('checking out %s...', plugin.commit))
end
r:and_then(await, jobs.run(config.exec_cmd .. fmt(config.subcommands.checkout, plugin.commit), job_opts))
:map_err(function(err)
return {
msg = fmt('Error checking out commit %s for %s', plugin.commit, plugin_name),
data = err,
output = output,
}
end)
end
return r:map_ok(function(ok)
return { status = ok, output = output }
end):map_err(function(err)
if not err.msg then
return {
msg = fmt('Error updating %s: %s', plugin_name, table.concat(err, '\n')),
data = err,
output = output,
}
end
err.output = output
return err
end)
end)
end
local get_rev = function(plugin)
local plugin_name = util.get_plugin_full_name(plugin)
local rev_cmd = config.exec_cmd .. config.subcommands.get_rev
return async(function()
local rev = await(jobs.run(rev_cmd, { cwd = plugin.install_path, options = { env = git.job_env }, capture_output = true }))
:map_ok(function(ok)
local _, r = next(ok.output.data.stdout)
return r
end)
:map_err(function(err)
local _, msg = fmt('%s: %s', plugin_name, next(err.output.data.stderr))
return msg
end)
return rev
end)
end
local split_messages = function(messages)
local lines = {}
for _, message in ipairs(messages) do
vim.list_extend(lines, vim.split(message, '\n'))
table.insert(lines, '')
end
return lines
end
git.setup = function(plugin)
local plugin_name = util.get_plugin_full_name(plugin)
local install_to = plugin.install_path
local install_cmd =
vim.split(config.exec_cmd .. fmt(config.subcommands.install, plugin.commit and 999999 or config.depth), '%s+')
local submodule_cmd = config.exec_cmd .. config.subcommands.submodules
local rev_cmd = config.exec_cmd .. config.subcommands.get_rev
local update_cmd = config.exec_cmd .. config.subcommands.update
local update_head_cmd = config.exec_cmd .. config.subcommands.update_head
local fetch_cmd = config.exec_cmd .. config.subcommands.fetch
if plugin.commit or plugin.tag then
update_cmd = fetch_cmd
end
local branch_cmd = config.exec_cmd .. config.subcommands.current_branch
local current_commit_cmd = vim.split(config.exec_cmd .. config.subcommands.get_header, '%s+')
for i, arg in ipairs(current_commit_cmd) do
current_commit_cmd[i] = string.gsub(arg, 'FMT', config.subcommands.diff_fmt)
end
if plugin.branch or (plugin.tag and not has_wildcard(plugin.tag)) then
install_cmd[#install_cmd + 1] = '--branch'
install_cmd[#install_cmd + 1] = plugin.branch and plugin.branch or plugin.tag
end
install_cmd[#install_cmd + 1] = plugin.url
install_cmd[#install_cmd + 1] = install_to
local needs_checkout = plugin.tag ~= nil or plugin.commit ~= nil or plugin.branch ~= nil
plugin.installer = function(disp)
local output = jobs.output_table()
local callbacks = {
stdout = jobs.logging_callback(output.err.stdout, output.data.stdout),
stderr = jobs.logging_callback(output.err.stderr, output.data.stderr, nil, disp, plugin_name),
}
local installer_opts = {
capture_output = callbacks,
timeout = config.clone_timeout,
options = { env = git.job_env },
}
return async(function()
disp:task_update(plugin_name, 'cloning...')
local r = await(jobs.run(install_cmd, installer_opts))
installer_opts.cwd = install_to
r:and_then(await, jobs.run(submodule_cmd, installer_opts))
if plugin.commit then
disp:task_update(plugin_name, fmt('checking out %s...', plugin.commit))
r:and_then(await, jobs.run(config.exec_cmd .. fmt(config.subcommands.checkout, plugin.commit), installer_opts))
:map_err(function(err)
return {
msg = fmt('Error checking out commit %s for %s', plugin.commit, plugin_name),
data = { err, output },
}
end)
end
r:and_then(await, jobs.run(current_commit_cmd, installer_opts))
:map_ok(function(_)
plugin.messages = output.data.stdout
end)
:map_err(function(err)
plugin.output = { err = output.data.stderr }
if not err.msg then
return {
msg = fmt('Error installing %s: %s', plugin_name, table.concat(output.data.stderr, '\n')),
data = { err, output },
}
end
end)
return r
end)
end
plugin.remote_url = function()
return async(function()
return await(
jobs.run(
fmt('%s remote get-url origin', config.exec_cmd),
{ capture_output = true, cwd = plugin.install_path, options = { env = git.job_env } }
)
):map_ok(function(data)
return { remote = data.output.data.stdout[1] }
end)
end)
end
plugin.updater = function(disp, opts)
return async(function()
local update_info = { err = {}, revs = {}, output = {}, messages = {} }
local function exit_ok(r)
if #update_info.err > 0 or r.exit_code ~= 0 then
return result.err(r)
end
return result.ok(r)
end
local rev_onread = jobs.logging_callback(update_info.err, update_info.revs)
local rev_callbacks = { stdout = rev_onread, stderr = rev_onread }
disp:task_update(plugin_name, 'checking current commit...')
local r = await(
jobs.run(
rev_cmd,
{ success_test = exit_ok, capture_output = rev_callbacks, cwd = install_to, options = { env = git.job_env } }
)
):map_err(function(err)
plugin.output = { err = vim.list_extend(update_info.err, update_info.revs), data = {} }
return {
msg = fmt('Error getting current commit for %s: %s', plugin_name, table.concat(update_info.revs, '\n')),
data = err,
}
end)
local current_branch
disp:task_update(plugin_name, 'checking current branch...')
r:and_then(
await,
jobs.run(
branch_cmd,
{ success_test = exit_ok, capture_output = true, cwd = install_to, options = { env = git.job_env } }
)
)
:map_ok(function(ok)
current_branch = ok.output.data.stdout[1]
end)
:map_err(function(err)
plugin.output = { err = vim.list_extend(update_info.err, update_info.revs), data = {} }
return {
msg = fmt('Error checking current branch for %s: %s', plugin_name, table.concat(update_info.revs, '\n')),
data = err,
}
end)
if not needs_checkout then
local origin_branch = ''
disp:task_update(plugin_name, 'checking origin branch...')
local origin_refs_path = util.join_paths(install_to, '.git', 'refs', 'remotes', 'origin', 'HEAD')
local origin_refs_file = vim.loop.fs_open(origin_refs_path, 'r', 438)
if origin_refs_file ~= nil then
local origin_refs_stat = vim.loop.fs_fstat(origin_refs_file)
-- NOTE: This should check for errors
local origin_refs = vim.split(vim.loop.fs_read(origin_refs_file, origin_refs_stat.size, 0), '\n')
vim.loop.fs_close(origin_refs_file)
if #origin_refs > 0 then
origin_branch = string.match(origin_refs[1], [[^ref: refs/remotes/origin/(.*)]])
end
end
if current_branch ~= origin_branch then
needs_checkout = true
plugin.branch = origin_branch
end
end
local update_callbacks = {
stdout = jobs.logging_callback(update_info.err, update_info.output),
stderr = jobs.logging_callback(update_info.err, update_info.output, nil, disp, plugin_name),
}
local update_opts = {
success_test = exit_ok,
capture_output = update_callbacks,
cwd = install_to,
options = { env = git.job_env },
}
if needs_checkout then
r:and_then(await, jobs.run(config.exec_cmd .. config.subcommands.fetch, update_opts))
r:and_then(await, handle_checkouts(plugin, install_to, disp, opts))
local function merge_output(res)
if res.output ~= nil then
vim.list_extend(update_info.err, res.output.err.stderr)
vim.list_extend(update_info.err, res.output.err.stdout)
vim.list_extend(update_info.output, res.output.data.stdout)
vim.list_extend(update_info.output, res.output.data.stderr)
end
end
r:map_ok(merge_output)
r:map_err(function(err)
merge_output(err)
plugin.output = { err = vim.list_extend(update_info.err, update_info.output), data = {} }
local errmsg = '<unknown error>'
if err ~= nil and err.msg ~= nil then
errmsg = err.msg
end
return { msg = errmsg .. ' ' .. table.concat(update_info.output, '\n'), data = err.data }
end)
end
if opts.preview_updates then
disp:task_update(plugin_name, 'fetching updates...')
r:and_then(await, jobs.run(fetch_cmd, update_opts))
elseif opts.pull_head then
disp:task_update(plugin_name, 'pulling updates from head...')
r:and_then(await, jobs.run(update_head_cmd, update_opts))
else
disp:task_update(plugin_name, 'pulling updates...')
r:and_then(await, jobs.run(update_cmd, update_opts)):and_then(await, jobs.run(submodule_cmd, update_opts))
end
r:map_err(function(err)
plugin.output = { err = vim.list_extend(update_info.err, update_info.output), data = {} }
return {
msg = fmt('Error getting updates for %s: %s', plugin_name, table.concat(update_info.output, '\n')),
data = err,
}
end)
local post_rev_cmd
if plugin.tag ~= nil then
-- NOTE that any tag wildcard should already been expanded to a specific commit at this point
post_rev_cmd = string.gsub(rev_cmd, 'HEAD', string.format('%s^{}', plugin.tag))
elseif opts.preview_updates then
post_rev_cmd = string.gsub(rev_cmd, 'HEAD', 'FETCH_HEAD')
else
post_rev_cmd = rev_cmd
end
disp:task_update(plugin_name, 'checking updated commit...')
r:and_then(
await,
jobs.run(post_rev_cmd, {
success_test = exit_ok,
capture_output = rev_callbacks,
cwd = install_to,
options = { env = git.job_env },
})
):map_err(function(err)
plugin.output = { err = vim.list_extend(update_info.err, update_info.revs), data = {} }
return {
msg = fmt('Error checking updated commit for %s: %s', plugin_name, table.concat(update_info.revs, '\n')),
data = err,
}
end)
if r.ok then
if update_info.revs[1] ~= update_info.revs[2] then
local commit_headers_onread = jobs.logging_callback(update_info.err, update_info.messages)
local commit_headers_callbacks = { stdout = commit_headers_onread, stderr = commit_headers_onread }
local diff_cmd = string.format(config.subcommands.diff, update_info.revs[1], update_info.revs[2])
local commit_headers_cmd = vim.split(config.exec_cmd .. diff_cmd, '%s+')
for i, arg in ipairs(commit_headers_cmd) do
commit_headers_cmd[i] = string.gsub(arg, 'FMT', config.subcommands.diff_fmt)
end
disp:task_update(plugin_name, 'getting commit messages...')
r:and_then(
await,
jobs.run(commit_headers_cmd, {
success_test = exit_ok,
capture_output = commit_headers_callbacks,
cwd = install_to,
options = { env = git.job_env },
})
)
plugin.output = { err = update_info.err, data = update_info.output }
if r.ok then
plugin.messages = update_info.messages
plugin.revs = update_info.revs
end
if config.mark_breaking_changes then
local commit_bodies = { err = {}, output = {} }
local commit_bodies_onread = jobs.logging_callback(commit_bodies.err, commit_bodies.output)
local commit_bodies_callbacks = { stdout = commit_bodies_onread, stderr = commit_bodies_onread }
local commit_bodies_cmd = config.exec_cmd .. config.subcommands.get_bodies
if opts.preview_updates then
commit_bodies_cmd = config.exec_cmd .. config.subcommands.get_fetch_bodies
end
disp:task_update(plugin_name, 'checking for breaking changes...')
r:and_then(
await,
jobs.run(commit_bodies_cmd, {
success_test = exit_ok,
capture_output = commit_bodies_callbacks,
cwd = install_to,
options = { env = git.job_env },
})
):map_ok(function(ok)
plugin.breaking_commits = {}
mark_breaking_commits(plugin, commit_bodies.output)
return ok
end)
end
else
plugin.revs = update_info.revs
plugin.messages = update_info.messages
end
else
plugin.output.err = vim.list_extend(plugin.output.err, update_info.messages)
end
r.info = update_info
return r
end)
end
plugin.diff = function(commit, callback)
async(function()
local diff_cmd = config.exec_cmd .. fmt(config.subcommands.git_diff_fmt, commit)
local diff_info = { err = {}, output = {}, messages = {} }
local diff_onread = jobs.logging_callback(diff_info.err, diff_info.messages)
local diff_callbacks = { stdout = diff_onread, stderr = diff_onread }
return await(jobs.run(diff_cmd, { capture_output = diff_callbacks, cwd = install_to, options = { env = git.job_env } }))
:map_ok(function(_)
return callback(split_messages(diff_info.messages))
end)
:map_err(function(err)
return callback(nil, err)
end)
end)()
end
plugin.revert_last = function()
local r = result.ok()
async(function()
local revert_cmd = config.exec_cmd .. config.subcommands.revert
r:and_then(
await,
jobs.run(revert_cmd, { capture_output = true, cwd = install_to, options = { env = git.job_env } })
)
if needs_checkout then
r:and_then(await, handle_checkouts(plugin, install_to, nil, {}))
end
return r
end)()
return r
end
---Reset the plugin to `commit`
---@param commit string
plugin.revert_to = function(commit)
assert(type(commit) == 'string', fmt("commit: string expected but '%s' provided", type(commit)))
return async(function()
require('packer.log').debug(fmt("Reverting '%s' to commit '%s'", plugin.name, commit))
return await(reset(install_to, commit))
end)
end
---Returns HEAD's short hash
---@return string
plugin.get_rev = function()
return get_rev(plugin)
end
end
return git
================================================
FILE: lua/packer/plugin_types/local.lua
================================================
local a = require 'packer.async'
local log = require 'packer.log'
local util = require 'packer.util'
local result = require 'packer.result'
local async = a.sync
local await = a.wait
local fmt = string.format
local config = nil
local function cfg(_config)
config = _config
end
-- Due to #679, we know that fs_symlink requires admin privileges on Windows. This is a workaround,
-- as suggested by @nonsleepr.
local symlink_fn
if util.is_windows then
symlink_fn = function(path, new_path, flags, callback)
flags = flags or {}
flags.junction = true
return vim.loop.fs_symlink(path, new_path, flags, callback)
end
else
symlink_fn = vim.loop.fs_symlink
end
local symlink = a.wrap(symlink_fn)
local unlink = a.wrap(vim.loop.fs_unlink)
local function setup_local(plugin)
local from = vim.loop.fs_realpath(util.strip_trailing_sep(plugin.path))
local to = util.strip_trailing_sep(plugin.install_path)
local plugin_name = util.get_plugin_full_name(plugin)
plugin.installer = function(disp)
return async(function()
disp:task_update(plugin_name, 'making symlink...')
local err, success = await(symlink(from, to, { dir = true }))
if not success then
plugin.output = { err = { err } }
return result.err(err)
end
return result.ok()
end)
end
plugin.updater = function(disp)
return async(function()
local r = result.ok()
disp:task_update(plugin_name, 'checking symlink...')
local resolved_path = vim.loop.fs_realpath(to)
if resolved_path ~= from then
disp:task_
gitextract_bz_3r55i/ ├── .github/ │ ├── FUNDING.yml │ ├── ISSUE_TEMPLATE/ │ │ ├── bug-report--packer-v1-.md │ │ ├── bug-report--packer-v2-.md │ │ └── feature_request.md │ └── workflows/ │ ├── formatting.yaml │ └── test.yaml ├── .gitignore ├── .lua-format ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── doc/ │ └── packer.txt ├── lua/ │ ├── packer/ │ │ ├── async.lua │ │ ├── clean.lua │ │ ├── compile.lua │ │ ├── display.lua │ │ ├── handlers.lua │ │ ├── install.lua │ │ ├── jobs.lua │ │ ├── load.lua │ │ ├── log.lua │ │ ├── luarocks.lua │ │ ├── plugin_types/ │ │ │ ├── git.lua │ │ │ └── local.lua │ │ ├── plugin_types.lua │ │ ├── plugin_utils.lua │ │ ├── result.lua │ │ ├── snapshot.lua │ │ ├── update.lua │ │ └── util.lua │ └── packer.lua ├── selene.toml ├── stylua.toml ├── tests/ │ ├── helpers.lua │ ├── local_plugin_spec.lua │ ├── minimal.vim │ ├── packer_plugin_utils_spec.lua │ ├── packer_use_spec.lua │ ├── plugin_utils_spec.lua │ └── snapshot_spec.lua └── vim.toml
Condensed preview — 42 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (283K chars).
[
{
"path": ".github/FUNDING.yml",
"chars": 644,
"preview": "# These are supported funding model platforms\n\ngithub: wbthomason\npatreon: # Replace with a single Patreon username\nopen"
},
{
"path": ".github/ISSUE_TEMPLATE/bug-report--packer-v1-.md",
"chars": 980,
"preview": "---\nname: Bug report (packer v1)\nabout: Report a bug in packer.nvim, v1 (current master branch)\ntitle: ''\nlabels: bug, v"
},
{
"path": ".github/ISSUE_TEMPLATE/bug-report--packer-v2-.md",
"chars": 990,
"preview": "---\nname: Bug report (packer v2)\nabout: Report a bug in packer.nvim, v2 (currently in alpha)\ntitle: ''\nlabels: bug, fix "
},
{
"path": ".github/ISSUE_TEMPLATE/feature_request.md",
"chars": 399,
"preview": "---\nname: Feature request\nabout: Suggest a feature for packer.nvim\ntitle: ''\nlabels: enhancement\nassignees: ''\n\n---\n\n<!-"
},
{
"path": ".github/workflows/formatting.yaml",
"chars": 844,
"preview": "name: formatting\non:\n push:\n paths-ignore:\n - \".github/**\"\n - \"*.md\"\n branches: [\"master\"]\n\njobs:\n sty"
},
{
"path": ".github/workflows/test.yaml",
"chars": 1031,
"preview": "---\nname: CI\non:\n pull_request: ~\n push:\n branches:\n - master\n\njobs:\n build:\n name: Run tests\n runs-on:"
},
{
"path": ".gitignore",
"chars": 357,
"preview": "# Compiled Lua sources\nluac.out\n\n# luarocks build files\n*.src.rock\n*.zip\n*.tar.gz\n\n# Object files\n*.o\n*.os\n*.ko\n*.obj\n*."
},
{
"path": ".lua-format",
"chars": 690,
"preview": "align_args: true\nalign_parameter: true\nalign_table_field: true\nbreak_after_functioncall_lp: false\nbreak_after_functionde"
},
{
"path": "Dockerfile",
"chars": 710,
"preview": "FROM archlinux:base-devel\nWORKDIR /setup\nRUN pacman -Sy git neovim python --noconfirm\nRUN useradd -m test\n\nUSER test\nRUN"
},
{
"path": "LICENSE",
"chars": 1069,
"preview": "MIT License\n\nCopyright (c) 2017 Wil Thomason\n\nPermission is hereby granted, free of charge, to any person obtaining a co"
},
{
"path": "Makefile",
"chars": 328,
"preview": "test:\n\tnvim --headless --noplugin -u tests/minimal.vim -c \"PlenaryBustedDirectory tests/ { minimal_init = './tests/minim"
},
{
"path": "README.md",
"chars": 33657,
"preview": "**NOTICE:**\n\nThis repository is currently unmaintained. For the time being (as of August, 2023), it is recommended to us"
},
{
"path": "doc/packer.txt",
"chars": 31413,
"preview": "*packer.txt* A use-package inspired Neovim plugin manager\n*packer.nvim*\n\nAuthor: Wil Thomason <wil."
},
{
"path": "lua/packer/async.lua",
"chars": 3410,
"preview": "-- Adapted from https://ms-jpq.github.io/neovim-async-tutorial/\nlocal log = require 'packer.log'\nlocal yield = coroutine"
},
{
"path": "lua/packer/clean.lua",
"chars": 3070,
"preview": "local plugin_utils = require 'packer.plugin_utils'\nlocal a = require 'packer.async'\nlocal display = require 'packer.disp"
},
{
"path": "lua/packer/compile.lua",
"chars": 24905,
"preview": "-- Compiling plugin specifications to Lua for lazy-loading\nlocal util = require 'packer.util'\nlocal log = require 'packe"
},
{
"path": "lua/packer/display.lua",
"chars": 32831,
"preview": "local api = vim.api\nlocal log = require 'packer.log'\nlocal a = require 'packer.async'\nlocal plugin_utils = require 'pack"
},
{
"path": "lua/packer/handlers.lua",
"chars": 123,
"preview": "local config = nil\n\nlocal function cfg(_config)\n config = _config\nend\n\nlocal handlers = {\n cfg = cfg,\n}\n\nreturn handle"
},
{
"path": "lua/packer/install.lua",
"chars": 1839,
"preview": "local a = require 'packer.async'\nlocal log = require 'packer.log'\nlocal util = require 'packer.util'\nlocal display = req"
},
{
"path": "lua/packer/jobs.lua",
"chars": 7351,
"preview": "-- Interface with Neovim job control and provide a simple job sequencing structure\nlocal split = vim.split\nlocal loop = "
},
{
"path": "lua/packer/load.lua",
"chars": 5529,
"preview": "local packer_load = nil\nlocal cmd = vim.api.nvim_command\nlocal fmt = string.format\n\nlocal function verify_conditions(con"
},
{
"path": "lua/packer/log.lua",
"chars": 4644,
"preview": "-- log.lua\n--\n-- Inspired by rxi/log.lua\n-- Modified by tjdevries and can be found at github.com/tjdevries/vlog.nvim\n--\n"
},
{
"path": "lua/packer/luarocks.lua",
"chars": 18213,
"preview": "-- Add support for installing and cleaning Luarocks dependencies\n-- Based off of plenary/neorocks/init.lua in https://gi"
},
{
"path": "lua/packer/plugin_types/git.lua",
"chars": 20170,
"preview": "local util = require 'packer.util'\nlocal jobs = require 'packer.jobs'\nlocal a = require 'packer.async'\nlocal result = re"
},
{
"path": "lua/packer/plugin_types/local.lua",
"chars": 1887,
"preview": "local a = require 'packer.async'\nlocal log = require 'packer.log'\nlocal util = require 'packer.util'\nlocal result = requ"
},
{
"path": "lua/packer/plugin_types.lua",
"chars": 276,
"preview": "local config\n\nlocal function cfg(_config)\n config = _config\nend\n\nlocal plugin_types = setmetatable({ cfg = cfg }, {\n _"
},
{
"path": "lua/packer/plugin_utils.lua",
"chars": 10144,
"preview": "local a = require 'packer.async'\nlocal jobs = require 'packer.jobs'\nlocal util = require 'packer.util'\nlocal result = re"
},
{
"path": "lua/packer/result.lua",
"chars": 1459,
"preview": "-- A simple Result<V, E> type to simplify control flow with installers and updaters\nlocal result = {}\n\nlocal ok_result_m"
},
{
"path": "lua/packer/snapshot.lua",
"chars": 7228,
"preview": "local a = require 'packer.async'\nlocal util = require 'packer.util'\nlocal log = require 'packer.log'\nlocal plugin_utils "
},
{
"path": "lua/packer/update.lua",
"chars": 4901,
"preview": "local util = require 'packer.util'\nlocal result = require 'packer.result'\nlocal display = require 'packer.display'\nlocal"
},
{
"path": "lua/packer/util.lua",
"chars": 3993,
"preview": "local util = {}\n\nutil.map = function(func, seq)\n local result = {}\n for _, v in ipairs(seq) do\n table.insert(result"
},
{
"path": "lua/packer.lua",
"chars": 34995,
"preview": "-- TODO: Performance analysis/tuning\n-- TODO: Merge start plugins?\nlocal util = require 'packer.util'\n\nlocal join_paths "
},
{
"path": "selene.toml",
"chars": 10,
"preview": "std=\"vim\"\n"
},
{
"path": "stylua.toml",
"chars": 100,
"preview": "indent_type = \"Spaces\"\nindent_width = 2\nquote_style = \"AutoPreferSingle\"\nno_call_parentheses = true\n"
},
{
"path": "tests/helpers.lua",
"chars": 624,
"preview": "local util = require 'packer.util'\n\nlocal M = { base_dir = '/tmp/__packer_tests__' }\n\n---Create a fake git repository\n--"
},
{
"path": "tests/local_plugin_spec.lua",
"chars": 1079,
"preview": "local a = require('plenary.async_lib.tests')\nlocal await = require('packer.async').wait\nlocal local_plugin = require('pa"
},
{
"path": "tests/minimal.vim",
"chars": 64,
"preview": "set rtp+=.\nset rtp+=../plenary.nvim\nruntime! plugin/plenary.vim\n"
},
{
"path": "tests/packer_plugin_utils_spec.lua",
"chars": 882,
"preview": "local a = require('plenary.async_lib.tests')\nlocal await = require('packer.async').wait\nlocal plugin_utils = require(\"pa"
},
{
"path": "tests/packer_use_spec.lua",
"chars": 782,
"preview": "local packer = require(\"packer\")\nlocal use = packer.use\nlocal packer_path = vim.fn.stdpath(\"data\")..\"/site/pack/packer/s"
},
{
"path": "tests/plugin_utils_spec.lua",
"chars": 2594,
"preview": "local a = require('plenary.async_lib.tests')\nlocal await = require('packer.async').wait\nlocal async = require('packer.as"
},
{
"path": "tests/snapshot_spec.lua",
"chars": 5917,
"preview": "local before_each = require('plenary.busted').before_each\nlocal a = require 'plenary.async_lib.tests'\nlocal util = requi"
},
{
"path": "vim.toml",
"chars": 610,
"preview": "[selene]\nbase = \"lua51\"\nname = \"vim\"\n\n[vim]\nany = true\n\n[jit]\nany = true\n\n[[describe.args]]\ntype = \"string\"\n[[describe.a"
}
]
About this extraction
This page contains the full source code of the wbthomason/packer.nvim GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 42 files (266.3 KB), approximately 68.4k tokens. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.