├── .github └── workflows │ ├── test.yml │ └── vint.yml ├── .gitignore ├── .vintrc.yml ├── Dockerfile ├── LICENSE ├── README.adoc ├── benchmark ├── Makefile ├── jetpack.vim ├── lua.vim ├── plug.vim ├── plugin │ ├── jetpack.vim │ └── plug.vim ├── profile.vim ├── requirements.txt ├── run.py ├── setup.py └── stat.py ├── doc └── jetpack.txt ├── plugin └── jetpack.vim └── test └── test.vim /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | on: [push, pull_request] 3 | jobs: 4 | test: 5 | name: ${{ matrix.os }} / ${{ matrix.vi }} 6 | runs-on: ${{ matrix.os }} 7 | strategy: 8 | fail-fast: false 9 | matrix: 10 | os: [ubuntu-22.04, windows-2022, macos-12] 11 | vi: [neovim, vim] 12 | steps: 13 | - name: Init git 14 | run: | 15 | git config --global user.email vim-jetpack@example.com 16 | git config --global user.name vim-jetpack 17 | 18 | - name: Clone vim-jetpack 19 | uses: actions/checkout@v3 20 | 21 | - uses: ilammy/msvc-dev-cmd@v1 22 | - name: Install Lua 23 | uses: leafo/gh-actions-lua@v10 24 | if: ${{ contains(matrix.os, 'windows') }} 25 | 26 | - name: Install vim-themis 27 | uses: actions/checkout@v3 28 | with: 29 | repository: thinca/vim-themis 30 | path: vim-themis 31 | 32 | - name: Install ${{ matrix.vi }} 33 | uses: rhysd/action-setup-vim@v1 34 | id: vim 35 | with: 36 | version: stable 37 | neovim: ${{ matrix.vi == 'neovim' }} 38 | 39 | - name: Run tests 40 | timeout-minutes: 5 41 | env: 42 | THEMIS_VIM: ${{ steps.vim.outputs.executable }} 43 | run: ./vim-themis/bin/themis ./test/test.vim 44 | post-test: 45 | name: All tests passed 46 | runs-on: ubuntu-22.04 47 | needs: test 48 | steps: 49 | - run: echo ok 50 | -------------------------------------------------------------------------------- /.github/workflows/vint.yml: -------------------------------------------------------------------------------- 1 | name: Reviewdog 2 | on: [pull_request] 3 | jobs: 4 | lint: 5 | name: Lint 6 | runs-on: ubuntu-20.04 7 | steps: 8 | - uses: actions/checkout@v2 9 | - name: vint 10 | uses: reviewdog/action-vint@v1 11 | with: 12 | github_token: ${{ secrets.GITHUB_TOKEN }} 13 | reporter: github-pr-review 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | **/pack 2 | **/plugged 3 | **/*.log 4 | **/tags 5 | **/*.png 6 | **/data 7 | -------------------------------------------------------------------------------- /.vintrc.yml: -------------------------------------------------------------------------------- 1 | cmdargs: 2 | verbose: no 3 | severity: style_problem 4 | error-limit: 50 5 | stat: no 6 | color: no 7 | json: no 8 | env: 9 | neovim: no 10 | stdin_display_name: stdin 11 | 12 | policies: 13 | ProhibitUnusedVariable: 14 | enabled: no 15 | ProhibitImplicitScopeVariable: 16 | enabled: no 17 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:20.04 2 | 3 | # Install Vim 4 | RUN apt-get update && apt-get install -y vim git curl \ 5 | && apt-get clean && rm -rf /var/lib/apt/lists/* 6 | 7 | # Copy vim-jetpack 8 | COPY . /root/.vim/pack/jetpack/start/vim-jetpack 9 | 10 | # Set working directory 11 | WORKDIR /root/.vim/pack/jetpack/start/vim-jetpack 12 | 13 | # Install thinca/vim-themis 14 | RUN git -C /opt clone --depth=1 https://github.com/thinca/vim-themis 15 | ENV PATH=/opt/vim-themis/bin:$PATH 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 TANIGUCHI Masaya 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.adoc: -------------------------------------------------------------------------------- 1 | // Document Settings 2 | :imagesdir: . 3 | ifndef::env-github[:icons: font] 4 | ifdef::env-github[] 5 | :caution-caption: :fire: 6 | :important-caption: :exclamation: 7 | :note-caption: :paperclip: 8 | :tip-caption: :bulb: 9 | :warning-caption: :warning: 10 | endif::[] 11 | 12 | = Jetpack.vim 13 | 14 | Jetpack.vim is a plugin manager for https://vim.org[Vim], https://neovim.io[Neovim], and https://github.com/terrychou/iVim[iVim]. 15 | 16 | The **lightning-fast** minimalist plugin manager for Vim/ Neovim. vim-jetpack is 17 | a jetpack for the most of vimmers. Unbelievably, it is faster than managing 18 | plugins via vimrc with the built-in plugin manager only. 19 | 20 | image::https://user-images.githubusercontent.com/5019902/154419764-d246c45c-8940-4e60-9658-9ed3424cbeaa.gif[] 21 | 22 | == Features 23 | 24 | * Single file installation 25 | ** You just need to download a single file and put it on the runtimepath. 26 | * First-class Lua support 27 | ** This plugin is not written in Lua but we provide a Lua API. 28 | * Fancy User Interface 29 | ** You can see a progress of the installation with a graphical progress bar. 30 | * `pack/*/start` -free installation 31 | ** You can install plugins without `pack/*/start` directory. 32 | * Git-free installation 33 | ** You can optionally install plugins without git. 34 | 35 | == Installation 36 | 37 | The installation is very simple. 38 | You just need to download a single file and put it on the runtimepath. 39 | 40 | [source] 41 | ---- 42 | https://raw.githubusercontent.com/tani/vim-jetpack/master/plugin/jetpack.vim 43 | ---- 44 | 45 | The following is an example of installation with cURL command. 46 | 47 | .Vim for Linux and macOS 48 | [%collapsible] 49 | ==== 50 | [source] 51 | ---- 52 | curl -fLo ~/.vim/pack/jetpack/opt/vim-jetpack/plugin/jetpack.vim --create-dirs https://raw.githubusercontent.com/tani/vim-jetpack/master/plugin/jetpack.vim 53 | ---- 54 | ==== 55 | 56 | .Neovim for Linux and macOS 57 | [%collapsible] 58 | ==== 59 | [source] 60 | ---- 61 | curl -fLo ~/.local/share/nvim/site/pack/jetpack/opt/vim-jetpack/plugin/jetpack.vim --create-dirs https://raw.githubusercontent.com/tani/vim-jetpack/master/plugin/jetpack.vim 62 | ---- 63 | ==== 64 | 65 | .Vim for Windows 66 | [%collapsible] 67 | ==== 68 | [source] 69 | ---- 70 | curl -fLo %USERPROFILE%\vimfiles\pack\jetpack\opt\vim-jetpack\plugin\jetpack.vim --create-dirs https://raw.githubusercontent.com/tani/vim-jetpack/master/plugin/jetpack.vim 71 | ---- 72 | ==== 73 | 74 | .Neovim for Windows 75 | [%collapsible] 76 | ==== 77 | [source] 78 | ---- 79 | curl -fLo %USERPROFILE%\AppData\Local\nvim-data\site\pack\jetpack\opt\vim-jetpack\plugin\jetpack.vim --create-dirs https://raw.githubusercontent.com/tani/vim-jetpack/master/plugin/jetpack.vim 80 | ---- 81 | ==== 82 | 83 | TIP: There exists an option for automatic installation on startup. 84 | Fore more details, see Configuration Snippets. 85 | 86 | == Usage 87 | 88 | Jetpack was initially implemented as a successor of **vim-plug**. 89 | Nowadays, we also provide compatibility layers for other plugin managers: 90 | **dein.vim**, **packer.nvim.nvim**, and **paq.nvim.nvim**. 91 | You can use Jetpack as a drop-in replacement for any of them. 92 | In personal opinion, the vim-plug style is the most stable and easy to use. 93 | 94 | === Vim-plug style 95 | 96 | [source, vim] 97 | ---- 98 | packadd vim-jetpack 99 | call jetpack#begin() 100 | Jetpack 'tani/vim-jetpack', {'opt': 1} "bootstrap 101 | Jetpack 'https://github.com/dense-analysis/ale' 102 | Jetpack 'junegunn/fzf.vim' 103 | Jetpack 'junegunn/fzf', { 'do': {-> fzf#install()} } 104 | Jetpack 'neoclide/coc.nvim', { 'branch': 'release' } 105 | Jetpack 'neoclide/coc.nvim', { 'branch': 'master', 'do': 'yarn install --frozen-lockfile' } 106 | Jetpack 'vlime/vlime', { 'rtp': 'vim' } 107 | Jetpack 'dracula/vim', { 'as': 'dracula' } 108 | Jetpack 'tpope/vim-fireplace', { 'for': 'clojure' } 109 | call jetpack#end() 110 | ---- 111 | 112 | === Dein.vim style 113 | 114 | [source, vim] 115 | ---- 116 | packadd vim-jetpack 117 | call jetpack#begin() 118 | call jetpack#load_toml('path/to/dein.toml') 119 | call jetpack#add('tani/vim-jetpack', {'opt': 1}) "bootstrap 120 | call jetpack#add('https://github.com/dense-analysis/ale') 121 | call jetpack#add('junegunn/fzf.vim') 122 | call jetpack#add('junegunn/fzf', { 'build': './install' }) 123 | call jetpack#add('neoclide/coc.nvim', { 'branch': 'release' }) 124 | call jetpack#add('neoclide/coc.nvim', { 'branch': 'master', 'build': 'yarn install --frozen-lockfile' }) 125 | call jetpack#add('vlime/vlime', { 'rtp': 'vim' }) 126 | call jetpack#add('dracula/vim', { 'name': 'dracula' }) 127 | call jetpack#add('tpope/vim-fireplace', { 'on_ft': 'clojure' }) 128 | call jetpack#end() 129 | ---- 130 | 131 | [source, toml] 132 | ---- 133 | [[plugins]] 134 | repo = 'tani/vim-jetpack' 135 | 136 | [[plugins]] 137 | repo = 'tpope/vim-fugitive' 138 | on_cmd = 'Git' 139 | hook_source = ''' 140 | let g:fugitive_no_mappings = 1 141 | ''' 142 | hook_post_source = ''' 143 | echom 'fugitive is loaded' 144 | ''' 145 | ---- 146 | 147 | === Legacy packer.nvim style 148 | 149 | [source, lua] 150 | ---- 151 | -- Packer.nvim v1 152 | vim.cmd('packadd vim-jetpack') 153 | require('jetpack.packer').startup(function(use) 154 | use { 'tani/vim-jetpack' } -- bootstrap 155 | use 'https://github.com/dense-analysis/ale' 156 | use 'junegunn/fzf.vim' 157 | use {'junegunn/fzf', run = 'call fzf#install()' } 158 | use {'neoclide/coc.nvim', branch = 'release'} 159 | use {'neoclide/coc.nvim', branch = 'master', run = 'yarn install --frozen-lockfile'} 160 | use {'vlime/vlime', rtp = 'vim' } 161 | use {'dracula/vim', as = 'dracula' } 162 | use {'tpope/vim-fireplace', ft = 'clojure' }, 163 | use {'nvim-treesitter/nvim-treesitter', 164 | run = ':TSUpdate', 165 | config = function() 166 | require'nvim-treesitter.configs'.setup { 167 | ensure_installed = 'maintained', 168 | highlight = { enable = true } 169 | } 170 | end 171 | } 172 | end) 173 | ---- 174 | 175 | === Modern packer.nvim/ paq.nvim style 176 | 177 | [source, lua] 178 | ---- 179 | -- Packer.nvim v2 180 | vim.cmd('packadd vim-jetpack') 181 | require('jetpack.packer').add { 182 | {'tani/vim-jetpack'}, -- bootstrap 183 | 'https://github.com/dense-analysis/ale', 184 | 'junegunn/fzf.vim', 185 | {'junegunn/fzf', run = 'call fzf#install()' }, 186 | {'neoclide/coc.nvim', branch = 'release'}, 187 | {'neoclide/coc.nvim', branch = 'master', run = 'yarn install --frozen-lockfile'}, 188 | {'vlime/vlime', rtp = 'vim' }, 189 | {'dracula/vim', as = 'dracula' }, 190 | {'tpope/vim-fireplace', ft = 'clojure' }, 191 | {'nvim-treesitter/nvim-treesitter', 192 | run = ':TSUpdate', 193 | config = function() 194 | require'nvim-treesitter.configs'.setup { 195 | ensure_installed = 'maintained', 196 | highlight = { enable = true } 197 | } 198 | end 199 | } 200 | } 201 | ---- 202 | 203 | [source, lua] 204 | ---- 205 | -- Paq.nvim 206 | vim.cmd('packadd vim-jetpack') 207 | require('jetpack.paq') { 208 | {'tani/vim-jetpack'}, -- bootstrap 209 | 'https://github.com/dense-analysis/ale', 210 | 'junegunn/fzf.vim', 211 | {'junegunn/fzf', run = './install' }, 212 | {'neoclide/coc.nvim', branch = 'release'}, 213 | {'neoclide/coc.nvim', branch = 'master', run = 'yarn install --frozen-lockfile'}, 214 | {'dracula/vim', as = 'dracula' }, 215 | } 216 | ---- 217 | 218 | == Configuration 219 | 220 | === Configuration Variables 221 | 222 | We provide configuration variables to change the internal behaviors: 223 | copying files, downloading plugins. 224 | 225 | `g:jetpack_download_method`:: 226 | Jetpack downloads plugins with git by default. 227 | Further, Jetpack can download plugins with git, cURL, and wget 228 | This is useful for environments without git. 229 | Non-git options are not recommended because they are slower than git 230 | as jeptack downloads the whole repository as a tar archive. 231 | 232 | [TIP] 233 | .iVim does not have git command. 234 | ==== 235 | We recommend the following configuration to avoid 236 | the external commands except cURL. 237 | [source, vim] 238 | ---- 239 | let g:jetpack_download_method = 'curl' 240 | ---- 241 | ==== 242 | 243 | === Configuration Parameters 244 | 245 | CAUTION: While it is possible to use other, unlisted configuration parameters, 246 | such as `on_cmd` in vim-plug style, for example, but as they might be removed in 247 | the future, we recommend you not to use unlisted configuration parameters. 248 | 249 | ==== Vim-plug style 250 | 251 | You can pass the configuration parameters to `Jetpack` command as follows. 252 | 253 | [source, vim] 254 | ---- 255 | Jetpack 'tani/vim-jetpack', { 'as': 'jetpack' } 256 | ---- 257 | 258 | The following is a list of configuration parameters for vim-plug style. 259 | 260 | [%autowidth] 261 | |=== 262 | |Parameter|Type|Description 263 | 264 | |`on` 265 | |`string` or `array` 266 | | On-demand loading plugins by commands, keymaps. 267 | 268 | |`for` 269 | |`string` or `array` 270 | | On-demand loading plugins by filetypes. 271 | 272 | |`branch` 273 | |`string` 274 | | Install plugins from the specified branch. 275 | 276 | |`commit` 277 | |`string` 278 | | Install plugins from the specified commit. 279 | 280 | |`tag` 281 | |`string` 282 | | Install plugins from the specified tag. 283 | 284 | |`rtp` 285 | |`string` 286 | | Add the specified directory to the runtimepath. 287 | 288 | |`do` 289 | |`string` or `function` 290 | | Execute the specified command after installation. 291 | 292 | |`as` 293 | |`string` 294 | | Install plugins as the specified name. 295 | 296 | |`dir` 297 | |`string` 298 | | Install plugins to the specified directory. 299 | 300 | |`frozen` 301 | |`boolean` 302 | | Freeze plugins to the current version. 303 | |=== 304 | 305 | NOTE: Jetpack mechanically distinguishes `:SomeCommand` and `(some-command)`, and the external command, for `on` parameters and `do` parameters. 306 | 307 | ==== Dein.vim style 308 | 309 | You can pass the configuration parameters to `jetpack#add` function as follows. 310 | 311 | [source, vim] 312 | ---- 313 | call jetpack#add('tani/vim-jetpack', { 'name': 'jetpack' }) 314 | ---- 315 | 316 | The following is a list of configuration parameters for dein.vim style. 317 | Note that we do not support full features of dein.vim. 318 | We are welcome to your pull requests to improve the compatibility. 319 | 320 | [%autowidth] 321 | |=== 322 | |Parameter|Type|Description 323 | 324 | |`on_cmd` 325 | |`string` or `array` 326 | | On-demand loading plugins by commands. 327 | 328 | |`on_ft` 329 | |`string` or `array` 330 | | On-demand loading plugins by filetypes. 331 | 332 | |`on_map` 333 | |`string` or `array` 334 | | On-demand loading plugins by keymaps. 335 | 336 | |`on_event` 337 | |`string` or `array` 338 | | On-demand loading plugins by events. 339 | 340 | |`branch` 341 | |`string` 342 | | Install plugins from the specified branch. 343 | 344 | |`commit` 345 | |`string` 346 | | Install plugins from the specified commit. 347 | 348 | |`tag` 349 | |`string` 350 | | Install plugins from the specified tag. 351 | 352 | |`path` 353 | |`string` 354 | | Install plugins to the specified directory. 355 | 356 | |`rtp` 357 | |`string` 358 | | Add the specified directory to the runtimepath. 359 | 360 | |`build` 361 | |`string` 362 | | Execute the specified external command after installation. 363 | 364 | |`name` 365 | |`string` 366 | | Install plugins as the specified name. 367 | 368 | |`merged` 369 | |`boolean` 370 | | Merge plugins to the current runtimepath. 371 | 372 | |`frozen` 373 | |`boolean` 374 | | Freeze plugins to the current version. 375 | 376 | |`depends` 377 | |`string` or `array` 378 | | Load the specified plugins before the plugin. 379 | 380 | |`on_source` 381 | |`string` or `array` 382 | | Load the plugin before the specified plugins. 383 | 384 | |`on_post_source` 385 | |`string` or `array` 386 | | Load the plugin after the specified plugins. 387 | 388 | |`hook_add` 389 | |`string` 390 | | Execute the specified Vim script at the end of `jetpack#add` function. 391 | 392 | |`hook_source` 393 | |`string` 394 | | Execute the specified Vim script before loading the plugin. 395 | 396 | |`hook_post_source` 397 | |`string` 398 | | Execute the specified Vim script after loading the plugin. 399 | |=== 400 | 401 | ==== Legacy packer.nvim style 402 | 403 | You can pass the configuration parameters to `use` function as follows. 404 | 405 | [source, lua] 406 | ---- 407 | use { 'tani/vim-jetpack', as = 'jetpack' } 408 | ---- 409 | 410 | Note that we do not support full features of packer.nvim. 411 | We are welcome to your pull requests to improve the compatibility. 412 | 413 | [%autowidth] 414 | |=== 415 | |Parameter|Type|Description 416 | 417 | |`opt` 418 | |`boolean` 419 | | On-demand loading plugins by `packadd` 420 | 421 | |`cmd` 422 | |`string` or `array` 423 | | On-demand loading plugins by commands. 424 | 425 | |`keys` 426 | |`string` or `array` 427 | | On-demand loading plugins by keymaps. 428 | 429 | |`event` 430 | |`string` or `array` 431 | | On-demand loading plugins by event. 432 | 433 | |`ft` 434 | |`string` or `array` 435 | | On-demand loading plugins by filetypes. 436 | 437 | |`branch` 438 | |`string` 439 | | Install plugins from the specified branch. 440 | 441 | |`commit` 442 | |`string` 443 | | Install plugins from the specified commit. 444 | 445 | |`tag` 446 | |`string` 447 | | Install plugins from the specified tag. 448 | 449 | |`rtp` 450 | |`string` 451 | | Add the specified directory to the runtimepath. 452 | 453 | |`run` 454 | |`string` or `function` 455 | | Execute the specified command after installation. 456 | 457 | |`as` 458 | |`string` 459 | | Install plugins as the specified name. 460 | 461 | |`requires` 462 | |`string` or `array` 463 | | Enable the plugin after the specified plugins. 464 | 465 | |`after` 466 | |`string` or `array` 467 | | Enable the plugin after the specified plugins. 468 | 469 | |`before` 470 | |`string` or `array` 471 | | Enable the plugin before the specified plugins. 472 | 473 | |`lock` 474 | |`boolean` 475 | | Freeze plugins to the current version. 476 | 477 | |`config` 478 | |`function` or `string` 479 | | Execute the specified function after startup. 480 | 481 | |`setup` 482 | |`function` or `string` 483 | | Execute the specified function before startup. 484 | |=== 485 | 486 | CAUTION: Note that we do not install the specified plugins automatically. 487 | You have to declare the specified plugins by `use` function. 488 | 489 | ==== Modern packer.nvim/ paq.nvim style 490 | 491 | WARNING: Modern packer.nvim style is still experimental. 492 | The configuration parameters might be changed in the future. 493 | 494 | You can pass the configuration parameters to a table as follows. 495 | 496 | [source, lua] 497 | ---- 498 | { 'tani/vim-jetpack', as = 'jetpack' } 499 | ---- 500 | 501 | Note that we do not support full features of packer.nvim. 502 | We are welcome to your pull requests to improve the compatibility. 503 | 504 | [%autowidth] 505 | |=== 506 | |Parameter|Type|Description 507 | 508 | |`opt` 509 | |`boolean` 510 | | On-demand loading plugins by `packadd` 511 | 512 | |`cmd` 513 | |`string` or `array` 514 | | On-demand loading plugins by commands. 515 | 516 | |`keys` 517 | |`string` or `array` 518 | | On-demand loading plugins by keymaps. 519 | 520 | |`event` 521 | |`string` or `array` 522 | | On-demand loading plugins by event. 523 | 524 | |`ft` 525 | |`string` or `array` 526 | | On-demand loading plugins by filetypes. 527 | 528 | |`branch` 529 | |`string` 530 | | Install plugins from the specified branch. 531 | 532 | |`commit` 533 | |`string` 534 | | Install plugins from the specified commit. 535 | 536 | |`tag` 537 | |`string` 538 | | Install plugins from the specified tag. 539 | 540 | |`rtp` 541 | |`string` 542 | | Add the specified directory to the runtimepath. 543 | 544 | |`run` 545 | |`string` or `function` 546 | | Execute the specified command after installation. 547 | 548 | |`as` 549 | |`string` 550 | | Install plugins as the specified name. 551 | 552 | |`requires` 553 | |`string` 554 | | Install plugins after the specified plugins. 555 | 556 | |`lock` 557 | |`boolean` 558 | | Freeze plugins to the current version. 559 | 560 | |`config` 561 | |`function` or `string` 562 | | Execute the specified function after startup. 563 | 564 | |`setup` 565 | |`function` or `string` 566 | | Execute the specified function before startup. 567 | |=== 568 | 569 | === Configuration Snippets 570 | 571 | ==== Automatic installation on startup 572 | 573 | .Vim 574 | [source, vim] 575 | ---- 576 | let s:jetpackfile = expand(':p:h') .. '/pack/jetpack/opt/vim-jetpack/plugin/jetpack.vim' 577 | let s:jetpackurl = "https://raw.githubusercontent.com/tani/vim-jetpack/master/plugin/jetpack.vim" 578 | if !filereadable(s:jetpackfile) 579 | call system(printf('curl -fsSLo %s --create-dirs %s', s:jetpackfile, s:jetpackurl)) 580 | endif 581 | ---- 582 | 583 | .Neovim 584 | [source, vim] 585 | ---- 586 | "neovim + vim 587 | let s:jetpackfile = stdpath('data') .. '/site/pack/jetpack/opt/vim-jetpack/plugin/jetpack.vim' 588 | let s:jetpackurl = "https://raw.githubusercontent.com/tani/vim-jetpack/master/plugin/jetpack.vim" 589 | if !filereadable(s:jetpackfile) 590 | call system(printf('curl -fsSLo %s --create-dirs %s', s:jetpackfile, s:jetpackurl)) 591 | endif 592 | ---- 593 | 594 | .Neovim with Lua 595 | [source, lua] 596 | ---- 597 | local jetpackfile = vim.fn.stdpath('data') .. '/site/pack/jetpack/opt/vim-jetpack/plugin/jetpack.vim' 598 | local jetpackurl = "https://raw.githubusercontent.com/tani/vim-jetpack/master/plugin/jetpack.vim" 599 | if vim.fn.filereadable(jetpackfile) == 0 then 600 | vim.fn.system(string.format('curl -fsSLo %s --create-dirs %s', jetpackfile, jetpackurl)) 601 | end 602 | ---- 603 | 604 | ==== Automatic plugin installation on startup 605 | 606 | .Vim and Neovim 607 | [source, vim] 608 | ---- 609 | for name in jetpack#names() 610 | if !jetpack#tap(name) 611 | call jetpack#sync() 612 | break 613 | endif 614 | endfor 615 | ---- 616 | 617 | .Neovim with Lua 618 | [source, lua] 619 | ---- 620 | local jetpack = require('jetpack') 621 | for _, name in ipairs(jetpack.names()) do 622 | if not jetpack.tap(name) then 623 | jetpack.sync() 624 | break 625 | end 626 | end 627 | ---- 628 | 629 | ==== Build cache of treesitter parsrs 630 | 631 | [source, lua] 632 | ---- 633 | local parser_install_dir = vim.fn.stdpath "data" .. "/treesitter" 634 | vim.opt.runtimepath:append(parser_install_dir) 635 | 636 | require("nvim-treesitter.configs").setup { 637 | parser_install_dir = parser_install_dir, 638 | ... 639 | } 640 | ---- 641 | 642 | == API 643 | 644 | CAUTION: You might be able to use other functions, commands, and events. 645 | but it might be going to be removed in the future. 646 | Thus, we recommend you not to use unlisted ones. 647 | 648 | === VimL Function 649 | 650 | * `jetpack#begin([path])` 651 | ** The function setups jetpack plugins. All plugin declarations should be 652 | placed after this function. You can give `path` if you want to use another 653 | directory to manage plugins. 654 | * `jetpack#add(repo [, options])` 655 | ** repo is a pair of string concatenated with `/` such as `tani/vim-jetpack`. 656 | `options` is a dictionary. See below. 657 | * `jetpack#sync()` 658 | ** The function performs to install, update, and bundle all plugins. 659 | The function is everything all you need to know. 660 | You must run this function after a change of your configuration. 661 | * `jetpack#end()` 662 | ** The function loads declared plugins. All plugin declarations should be 663 | placed before this function. 664 | * `jetpack#tap(name)` 665 | ** It returns a truthy value if the plugin is available, 666 | otherwise it returns a falsy value. 667 | * `jetpack#names()` 668 | ** It returns the list of plugin names registered including unavailable 669 | plugins. 670 | * `jetpack#get(name)` 671 | ** It returns metadata of the plugin if possible, otherwise it returns `{}`. 672 | This is the same as `dein#get` of `dein.vim`. 673 | * `jetpack#load(name)` 674 | ** This is a wrapper function for `packadd`; since it fires config options, 675 | etc., it is recommended to use this instead of `packadd`. 676 | * `jetpack#load_toml(path)` 677 | ** This function load dein-style toml settings. 678 | 679 | === Lua Function 680 | 681 | All `jetpack#` functions are exported as `jetpack` module. 682 | You can call them using `require('jetpack')` as you want. 683 | Additionally, functions compatible with packer.nvim and paq.nvim are available. 684 | 685 | * `require('jetpack.paq')(config)` 686 | ** This function loads plugins described in config like `paq.nvim`. 687 | * `require('jetpack.packer').startup(config)` 688 | ** This function loads plugins described by `use` function like `packer.nvim`. 689 | * `require('jetpack.packer').add(config)` 690 | ** This function loads plugins described by `use` function like `packer.nvim`. 691 | * `require('jetpack.packer').init(option)` 692 | ** Now supported option is only `package_root`. 693 | 694 | === Commands 695 | 696 | * `:JetpackSync` 697 | ** The function performs to install, update, and bundle all plugins. 698 | The function is everything all you need to know. 699 | You must run this function after a change of your configuration. 700 | 701 | * `:Jetpack repo [, options]` 702 | ** A command version of `jetpack#add()`. 703 | It is useful for the vim-plug style declaration of plugins in vimrc. 704 | 705 | === Events 706 | 707 | * `User JetpackPre:{plugin-name}`/ `User JetpackPost:{plugin-name}` 708 | ** Let {plugin-name} be the name of the plugin. 709 | The specified event is fired before/after the plugin is loaded. 710 | 711 | * `User Jetpack{PluginName}Pre`/ `User Jetpack{PluginName}Post` 712 | ** Let {PluginName} be a CamelCase of plugin name. 713 | The specified event is fired before/after the plugin is loaded. 714 | + 715 | [%autowidth] 716 | |=== 717 | | plugin-name | EventName 718 | 719 | | vim-jetpack | VimJetpack 720 | 721 | | goyo.vim | GoyoVim 722 | 723 | | vim_foo | VimFoo 724 | |=== 725 | 726 | 727 | === Autocmd Groups 728 | 729 | * `Jetpack` 730 | ** vim-jetpack's lazy loading system uses autocommands 731 | defined under `Jetpack` autocmd-group. 732 | 733 | == Contributing 734 | 735 | == License 736 | 737 | Copyright (c) 2022 -- 2025 Masaya Taniguchi 738 | 739 | The software is released under the MIT License, 740 | see the header of the source code. 741 | -------------------------------------------------------------------------------- /benchmark/Makefile: -------------------------------------------------------------------------------- 1 | all: run 2 | 3 | setup: 4 | ./setup.py 5 | 6 | run: setup 7 | ./run.py 8 | 9 | stat: run 10 | ./stat.py 11 | 12 | .PHONY: clean 13 | 14 | clean: 15 | rm -rf ./*.log ./*.png pack plugged 16 | 17 | profile: 18 | nvim -u jetpack.vim --startuptime startup.log --cmd 'profile start profile.log' --cmd 'profile func *' --cmd 'profile file *' --cmd 'profile! func *' --cmd 'profile! file *' -c 'prorfile pause' -c 'noautocmd qall' 19 | -------------------------------------------------------------------------------- /benchmark/jetpack.vim: -------------------------------------------------------------------------------- 1 | set packpath= 2 | execute 'set runtimepath-=' . stdpath('config') 3 | call execute(printf('source %s/plugin/jetpack.vim', expand(':p:h'))) 4 | 5 | function s:fallback(val, default) 6 | return empty(a:val) ? a:default : a:val 7 | endfunction 8 | let g:jetpack_copy_method = s:fallback(getenv('JETPACK_COPY_METHOD'), 'system') 9 | let g:jetpack_download_method = s:fallback(getenv('JETPACK_DOWNLOAD_METHOD'), 'git') 10 | 11 | let g:vimhome = substitute(expand(':p:h'), '\', '/', 'g') 12 | let s:optdir = g:vimhome . '/pack/jetpack/opt' 13 | let s:srcdir = g:vimhome . '/pack/jetpack/src' 14 | 15 | call jetpack#begin(g:vimhome) 16 | Jetpack 'preservim/nerdtree' 17 | Jetpack 'vim-airline/vim-airline' 18 | Jetpack 'dense-analysis/ale' 19 | Jetpack 'vim-syntastic/syntastic' 20 | Jetpack 'codota/TabNine' 21 | Jetpack 'vimwiki/vimwiki' 22 | Jetpack 'easymotion/vim-easymotion' 23 | Jetpack 'itchyny/lightline.vim' 24 | Jetpack 'mhinz/vim-startify' 25 | Jetpack 'preservim/nerdcommenter' 26 | Jetpack 'lervag/vimtex' 27 | Jetpack 'Yggdroot/indentLine' 28 | Jetpack 'neomake/neomake' 29 | Jetpack 'mhinz/vim-signify' 30 | Jetpack 'tpope/vim-dispatch' 31 | Jetpack 'tpope/vim-fugitive' 32 | Jetpack 'tpope/vim-surround' 33 | Jetpack 'tpope/vim-repeat' 34 | Jetpack 'tpope/vim-unimpaired' 35 | Jetpack 'tpope/vim-rhubarb' 36 | Jetpack 'wellle/targets.vim' 37 | Jetpack 'lukas-reineke/indent-blankline.nvim' 38 | Jetpack 'soywod/himalaya' 39 | Jetpack 'preservim/vimux' 40 | Jetpack 'Xuyuanp/nerdtree-git-plugin' 41 | Jetpack 'liuchengxu/vim-clap' 42 | Jetpack 'dhruvasagar/vim-table-mode' 43 | Jetpack 'liuchengxu/vim-which-key' 44 | Jetpack 'AndrewRadev/splitjoin.vim' 45 | Jetpack 'liuchengxu/vista.vim' 46 | Jetpack 'skywind3000/asyncrun.vim' 47 | Jetpack 'luochen1990/rainbow' 48 | Jetpack 'preservim/vim-pencil' 49 | Jetpack 'tomtom/tcomment_vim' 50 | Jetpack 'jreybert/vimagit' 51 | Jetpack 'andymass/vim-matchup' 52 | Jetpack 'glepnir/dashboard-nvim' 53 | Jetpack 'unblevable/quick-scope' 54 | Jetpack 'maralla/completor.vim' 55 | Jetpack 'rhysd/git-messenger.vim' 56 | Jetpack 'mhinz/vim-grepper' 57 | Jetpack 'haya14busa/incsearch.vim' 58 | Jetpack 'matze/vim-move' 59 | "Jetpack 'wellle/context.vim' 60 | Jetpack 'rhysd/vim-grammarous' 61 | Jetpack 'jacoborus/tender.vim' 62 | Jetpack 'rhysd/vim-clang-format' 63 | Jetpack 'skywind3000/vim-quickui' 64 | Jetpack 'rhysd/clever-f.vim' 65 | Jetpack 'gelguy/wilder.nvim' 66 | Jetpack 'romainl/Apprentice' 67 | Jetpack 'ackyshake/VimCompletesMe' 68 | Jetpack 'skywind3000/asynctasks.vim' 69 | Jetpack 'thinca/vim-quickrun' 70 | Jetpack 'pearofducks/ansible-vim' 71 | Jetpack 'srcery-colors/srcery-vim' 72 | Jetpack 'preservim/vim-wordy' 73 | Jetpack 'JuliaEditorSupport/julia-vim' 74 | Jetpack 'hrsh7th/vim-vsnip' 75 | Jetpack 'pineapplegiant/spaceduck' 76 | Jetpack 'markonm/traces.vim' 77 | Jetpack 'slashmili/alchemist.vim' 78 | Jetpack 'rhysd/committia.vim' 79 | Jetpack 'jamessan/vim-gnupg' 80 | Jetpack 'drewtempelmeyer/palenight.vim' 81 | Jetpack 'mcchrish/nnn.vim' 82 | Jetpack 'euclio/vim-markdown-composer' 83 | Jetpack 'APZelos/blamer.nvim' 84 | Jetpack 'AndrewRadev/switch.vim' 85 | Jetpack 'shawncplus/phpcomplete.vim' 86 | Jetpack 'preservim/vim-colors-pencil' 87 | Jetpack 'dodie/vim-disapprove-deep-indentation' 88 | Jetpack 'romainl/vim-qf' 89 | Jetpack 'mzlogin/vim-markdown-toc' 90 | Jetpack 'itchyny/vim-cursorword' 91 | Jetpack 'wellle/tmux-complete.vim' 92 | Jetpack 'liquidz/vim-iced' 93 | Jetpack 'Rigellute/rigel' 94 | Jetpack 'arithran/vim-pizza' 95 | Jetpack 'AndrewRadev/sideways.vim' 96 | Jetpack 'camspiers/lens.vim' 97 | Jetpack 'wesQ3/vim-windowswap' 98 | Jetpack 'SidOfc/mkdx' 99 | Jetpack '907th/vim-auto-save' 100 | Jetpack 'thiagoalessio/rainbow_levels.vim' 101 | Jetpack 'ojroques/vim-oscyank' 102 | Jetpack 'sillybun/vim-repl' 103 | Jetpack 'WolfgangMehner/vim-plugins' 104 | Jetpack 'jupyter-vim/jupyter-vim' 105 | Jetpack 'bagrat/vim-buffet' 106 | Jetpack 'romainl/vim-cool' 107 | Jetpack 'vim-crystal/vim-crystal' 108 | Jetpack 'tmsvg/pear-tree' 109 | Jetpack 'lukas-reineke/lsp-format.nvim' 110 | Jetpack 'ctjhoa/spacevim' 111 | Jetpack 'aperezdc/vim-template' 112 | Jetpack 'lfv89/vim-interestingwords' 113 | Jetpack 'kristijanhusak/vim-dadbod-completion' 114 | Jetpack 'whiteinge/diffconflicts' 115 | Jetpack 'AndrewRadev/tagalong.vim' 116 | call jetpack#end() 117 | 118 | execute 'autocmd SourcePost' expand(':p:h') 'echomsg "loaded"' 119 | -------------------------------------------------------------------------------- /benchmark/lua.vim: -------------------------------------------------------------------------------- 1 | set packpath= 2 | execute 'set runtimepath-=' . expand('~/.vim') 3 | call execute(printf('source %s/plugin/jetpack.vim', expand(':p:h'))) 4 | 5 | function s:fallback(val, default) 6 | return empty(a:val) ? a:default : a:val 7 | endfunction 8 | let g:jetpack_copy_method = s:fallback(getenv('JETPACK_COPY_METHOD'), 'system') 9 | let g:jetpack_download_method = s:fallback(getenv('JETPACK_DOWNLOAD_METHOD'), 'git') 10 | 11 | let g:vimhome = substitute(expand(':p:h'), '\', '/', 'g') 12 | let s:optdir = g:vimhome . '/pack/jetpack/opt' 13 | let s:srcdir = g:vimhome . '/pack/jetpack/src' 14 | 15 | lua<:p:h'))) 4 | 5 | let g:vimhome = substitute(expand(':p:h'), '\', '/', 'g') 6 | 7 | call plug#begin(g:vimhome .. '/plugged') 8 | Plug 'preservim/nerdtree' 9 | Plug 'vim-airline/vim-airline' 10 | Plug 'dense-analysis/ale' 11 | Plug 'vim-syntastic/syntastic' 12 | Plug 'codota/TabNine' 13 | Plug 'vimwiki/vimwiki' 14 | Plug 'easymotion/vim-easymotion' 15 | Plug 'itchyny/lightline.vim' 16 | Plug 'mhinz/vim-startify' 17 | Plug 'preservim/nerdcommenter' 18 | Plug 'lervag/vimtex' 19 | Plug 'Yggdroot/indentLine' 20 | Plug 'neomake/neomake' 21 | Plug 'mhinz/vim-signify' 22 | Plug 'tpope/vim-dispatch' 23 | Plug 'tpope/vim-fugitive' 24 | Plug 'tpope/vim-surround' 25 | Plug 'tpope/vim-repeat' 26 | Plug 'tpope/vim-unimpaired' 27 | Plug 'tpope/vim-rhubarb' 28 | Plug 'wellle/targets.vim' 29 | Plug 'lukas-reineke/indent-blankline.nvim' 30 | Plug 'soywod/himalaya' 31 | Plug 'preservim/vimux' 32 | Plug 'Xuyuanp/nerdtree-git-plugin' 33 | Plug 'liuchengxu/vim-clap' 34 | Plug 'dhruvasagar/vim-table-mode' 35 | Plug 'liuchengxu/vim-which-key' 36 | Plug 'AndrewRadev/splitjoin.vim' 37 | Plug 'liuchengxu/vista.vim' 38 | Plug 'skywind3000/asyncrun.vim' 39 | Plug 'luochen1990/rainbow' 40 | Plug 'preservim/vim-pencil' 41 | Plug 'tomtom/tcomment_vim' 42 | Plug 'jreybert/vimagit' 43 | Plug 'andymass/vim-matchup' 44 | Plug 'glepnir/dashboard-nvim' 45 | Plug 'unblevable/quick-scope' 46 | Plug 'maralla/completor.vim' 47 | Plug 'rhysd/git-messenger.vim' 48 | Plug 'mhinz/vim-grepper' 49 | Plug 'haya14busa/incsearch.vim' 50 | Plug 'matze/vim-move' 51 | Plug 'rhysd/vim-grammarous' 52 | Plug 'jacoborus/tender.vim' 53 | Plug 'rhysd/vim-clang-format' 54 | Plug 'skywind3000/vim-quickui' 55 | Plug 'rhysd/clever-f.vim' 56 | Plug 'gelguy/wilder.nvim' 57 | Plug 'romainl/Apprentice' 58 | Plug 'ackyshake/VimCompletesMe' 59 | Plug 'skywind3000/asynctasks.vim' 60 | Plug 'thinca/vim-quickrun' 61 | Plug 'pearofducks/ansible-vim' 62 | Plug 'srcery-colors/srcery-vim' 63 | Plug 'preservim/vim-wordy' 64 | Plug 'JuliaEditorSupport/julia-vim' 65 | Plug 'hrsh7th/vim-vsnip' 66 | Plug 'pineapplegiant/spaceduck' 67 | Plug 'markonm/traces.vim' 68 | Plug 'slashmili/alchemist.vim' 69 | Plug 'rhysd/committia.vim' 70 | Plug 'jamessan/vim-gnupg' 71 | Plug 'drewtempelmeyer/palenight.vim' 72 | Plug 'mcchrish/nnn.vim' 73 | Plug 'euclio/vim-markdown-composer' 74 | Plug 'APZelos/blamer.nvim' 75 | Plug 'AndrewRadev/switch.vim' 76 | Plug 'shawncplus/phpcomplete.vim' 77 | Plug 'preservim/vim-colors-pencil' 78 | Plug 'dodie/vim-disapprove-deep-indentation' 79 | Plug 'romainl/vim-qf' 80 | Plug 'mzlogin/vim-markdown-toc' 81 | Plug 'itchyny/vim-cursorword' 82 | Plug 'wellle/tmux-complete.vim' 83 | Plug 'liquidz/vim-iced' 84 | Plug 'Rigellute/rigel' 85 | Plug 'arithran/vim-pizza' 86 | Plug 'AndrewRadev/sideways.vim' 87 | Plug 'camspiers/lens.vim' 88 | Plug 'wesQ3/vim-windowswap' 89 | Plug 'SidOfc/mkdx' 90 | Plug '907th/vim-auto-save' 91 | Plug 'thiagoalessio/rainbow_levels.vim' 92 | Plug 'ojroques/vim-oscyank' 93 | Plug 'sillybun/vim-repl' 94 | Plug 'WolfgangMehner/vim-plugins' 95 | Plug 'jupyter-vim/jupyter-vim' 96 | Plug 'bagrat/vim-buffet' 97 | Plug 'romainl/vim-cool' 98 | Plug 'vim-crystal/vim-crystal' 99 | Plug 'tmsvg/pear-tree' 100 | Plug 'lukas-reineke/lsp-format.nvim' 101 | Plug 'ctjhoa/spacevim' 102 | Plug 'aperezdc/vim-template' 103 | Plug 'lfv89/vim-interestingwords' 104 | Plug 'kristijanhusak/vim-dadbod-completion' 105 | Plug 'whiteinge/diffconflicts' 106 | Plug 'AndrewRadev/tagalong.vim' 107 | call plug#end() 108 | -------------------------------------------------------------------------------- /benchmark/plugin/jetpack.vim: -------------------------------------------------------------------------------- 1 | ../../plugin/jetpack.vim -------------------------------------------------------------------------------- /benchmark/plugin/plug.vim: -------------------------------------------------------------------------------- 1 | " vim-plug: Vim plugin manager 2 | " ============================ 3 | " 4 | " Download plug.vim and put it in ~/.vim/autoload 5 | " 6 | " curl -fLo ~/.vim/autoload/plug.vim --create-dirs \ 7 | " https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim 8 | " 9 | " Edit your .vimrc 10 | " 11 | " call plug#begin('~/.vim/plugged') 12 | " 13 | " " Make sure you use single quotes 14 | " 15 | " " Shorthand notation; fetches https://github.com/junegunn/vim-easy-align 16 | " Plug 'junegunn/vim-easy-align' 17 | " 18 | " " Any valid git URL is allowed 19 | " Plug 'https://github.com/junegunn/vim-github-dashboard.git' 20 | " 21 | " " Multiple Plug commands can be written in a single line using | separators 22 | " Plug 'SirVer/ultisnips' | Plug 'honza/vim-snippets' 23 | " 24 | " " On-demand loading 25 | " Plug 'scrooloose/nerdtree', { 'on': 'NERDTreeToggle' } 26 | " Plug 'tpope/vim-fireplace', { 'for': 'clojure' } 27 | " 28 | " " Using a non-default branch 29 | " Plug 'rdnetto/YCM-Generator', { 'branch': 'stable' } 30 | " 31 | " " Using a tagged release; wildcard allowed (requires git 1.9.2 or above) 32 | " Plug 'fatih/vim-go', { 'tag': '*' } 33 | " 34 | " " Plugin options 35 | " Plug 'nsf/gocode', { 'tag': 'v.20150303', 'rtp': 'vim' } 36 | " 37 | " " Plugin outside ~/.vim/plugged with post-update hook 38 | " Plug 'junegunn/fzf', { 'dir': '~/.fzf', 'do': './install --all' } 39 | " 40 | " " Unmanaged plugin (manually installed and updated) 41 | " Plug '~/my-prototype-plugin' 42 | " 43 | " " Initialize plugin system 44 | " call plug#end() 45 | " 46 | " Then reload .vimrc and :PlugInstall to install plugins. 47 | " 48 | " Plug options: 49 | " 50 | "| Option | Description | 51 | "| ----------------------- | ------------------------------------------------ | 52 | "| `branch`/`tag`/`commit` | Branch/tag/commit of the repository to use | 53 | "| `rtp` | Subdirectory that contains Vim plugin | 54 | "| `dir` | Custom directory for the plugin | 55 | "| `as` | Use different name for the plugin | 56 | "| `do` | Post-update hook (string or funcref) | 57 | "| `on` | On-demand loading: Commands or ``-mappings | 58 | "| `for` | On-demand loading: File types | 59 | "| `frozen` | Do not update unless explicitly specified | 60 | " 61 | " More information: https://github.com/junegunn/vim-plug 62 | " 63 | " 64 | " Copyright (c) 2017 Junegunn Choi 65 | " 66 | " MIT License 67 | " 68 | " Permission is hereby granted, free of charge, to any person obtaining 69 | " a copy of this software and associated documentation files (the 70 | " "Software"), to deal in the Software without restriction, including 71 | " without limitation the rights to use, copy, modify, merge, publish, 72 | " distribute, sublicense, and/or sell copies of the Software, and to 73 | " permit persons to whom the Software is furnished to do so, subject to 74 | " the following conditions: 75 | " 76 | " The above copyright notice and this permission notice shall be 77 | " included in all copies or substantial portions of the Software. 78 | " 79 | " THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 80 | " EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 81 | " MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 82 | " NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 83 | " LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 84 | " OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 85 | " WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 86 | 87 | if exists('g:loaded_plug') 88 | finish 89 | endif 90 | let g:loaded_plug = 1 91 | 92 | let s:cpo_save = &cpo 93 | set cpo&vim 94 | 95 | let s:plug_src = 'https://github.com/junegunn/vim-plug.git' 96 | let s:plug_tab = get(s:, 'plug_tab', -1) 97 | let s:plug_buf = get(s:, 'plug_buf', -1) 98 | let s:mac_gui = has('gui_macvim') && has('gui_running') 99 | let s:is_win = has('win32') 100 | let s:nvim = has('nvim-0.2') || (has('nvim') && exists('*jobwait') && !s:is_win) 101 | let s:vim8 = has('patch-8.0.0039') && exists('*job_start') 102 | if s:is_win && &shellslash 103 | set noshellslash 104 | let s:me = resolve(expand(':p')) 105 | set shellslash 106 | else 107 | let s:me = resolve(expand(':p')) 108 | endif 109 | let s:base_spec = { 'branch': '', 'frozen': 0 } 110 | let s:TYPE = { 111 | \ 'string': type(''), 112 | \ 'list': type([]), 113 | \ 'dict': type({}), 114 | \ 'funcref': type(function('call')) 115 | \ } 116 | let s:loaded = get(s:, 'loaded', {}) 117 | let s:triggers = get(s:, 'triggers', {}) 118 | 119 | function! s:is_powershell(shell) 120 | return a:shell =~# 'powershell\(\.exe\)\?$' || a:shell =~# 'pwsh\(\.exe\)\?$' 121 | endfunction 122 | 123 | function! s:isabsolute(dir) abort 124 | return a:dir =~# '^/' || (has('win32') && a:dir =~? '^\%(\\\|[A-Z]:\)') 125 | endfunction 126 | 127 | function! s:git_dir(dir) abort 128 | let gitdir = s:trim(a:dir) . '/.git' 129 | if isdirectory(gitdir) 130 | return gitdir 131 | endif 132 | if !filereadable(gitdir) 133 | return '' 134 | endif 135 | let gitdir = matchstr(get(readfile(gitdir), 0, ''), '^gitdir: \zs.*') 136 | if len(gitdir) && !s:isabsolute(gitdir) 137 | let gitdir = a:dir . '/' . gitdir 138 | endif 139 | return isdirectory(gitdir) ? gitdir : '' 140 | endfunction 141 | 142 | function! s:git_origin_url(dir) abort 143 | let gitdir = s:git_dir(a:dir) 144 | let config = gitdir . '/config' 145 | if empty(gitdir) || !filereadable(config) 146 | return '' 147 | endif 148 | return matchstr(join(readfile(config)), '\[remote "origin"\].\{-}url\s*=\s*\zs\S*\ze') 149 | endfunction 150 | 151 | function! s:git_revision(dir) abort 152 | let gitdir = s:git_dir(a:dir) 153 | let head = gitdir . '/HEAD' 154 | if empty(gitdir) || !filereadable(head) 155 | return '' 156 | endif 157 | 158 | let line = get(readfile(head), 0, '') 159 | let ref = matchstr(line, '^ref: \zs.*') 160 | if empty(ref) 161 | return line 162 | endif 163 | 164 | if filereadable(gitdir . '/' . ref) 165 | return get(readfile(gitdir . '/' . ref), 0, '') 166 | endif 167 | 168 | if filereadable(gitdir . '/packed-refs') 169 | for line in readfile(gitdir . '/packed-refs') 170 | if line =~# ' ' . ref 171 | return matchstr(line, '^[0-9a-f]*') 172 | endif 173 | endfor 174 | endif 175 | 176 | return '' 177 | endfunction 178 | 179 | function! s:git_local_branch(dir) abort 180 | let gitdir = s:git_dir(a:dir) 181 | let head = gitdir . '/HEAD' 182 | if empty(gitdir) || !filereadable(head) 183 | return '' 184 | endif 185 | let branch = matchstr(get(readfile(head), 0, ''), '^ref: refs/heads/\zs.*') 186 | return len(branch) ? branch : 'HEAD' 187 | endfunction 188 | 189 | function! s:git_origin_branch(spec) 190 | if len(a:spec.branch) 191 | return a:spec.branch 192 | endif 193 | 194 | " The file may not be present if this is a local repository 195 | let gitdir = s:git_dir(a:spec.dir) 196 | let origin_head = gitdir.'/refs/remotes/origin/HEAD' 197 | if len(gitdir) && filereadable(origin_head) 198 | return matchstr(get(readfile(origin_head), 0, ''), 199 | \ '^ref: refs/remotes/origin/\zs.*') 200 | endif 201 | 202 | " The command may not return the name of a branch in detached HEAD state 203 | let result = s:lines(s:system('git symbolic-ref --short HEAD', a:spec.dir)) 204 | return v:shell_error ? '' : result[-1] 205 | endfunction 206 | 207 | if s:is_win 208 | function! s:plug_call(fn, ...) 209 | let shellslash = &shellslash 210 | try 211 | set noshellslash 212 | return call(a:fn, a:000) 213 | finally 214 | let &shellslash = shellslash 215 | endtry 216 | endfunction 217 | else 218 | function! s:plug_call(fn, ...) 219 | return call(a:fn, a:000) 220 | endfunction 221 | endif 222 | 223 | function! s:plug_getcwd() 224 | return s:plug_call('getcwd') 225 | endfunction 226 | 227 | function! s:plug_fnamemodify(fname, mods) 228 | return s:plug_call('fnamemodify', a:fname, a:mods) 229 | endfunction 230 | 231 | function! s:plug_expand(fmt) 232 | return s:plug_call('expand', a:fmt, 1) 233 | endfunction 234 | 235 | function! s:plug_tempname() 236 | return s:plug_call('tempname') 237 | endfunction 238 | 239 | function! plug#begin(...) 240 | if a:0 > 0 241 | let s:plug_home_org = a:1 242 | let home = s:path(s:plug_fnamemodify(s:plug_expand(a:1), ':p')) 243 | elseif exists('g:plug_home') 244 | let home = s:path(g:plug_home) 245 | elseif has('nvim') 246 | let home = stdpath('data') . '/plugged' 247 | elseif !empty(&rtp) 248 | let home = s:path(split(&rtp, ',')[0]) . '/plugged' 249 | else 250 | return s:err('Unable to determine plug home. Try calling plug#begin() with a path argument.') 251 | endif 252 | if s:plug_fnamemodify(home, ':t') ==# 'plugin' && s:plug_fnamemodify(home, ':h') ==# s:first_rtp 253 | return s:err('Invalid plug home. '.home.' is a standard Vim runtime path and is not allowed.') 254 | endif 255 | 256 | let g:plug_home = home 257 | let g:plugs = {} 258 | let g:plugs_order = [] 259 | let s:triggers = {} 260 | 261 | call s:define_commands() 262 | return 1 263 | endfunction 264 | 265 | function! s:define_commands() 266 | command! -nargs=+ -bar Plug call plug#() 267 | if !executable('git') 268 | return s:err('`git` executable not found. Most commands will not be available. To suppress this message, prepend `silent!` to `call plug#begin(...)`.') 269 | endif 270 | if has('win32') 271 | \ && &shellslash 272 | \ && (&shell =~# 'cmd\(\.exe\)\?$' || s:is_powershell(&shell)) 273 | return s:err('vim-plug does not support shell, ' . &shell . ', when shellslash is set.') 274 | endif 275 | if !has('nvim') 276 | \ && (has('win32') || has('win32unix')) 277 | \ && !has('multi_byte') 278 | return s:err('Vim needs +multi_byte feature on Windows to run shell commands. Enable +iconv for best results.') 279 | endif 280 | command! -nargs=* -bar -bang -complete=customlist,s:names PlugInstall call s:install(0, []) 281 | command! -nargs=* -bar -bang -complete=customlist,s:names PlugUpdate call s:update(0, []) 282 | command! -nargs=0 -bar -bang PlugClean call s:clean(0) 283 | command! -nargs=0 -bar PlugUpgrade if s:upgrade() | execute 'source' s:esc(s:me) | endif 284 | command! -nargs=0 -bar PlugStatus call s:status() 285 | command! -nargs=0 -bar PlugDiff call s:diff() 286 | command! -nargs=? -bar -bang -complete=file PlugSnapshot call s:snapshot(0, ) 287 | endfunction 288 | 289 | function! s:to_a(v) 290 | return type(a:v) == s:TYPE.list ? a:v : [a:v] 291 | endfunction 292 | 293 | function! s:to_s(v) 294 | return type(a:v) == s:TYPE.string ? a:v : join(a:v, "\n") . "\n" 295 | endfunction 296 | 297 | function! s:glob(from, pattern) 298 | return s:lines(globpath(a:from, a:pattern)) 299 | endfunction 300 | 301 | function! s:source(from, ...) 302 | let found = 0 303 | for pattern in a:000 304 | for vim in s:glob(a:from, pattern) 305 | execute 'source' s:esc(vim) 306 | let found = 1 307 | endfor 308 | endfor 309 | return found 310 | endfunction 311 | 312 | function! s:assoc(dict, key, val) 313 | let a:dict[a:key] = add(get(a:dict, a:key, []), a:val) 314 | endfunction 315 | 316 | function! s:ask(message, ...) 317 | call inputsave() 318 | echohl WarningMsg 319 | let answer = input(a:message.(a:0 ? ' (y/N/a) ' : ' (y/N) ')) 320 | echohl None 321 | call inputrestore() 322 | echo "\r" 323 | return (a:0 && answer =~? '^a') ? 2 : (answer =~? '^y') ? 1 : 0 324 | endfunction 325 | 326 | function! s:ask_no_interrupt(...) 327 | try 328 | return call('s:ask', a:000) 329 | catch 330 | return 0 331 | endtry 332 | endfunction 333 | 334 | function! s:lazy(plug, opt) 335 | return has_key(a:plug, a:opt) && 336 | \ (empty(s:to_a(a:plug[a:opt])) || 337 | \ !isdirectory(a:plug.dir) || 338 | \ len(s:glob(s:rtp(a:plug), 'plugin')) || 339 | \ len(s:glob(s:rtp(a:plug), 'after/plugin'))) 340 | endfunction 341 | 342 | function! plug#end() 343 | if !exists('g:plugs') 344 | return s:err('plug#end() called without calling plug#begin() first') 345 | endif 346 | 347 | if exists('#PlugLOD') 348 | augroup PlugLOD 349 | autocmd! 350 | augroup END 351 | augroup! PlugLOD 352 | endif 353 | let lod = { 'ft': {}, 'map': {}, 'cmd': {} } 354 | 355 | if get(g:, 'did_load_filetypes', 0) 356 | filetype off 357 | endif 358 | for name in g:plugs_order 359 | if !has_key(g:plugs, name) 360 | continue 361 | endif 362 | let plug = g:plugs[name] 363 | if get(s:loaded, name, 0) || !s:lazy(plug, 'on') && !s:lazy(plug, 'for') 364 | let s:loaded[name] = 1 365 | continue 366 | endif 367 | 368 | if has_key(plug, 'on') 369 | let s:triggers[name] = { 'map': [], 'cmd': [] } 370 | for cmd in s:to_a(plug.on) 371 | if cmd =~? '^.\+' 372 | if empty(mapcheck(cmd)) && empty(mapcheck(cmd, 'i')) 373 | call s:assoc(lod.map, cmd, name) 374 | endif 375 | call add(s:triggers[name].map, cmd) 376 | elseif cmd =~# '^[A-Z]' 377 | let cmd = substitute(cmd, '!*$', '', '') 378 | if exists(':'.cmd) != 2 379 | call s:assoc(lod.cmd, cmd, name) 380 | endif 381 | call add(s:triggers[name].cmd, cmd) 382 | else 383 | call s:err('Invalid `on` option: '.cmd. 384 | \ '. Should start with an uppercase letter or ``.') 385 | endif 386 | endfor 387 | endif 388 | 389 | if has_key(plug, 'for') 390 | let types = s:to_a(plug.for) 391 | if !empty(types) 392 | augroup filetypedetect 393 | call s:source(s:rtp(plug), 'ftdetect/**/*.vim', 'after/ftdetect/**/*.vim') 394 | augroup END 395 | endif 396 | for type in types 397 | call s:assoc(lod.ft, type, name) 398 | endfor 399 | endif 400 | endfor 401 | 402 | for [cmd, names] in items(lod.cmd) 403 | execute printf( 404 | \ 'command! -nargs=* -range -bang -complete=file %s call s:lod_cmd(%s, "", , , , %s)', 405 | \ cmd, string(cmd), string(names)) 406 | endfor 407 | 408 | for [map, names] in items(lod.map) 409 | for [mode, map_prefix, key_prefix] in 410 | \ [['i', '', ''], ['n', '', ''], ['v', '', 'gv'], ['o', '', '']] 411 | execute printf( 412 | \ '%snoremap %s %s:call lod_map(%s, %s, %s, "%s")', 413 | \ mode, map, map_prefix, string(map), string(names), mode != 'i', key_prefix) 414 | endfor 415 | endfor 416 | 417 | for [ft, names] in items(lod.ft) 418 | augroup PlugLOD 419 | execute printf('autocmd FileType %s call lod_ft(%s, %s)', 420 | \ ft, string(ft), string(names)) 421 | augroup END 422 | endfor 423 | 424 | call s:reorg_rtp() 425 | filetype plugin indent on 426 | if has('vim_starting') 427 | if has('syntax') && !exists('g:syntax_on') 428 | syntax enable 429 | end 430 | else 431 | call s:reload_plugins() 432 | endif 433 | endfunction 434 | 435 | function! s:loaded_names() 436 | return filter(copy(g:plugs_order), 'get(s:loaded, v:val, 0)') 437 | endfunction 438 | 439 | function! s:load_plugin(spec) 440 | call s:source(s:rtp(a:spec), 'plugin/**/*.vim', 'after/plugin/**/*.vim') 441 | endfunction 442 | 443 | function! s:reload_plugins() 444 | for name in s:loaded_names() 445 | call s:load_plugin(g:plugs[name]) 446 | endfor 447 | endfunction 448 | 449 | function! s:trim(str) 450 | return substitute(a:str, '[\/]\+$', '', '') 451 | endfunction 452 | 453 | function! s:version_requirement(val, min) 454 | for idx in range(0, len(a:min) - 1) 455 | let v = get(a:val, idx, 0) 456 | if v < a:min[idx] | return 0 457 | elseif v > a:min[idx] | return 1 458 | endif 459 | endfor 460 | return 1 461 | endfunction 462 | 463 | function! s:git_version_requirement(...) 464 | if !exists('s:git_version') 465 | let s:git_version = map(split(split(s:system(['git', '--version']))[2], '\.'), 'str2nr(v:val)') 466 | endif 467 | return s:version_requirement(s:git_version, a:000) 468 | endfunction 469 | 470 | function! s:progress_opt(base) 471 | return a:base && !s:is_win && 472 | \ s:git_version_requirement(1, 7, 1) ? '--progress' : '' 473 | endfunction 474 | 475 | function! s:rtp(spec) 476 | return s:path(a:spec.dir . get(a:spec, 'rtp', '')) 477 | endfunction 478 | 479 | if s:is_win 480 | function! s:path(path) 481 | return s:trim(substitute(a:path, '/', '\', 'g')) 482 | endfunction 483 | 484 | function! s:dirpath(path) 485 | return s:path(a:path) . '\' 486 | endfunction 487 | 488 | function! s:is_local_plug(repo) 489 | return a:repo =~? '^[a-z]:\|^[%~]' 490 | endfunction 491 | 492 | " Copied from fzf 493 | function! s:wrap_cmds(cmds) 494 | let cmds = [ 495 | \ '@echo off', 496 | \ 'setlocal enabledelayedexpansion'] 497 | \ + (type(a:cmds) == type([]) ? a:cmds : [a:cmds]) 498 | \ + ['endlocal'] 499 | if has('iconv') 500 | if !exists('s:codepage') 501 | let s:codepage = libcallnr('kernel32.dll', 'GetACP', 0) 502 | endif 503 | return map(cmds, printf('iconv(v:val."\r", "%s", "cp%d")', &encoding, s:codepage)) 504 | endif 505 | return map(cmds, 'v:val."\r"') 506 | endfunction 507 | 508 | function! s:batchfile(cmd) 509 | let batchfile = s:plug_tempname().'.bat' 510 | call writefile(s:wrap_cmds(a:cmd), batchfile) 511 | let cmd = plug#shellescape(batchfile, {'shell': &shell, 'script': 0}) 512 | if s:is_powershell(&shell) 513 | let cmd = '& ' . cmd 514 | endif 515 | return [batchfile, cmd] 516 | endfunction 517 | else 518 | function! s:path(path) 519 | return s:trim(a:path) 520 | endfunction 521 | 522 | function! s:dirpath(path) 523 | return substitute(a:path, '[/\\]*$', '/', '') 524 | endfunction 525 | 526 | function! s:is_local_plug(repo) 527 | return a:repo[0] =~ '[/$~]' 528 | endfunction 529 | endif 530 | 531 | function! s:err(msg) 532 | echohl ErrorMsg 533 | echom '[vim-plug] '.a:msg 534 | echohl None 535 | endfunction 536 | 537 | function! s:warn(cmd, msg) 538 | echohl WarningMsg 539 | execute a:cmd 'a:msg' 540 | echohl None 541 | endfunction 542 | 543 | function! s:esc(path) 544 | return escape(a:path, ' ') 545 | endfunction 546 | 547 | function! s:escrtp(path) 548 | return escape(a:path, ' ,') 549 | endfunction 550 | 551 | function! s:remove_rtp() 552 | for name in s:loaded_names() 553 | let rtp = s:rtp(g:plugs[name]) 554 | execute 'set rtp-='.s:escrtp(rtp) 555 | let after = globpath(rtp, 'after') 556 | if isdirectory(after) 557 | execute 'set rtp-='.s:escrtp(after) 558 | endif 559 | endfor 560 | endfunction 561 | 562 | function! s:reorg_rtp() 563 | if !empty(s:first_rtp) 564 | execute 'set rtp-='.s:first_rtp 565 | execute 'set rtp-='.s:last_rtp 566 | endif 567 | 568 | " &rtp is modified from outside 569 | if exists('s:prtp') && s:prtp !=# &rtp 570 | call s:remove_rtp() 571 | unlet! s:middle 572 | endif 573 | 574 | let s:middle = get(s:, 'middle', &rtp) 575 | let rtps = map(s:loaded_names(), 's:rtp(g:plugs[v:val])') 576 | let afters = filter(map(copy(rtps), 'globpath(v:val, "after")'), '!empty(v:val)') 577 | let rtp = join(map(rtps, 'escape(v:val, ",")'), ',') 578 | \ . ','.s:middle.',' 579 | \ . join(map(afters, 'escape(v:val, ",")'), ',') 580 | let &rtp = substitute(substitute(rtp, ',,*', ',', 'g'), '^,\|,$', '', 'g') 581 | let s:prtp = &rtp 582 | 583 | if !empty(s:first_rtp) 584 | execute 'set rtp^='.s:first_rtp 585 | execute 'set rtp+='.s:last_rtp 586 | endif 587 | endfunction 588 | 589 | function! s:doautocmd(...) 590 | if exists('#'.join(a:000, '#')) 591 | execute 'doautocmd' ((v:version > 703 || has('patch442')) ? '' : '') join(a:000) 592 | endif 593 | endfunction 594 | 595 | function! s:dobufread(names) 596 | for name in a:names 597 | let path = s:rtp(g:plugs[name]) 598 | for dir in ['ftdetect', 'ftplugin', 'after/ftdetect', 'after/ftplugin'] 599 | if len(finddir(dir, path)) 600 | if exists('#BufRead') 601 | doautocmd BufRead 602 | endif 603 | return 604 | endif 605 | endfor 606 | endfor 607 | endfunction 608 | 609 | function! plug#load(...) 610 | if a:0 == 0 611 | return s:err('Argument missing: plugin name(s) required') 612 | endif 613 | if !exists('g:plugs') 614 | return s:err('plug#begin was not called') 615 | endif 616 | let names = a:0 == 1 && type(a:1) == s:TYPE.list ? a:1 : a:000 617 | let unknowns = filter(copy(names), '!has_key(g:plugs, v:val)') 618 | if !empty(unknowns) 619 | let s = len(unknowns) > 1 ? 's' : '' 620 | return s:err(printf('Unknown plugin%s: %s', s, join(unknowns, ', '))) 621 | end 622 | let unloaded = filter(copy(names), '!get(s:loaded, v:val, 0)') 623 | if !empty(unloaded) 624 | for name in unloaded 625 | call s:lod([name], ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin']) 626 | endfor 627 | call s:dobufread(unloaded) 628 | return 1 629 | end 630 | return 0 631 | endfunction 632 | 633 | function! s:remove_triggers(name) 634 | if !has_key(s:triggers, a:name) 635 | return 636 | endif 637 | for cmd in s:triggers[a:name].cmd 638 | execute 'silent! delc' cmd 639 | endfor 640 | for map in s:triggers[a:name].map 641 | execute 'silent! unmap' map 642 | execute 'silent! iunmap' map 643 | endfor 644 | call remove(s:triggers, a:name) 645 | endfunction 646 | 647 | function! s:lod(names, types, ...) 648 | for name in a:names 649 | call s:remove_triggers(name) 650 | let s:loaded[name] = 1 651 | endfor 652 | call s:reorg_rtp() 653 | 654 | for name in a:names 655 | let rtp = s:rtp(g:plugs[name]) 656 | for dir in a:types 657 | call s:source(rtp, dir.'/**/*.vim') 658 | endfor 659 | if a:0 660 | if !s:source(rtp, a:1) && !empty(s:glob(rtp, a:2)) 661 | execute 'runtime' a:1 662 | endif 663 | call s:source(rtp, a:2) 664 | endif 665 | call s:doautocmd('User', name) 666 | endfor 667 | endfunction 668 | 669 | function! s:lod_ft(pat, names) 670 | let syn = 'syntax/'.a:pat.'.vim' 671 | call s:lod(a:names, ['plugin', 'after/plugin'], syn, 'after/'.syn) 672 | execute 'autocmd! PlugLOD FileType' a:pat 673 | call s:doautocmd('filetypeplugin', 'FileType') 674 | call s:doautocmd('filetypeindent', 'FileType') 675 | endfunction 676 | 677 | function! s:lod_cmd(cmd, bang, l1, l2, args, names) 678 | call s:lod(a:names, ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin']) 679 | call s:dobufread(a:names) 680 | execute printf('%s%s%s %s', (a:l1 == a:l2 ? '' : (a:l1.','.a:l2)), a:cmd, a:bang, a:args) 681 | endfunction 682 | 683 | function! s:lod_map(map, names, with_prefix, prefix) 684 | call s:lod(a:names, ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin']) 685 | call s:dobufread(a:names) 686 | let extra = '' 687 | while 1 688 | let c = getchar(0) 689 | if c == 0 690 | break 691 | endif 692 | let extra .= nr2char(c) 693 | endwhile 694 | 695 | if a:with_prefix 696 | let prefix = v:count ? v:count : '' 697 | let prefix .= '"'.v:register.a:prefix 698 | if mode(1) == 'no' 699 | if v:operator == 'c' 700 | let prefix = "\" . prefix 701 | endif 702 | let prefix .= v:operator 703 | endif 704 | call feedkeys(prefix, 'n') 705 | endif 706 | call feedkeys(substitute(a:map, '^', "\", '') . extra) 707 | endfunction 708 | 709 | function! plug#(repo, ...) 710 | if a:0 > 1 711 | return s:err('Invalid number of arguments (1..2)') 712 | endif 713 | 714 | try 715 | let repo = s:trim(a:repo) 716 | let opts = a:0 == 1 ? s:parse_options(a:1) : s:base_spec 717 | let name = get(opts, 'as', s:plug_fnamemodify(repo, ':t:s?\.git$??')) 718 | let spec = extend(s:infer_properties(name, repo), opts) 719 | if !has_key(g:plugs, name) 720 | call add(g:plugs_order, name) 721 | endif 722 | let g:plugs[name] = spec 723 | let s:loaded[name] = get(s:loaded, name, 0) 724 | catch 725 | return s:err(repo . ' ' . v:exception) 726 | endtry 727 | endfunction 728 | 729 | function! s:parse_options(arg) 730 | let opts = copy(s:base_spec) 731 | let type = type(a:arg) 732 | let opt_errfmt = 'Invalid argument for "%s" option of :Plug (expected: %s)' 733 | if type == s:TYPE.string 734 | if empty(a:arg) 735 | throw printf(opt_errfmt, 'tag', 'string') 736 | endif 737 | let opts.tag = a:arg 738 | elseif type == s:TYPE.dict 739 | for opt in ['branch', 'tag', 'commit', 'rtp', 'dir', 'as'] 740 | if has_key(a:arg, opt) 741 | \ && (type(a:arg[opt]) != s:TYPE.string || empty(a:arg[opt])) 742 | throw printf(opt_errfmt, opt, 'string') 743 | endif 744 | endfor 745 | for opt in ['on', 'for'] 746 | if has_key(a:arg, opt) 747 | \ && type(a:arg[opt]) != s:TYPE.list 748 | \ && (type(a:arg[opt]) != s:TYPE.string || empty(a:arg[opt])) 749 | throw printf(opt_errfmt, opt, 'string or list') 750 | endif 751 | endfor 752 | if has_key(a:arg, 'do') 753 | \ && type(a:arg.do) != s:TYPE.funcref 754 | \ && (type(a:arg.do) != s:TYPE.string || empty(a:arg.do)) 755 | throw printf(opt_errfmt, 'do', 'string or funcref') 756 | endif 757 | call extend(opts, a:arg) 758 | if has_key(opts, 'dir') 759 | let opts.dir = s:dirpath(s:plug_expand(opts.dir)) 760 | endif 761 | else 762 | throw 'Invalid argument type (expected: string or dictionary)' 763 | endif 764 | return opts 765 | endfunction 766 | 767 | function! s:infer_properties(name, repo) 768 | let repo = a:repo 769 | if s:is_local_plug(repo) 770 | return { 'dir': s:dirpath(s:plug_expand(repo)) } 771 | else 772 | if repo =~ ':' 773 | let uri = repo 774 | else 775 | if repo !~ '/' 776 | throw printf('Invalid argument: %s (implicit `vim-scripts'' expansion is deprecated)', repo) 777 | endif 778 | let fmt = get(g:, 'plug_url_format', 'https://git::@github.com/%s.git') 779 | let uri = printf(fmt, repo) 780 | endif 781 | return { 'dir': s:dirpath(g:plug_home.'/'.a:name), 'uri': uri } 782 | endif 783 | endfunction 784 | 785 | function! s:install(force, names) 786 | call s:update_impl(0, a:force, a:names) 787 | endfunction 788 | 789 | function! s:update(force, names) 790 | call s:update_impl(1, a:force, a:names) 791 | endfunction 792 | 793 | function! plug#helptags() 794 | if !exists('g:plugs') 795 | return s:err('plug#begin was not called') 796 | endif 797 | for spec in values(g:plugs) 798 | let docd = join([s:rtp(spec), 'doc'], '/') 799 | if isdirectory(docd) 800 | silent! execute 'helptags' s:esc(docd) 801 | endif 802 | endfor 803 | return 1 804 | endfunction 805 | 806 | function! s:syntax() 807 | syntax clear 808 | syntax region plug1 start=/\%1l/ end=/\%2l/ contains=plugNumber 809 | syntax region plug2 start=/\%2l/ end=/\%3l/ contains=plugBracket,plugX 810 | syn match plugNumber /[0-9]\+[0-9.]*/ contained 811 | syn match plugBracket /[[\]]/ contained 812 | syn match plugX /x/ contained 813 | syn match plugDash /^-\{1}\ / 814 | syn match plugPlus /^+/ 815 | syn match plugStar /^*/ 816 | syn match plugMessage /\(^- \)\@<=.*/ 817 | syn match plugName /\(^- \)\@<=[^ ]*:/ 818 | syn match plugSha /\%(: \)\@<=[0-9a-f]\{4,}$/ 819 | syn match plugTag /(tag: [^)]\+)/ 820 | syn match plugInstall /\(^+ \)\@<=[^:]*/ 821 | syn match plugUpdate /\(^* \)\@<=[^:]*/ 822 | syn match plugCommit /^ \X*[0-9a-f]\{7,9} .*/ contains=plugRelDate,plugEdge,plugTag 823 | syn match plugEdge /^ \X\+$/ 824 | syn match plugEdge /^ \X*/ contained nextgroup=plugSha 825 | syn match plugSha /[0-9a-f]\{7,9}/ contained 826 | syn match plugRelDate /([^)]*)$/ contained 827 | syn match plugNotLoaded /(not loaded)$/ 828 | syn match plugError /^x.*/ 829 | syn region plugDeleted start=/^\~ .*/ end=/^\ze\S/ 830 | syn match plugH2 /^.*:\n-\+$/ 831 | syn match plugH2 /^-\{2,}/ 832 | syn keyword Function PlugInstall PlugStatus PlugUpdate PlugClean 833 | hi def link plug1 Title 834 | hi def link plug2 Repeat 835 | hi def link plugH2 Type 836 | hi def link plugX Exception 837 | hi def link plugBracket Structure 838 | hi def link plugNumber Number 839 | 840 | hi def link plugDash Special 841 | hi def link plugPlus Constant 842 | hi def link plugStar Boolean 843 | 844 | hi def link plugMessage Function 845 | hi def link plugName Label 846 | hi def link plugInstall Function 847 | hi def link plugUpdate Type 848 | 849 | hi def link plugError Error 850 | hi def link plugDeleted Ignore 851 | hi def link plugRelDate Comment 852 | hi def link plugEdge PreProc 853 | hi def link plugSha Identifier 854 | hi def link plugTag Constant 855 | 856 | hi def link plugNotLoaded Comment 857 | endfunction 858 | 859 | function! s:lpad(str, len) 860 | return a:str . repeat(' ', a:len - len(a:str)) 861 | endfunction 862 | 863 | function! s:lines(msg) 864 | return split(a:msg, "[\r\n]") 865 | endfunction 866 | 867 | function! s:lastline(msg) 868 | return get(s:lines(a:msg), -1, '') 869 | endfunction 870 | 871 | function! s:new_window() 872 | execute get(g:, 'plug_window', 'vertical topleft new') 873 | endfunction 874 | 875 | function! s:plug_window_exists() 876 | let buflist = tabpagebuflist(s:plug_tab) 877 | return !empty(buflist) && index(buflist, s:plug_buf) >= 0 878 | endfunction 879 | 880 | function! s:switch_in() 881 | if !s:plug_window_exists() 882 | return 0 883 | endif 884 | 885 | if winbufnr(0) != s:plug_buf 886 | let s:pos = [tabpagenr(), winnr(), winsaveview()] 887 | execute 'normal!' s:plug_tab.'gt' 888 | let winnr = bufwinnr(s:plug_buf) 889 | execute winnr.'wincmd w' 890 | call add(s:pos, winsaveview()) 891 | else 892 | let s:pos = [winsaveview()] 893 | endif 894 | 895 | setlocal modifiable 896 | return 1 897 | endfunction 898 | 899 | function! s:switch_out(...) 900 | call winrestview(s:pos[-1]) 901 | setlocal nomodifiable 902 | if a:0 > 0 903 | execute a:1 904 | endif 905 | 906 | if len(s:pos) > 1 907 | execute 'normal!' s:pos[0].'gt' 908 | execute s:pos[1] 'wincmd w' 909 | call winrestview(s:pos[2]) 910 | endif 911 | endfunction 912 | 913 | function! s:finish_bindings() 914 | nnoremap R :call retry() 915 | nnoremap D :PlugDiff 916 | nnoremap S :PlugStatus 917 | nnoremap U :call status_update() 918 | xnoremap U :call status_update() 919 | nnoremap ]] :silent! call section('') 920 | nnoremap [[ :silent! call section('b') 921 | endfunction 922 | 923 | function! s:prepare(...) 924 | if empty(s:plug_getcwd()) 925 | throw 'Invalid current working directory. Cannot proceed.' 926 | endif 927 | 928 | for evar in ['$GIT_DIR', '$GIT_WORK_TREE'] 929 | if exists(evar) 930 | throw evar.' detected. Cannot proceed.' 931 | endif 932 | endfor 933 | 934 | call s:job_abort() 935 | if s:switch_in() 936 | if b:plug_preview == 1 937 | pc 938 | endif 939 | enew 940 | else 941 | call s:new_window() 942 | endif 943 | 944 | nnoremap q :call close_pane() 945 | if a:0 == 0 946 | call s:finish_bindings() 947 | endif 948 | let b:plug_preview = -1 949 | let s:plug_tab = tabpagenr() 950 | let s:plug_buf = winbufnr(0) 951 | call s:assign_name() 952 | 953 | for k in ['', 'L', 'o', 'X', 'd', 'dd'] 954 | execute 'silent! unmap ' k 955 | endfor 956 | setlocal buftype=nofile bufhidden=wipe nobuflisted nolist noswapfile nowrap cursorline modifiable nospell 957 | if exists('+colorcolumn') 958 | setlocal colorcolumn= 959 | endif 960 | setf vim-plug 961 | if exists('g:syntax_on') 962 | call s:syntax() 963 | endif 964 | endfunction 965 | 966 | function! s:close_pane() 967 | if b:plug_preview == 1 968 | pc 969 | let b:plug_preview = -1 970 | else 971 | bd 972 | endif 973 | endfunction 974 | 975 | function! s:assign_name() 976 | " Assign buffer name 977 | let prefix = '[Plugins]' 978 | let name = prefix 979 | let idx = 2 980 | while bufexists(name) 981 | let name = printf('%s (%s)', prefix, idx) 982 | let idx = idx + 1 983 | endwhile 984 | silent! execute 'f' fnameescape(name) 985 | endfunction 986 | 987 | function! s:chsh(swap) 988 | let prev = [&shell, &shellcmdflag, &shellredir] 989 | if !s:is_win 990 | set shell=sh 991 | endif 992 | if a:swap 993 | if s:is_powershell(&shell) 994 | let &shellredir = '2>&1 | Out-File -Encoding UTF8 %s' 995 | elseif &shell =~# 'sh' || &shell =~# 'cmd\(\.exe\)\?$' 996 | set shellredir=>%s\ 2>&1 997 | endif 998 | endif 999 | return prev 1000 | endfunction 1001 | 1002 | function! s:bang(cmd, ...) 1003 | let batchfile = '' 1004 | try 1005 | let [sh, shellcmdflag, shrd] = s:chsh(a:0) 1006 | " FIXME: Escaping is incomplete. We could use shellescape with eval, 1007 | " but it won't work on Windows. 1008 | let cmd = a:0 ? s:with_cd(a:cmd, a:1) : a:cmd 1009 | if s:is_win 1010 | let [batchfile, cmd] = s:batchfile(cmd) 1011 | endif 1012 | let g:_plug_bang = (s:is_win && has('gui_running') ? 'silent ' : '').'!'.escape(cmd, '#!%') 1013 | execute "normal! :execute g:_plug_bang\\" 1014 | finally 1015 | unlet g:_plug_bang 1016 | let [&shell, &shellcmdflag, &shellredir] = [sh, shellcmdflag, shrd] 1017 | if s:is_win && filereadable(batchfile) 1018 | call delete(batchfile) 1019 | endif 1020 | endtry 1021 | return v:shell_error ? 'Exit status: ' . v:shell_error : '' 1022 | endfunction 1023 | 1024 | function! s:regress_bar() 1025 | let bar = substitute(getline(2)[1:-2], '.*\zs=', 'x', '') 1026 | call s:progress_bar(2, bar, len(bar)) 1027 | endfunction 1028 | 1029 | function! s:is_updated(dir) 1030 | return !empty(s:system_chomp(['git', 'log', '--pretty=format:%h', 'HEAD...HEAD@{1}'], a:dir)) 1031 | endfunction 1032 | 1033 | function! s:do(pull, force, todo) 1034 | for [name, spec] in items(a:todo) 1035 | if !isdirectory(spec.dir) 1036 | continue 1037 | endif 1038 | let installed = has_key(s:update.new, name) 1039 | let updated = installed ? 0 : 1040 | \ (a:pull && index(s:update.errors, name) < 0 && s:is_updated(spec.dir)) 1041 | if a:force || installed || updated 1042 | execute 'cd' s:esc(spec.dir) 1043 | call append(3, '- Post-update hook for '. name .' ... ') 1044 | let error = '' 1045 | let type = type(spec.do) 1046 | if type == s:TYPE.string 1047 | if spec.do[0] == ':' 1048 | if !get(s:loaded, name, 0) 1049 | let s:loaded[name] = 1 1050 | call s:reorg_rtp() 1051 | endif 1052 | call s:load_plugin(spec) 1053 | try 1054 | execute spec.do[1:] 1055 | catch 1056 | let error = v:exception 1057 | endtry 1058 | if !s:plug_window_exists() 1059 | cd - 1060 | throw 'Warning: vim-plug was terminated by the post-update hook of '.name 1061 | endif 1062 | else 1063 | let error = s:bang(spec.do) 1064 | endif 1065 | elseif type == s:TYPE.funcref 1066 | try 1067 | call s:load_plugin(spec) 1068 | let status = installed ? 'installed' : (updated ? 'updated' : 'unchanged') 1069 | call spec.do({ 'name': name, 'status': status, 'force': a:force }) 1070 | catch 1071 | let error = v:exception 1072 | endtry 1073 | else 1074 | let error = 'Invalid hook type' 1075 | endif 1076 | call s:switch_in() 1077 | call setline(4, empty(error) ? (getline(4) . 'OK') 1078 | \ : ('x' . getline(4)[1:] . error)) 1079 | if !empty(error) 1080 | call add(s:update.errors, name) 1081 | call s:regress_bar() 1082 | endif 1083 | cd - 1084 | endif 1085 | endfor 1086 | endfunction 1087 | 1088 | function! s:hash_match(a, b) 1089 | return stridx(a:a, a:b) == 0 || stridx(a:b, a:a) == 0 1090 | endfunction 1091 | 1092 | function! s:checkout(spec) 1093 | let sha = a:spec.commit 1094 | let output = s:git_revision(a:spec.dir) 1095 | if !empty(output) && !s:hash_match(sha, s:lines(output)[0]) 1096 | let credential_helper = s:git_version_requirement(2) ? '-c credential.helper= ' : '' 1097 | let output = s:system( 1098 | \ 'git '.credential_helper.'fetch --depth 999999 && git checkout '.plug#shellescape(sha).' --', a:spec.dir) 1099 | endif 1100 | return output 1101 | endfunction 1102 | 1103 | function! s:finish(pull) 1104 | let new_frozen = len(filter(keys(s:update.new), 'g:plugs[v:val].frozen')) 1105 | if new_frozen 1106 | let s = new_frozen > 1 ? 's' : '' 1107 | call append(3, printf('- Installed %d frozen plugin%s', new_frozen, s)) 1108 | endif 1109 | call append(3, '- Finishing ... ') | 4 1110 | redraw 1111 | call plug#helptags() 1112 | call plug#end() 1113 | call setline(4, getline(4) . 'Done!') 1114 | redraw 1115 | let msgs = [] 1116 | if !empty(s:update.errors) 1117 | call add(msgs, "Press 'R' to retry.") 1118 | endif 1119 | if a:pull && len(s:update.new) < len(filter(getline(5, '$'), 1120 | \ "v:val =~ '^- ' && v:val !~# 'Already up.to.date'")) 1121 | call add(msgs, "Press 'D' to see the updated changes.") 1122 | endif 1123 | echo join(msgs, ' ') 1124 | call s:finish_bindings() 1125 | endfunction 1126 | 1127 | function! s:retry() 1128 | if empty(s:update.errors) 1129 | return 1130 | endif 1131 | echo 1132 | call s:update_impl(s:update.pull, s:update.force, 1133 | \ extend(copy(s:update.errors), [s:update.threads])) 1134 | endfunction 1135 | 1136 | function! s:is_managed(name) 1137 | return has_key(g:plugs[a:name], 'uri') 1138 | endfunction 1139 | 1140 | function! s:names(...) 1141 | return sort(filter(keys(g:plugs), 'stridx(v:val, a:1) == 0 && s:is_managed(v:val)')) 1142 | endfunction 1143 | 1144 | function! s:check_ruby() 1145 | silent! ruby require 'thread'; VIM::command("let g:plug_ruby = '#{RUBY_VERSION}'") 1146 | if !exists('g:plug_ruby') 1147 | redraw! 1148 | return s:warn('echom', 'Warning: Ruby interface is broken') 1149 | endif 1150 | let ruby_version = split(g:plug_ruby, '\.') 1151 | unlet g:plug_ruby 1152 | return s:version_requirement(ruby_version, [1, 8, 7]) 1153 | endfunction 1154 | 1155 | function! s:update_impl(pull, force, args) abort 1156 | let sync = index(a:args, '--sync') >= 0 || has('vim_starting') 1157 | let args = filter(copy(a:args), 'v:val != "--sync"') 1158 | let threads = (len(args) > 0 && args[-1] =~ '^[1-9][0-9]*$') ? 1159 | \ remove(args, -1) : get(g:, 'plug_threads', 16) 1160 | 1161 | let managed = filter(copy(g:plugs), 's:is_managed(v:key)') 1162 | let todo = empty(args) ? filter(managed, '!v:val.frozen || !isdirectory(v:val.dir)') : 1163 | \ filter(managed, 'index(args, v:key) >= 0') 1164 | 1165 | if empty(todo) 1166 | return s:warn('echo', 'No plugin to '. (a:pull ? 'update' : 'install')) 1167 | endif 1168 | 1169 | if !s:is_win && s:git_version_requirement(2, 3) 1170 | let s:git_terminal_prompt = exists('$GIT_TERMINAL_PROMPT') ? $GIT_TERMINAL_PROMPT : '' 1171 | let $GIT_TERMINAL_PROMPT = 0 1172 | for plug in values(todo) 1173 | let plug.uri = substitute(plug.uri, 1174 | \ '^https://git::@github\.com', 'https://github.com', '') 1175 | endfor 1176 | endif 1177 | 1178 | if !isdirectory(g:plug_home) 1179 | try 1180 | call mkdir(g:plug_home, 'p') 1181 | catch 1182 | return s:err(printf('Invalid plug directory: %s. '. 1183 | \ 'Try to call plug#begin with a valid directory', g:plug_home)) 1184 | endtry 1185 | endif 1186 | 1187 | if has('nvim') && !exists('*jobwait') && threads > 1 1188 | call s:warn('echom', '[vim-plug] Update Neovim for parallel installer') 1189 | endif 1190 | 1191 | let use_job = s:nvim || s:vim8 1192 | let python = (has('python') || has('python3')) && !use_job 1193 | let ruby = has('ruby') && !use_job && (v:version >= 703 || v:version == 702 && has('patch374')) && !(s:is_win && has('gui_running')) && threads > 1 && s:check_ruby() 1194 | 1195 | let s:update = { 1196 | \ 'start': reltime(), 1197 | \ 'all': todo, 1198 | \ 'todo': copy(todo), 1199 | \ 'errors': [], 1200 | \ 'pull': a:pull, 1201 | \ 'force': a:force, 1202 | \ 'new': {}, 1203 | \ 'threads': (python || ruby || use_job) ? min([len(todo), threads]) : 1, 1204 | \ 'bar': '', 1205 | \ 'fin': 0 1206 | \ } 1207 | 1208 | call s:prepare(1) 1209 | call append(0, ['', '']) 1210 | normal! 2G 1211 | silent! redraw 1212 | 1213 | " Set remote name, overriding a possible user git config's clone.defaultRemoteName 1214 | let s:clone_opt = ['--origin', 'origin'] 1215 | if get(g:, 'plug_shallow', 1) 1216 | call extend(s:clone_opt, ['--depth', '1']) 1217 | if s:git_version_requirement(1, 7, 10) 1218 | call add(s:clone_opt, '--no-single-branch') 1219 | endif 1220 | endif 1221 | 1222 | if has('win32unix') || has('wsl') 1223 | call extend(s:clone_opt, ['-c', 'core.eol=lf', '-c', 'core.autocrlf=input']) 1224 | endif 1225 | 1226 | let s:submodule_opt = s:git_version_requirement(2, 8) ? ' --jobs='.threads : '' 1227 | 1228 | " Python version requirement (>= 2.7) 1229 | if python && !has('python3') && !ruby && !use_job && s:update.threads > 1 1230 | redir => pyv 1231 | silent python import platform; print platform.python_version() 1232 | redir END 1233 | let python = s:version_requirement( 1234 | \ map(split(split(pyv)[0], '\.'), 'str2nr(v:val)'), [2, 6]) 1235 | endif 1236 | 1237 | if (python || ruby) && s:update.threads > 1 1238 | try 1239 | let imd = &imd 1240 | if s:mac_gui 1241 | set noimd 1242 | endif 1243 | if ruby 1244 | call s:update_ruby() 1245 | else 1246 | call s:update_python() 1247 | endif 1248 | catch 1249 | let lines = getline(4, '$') 1250 | let printed = {} 1251 | silent! 4,$d _ 1252 | for line in lines 1253 | let name = s:extract_name(line, '.', '') 1254 | if empty(name) || !has_key(printed, name) 1255 | call append('$', line) 1256 | if !empty(name) 1257 | let printed[name] = 1 1258 | if line[0] == 'x' && index(s:update.errors, name) < 0 1259 | call add(s:update.errors, name) 1260 | end 1261 | endif 1262 | endif 1263 | endfor 1264 | finally 1265 | let &imd = imd 1266 | call s:update_finish() 1267 | endtry 1268 | else 1269 | call s:update_vim() 1270 | while use_job && sync 1271 | sleep 100m 1272 | if s:update.fin 1273 | break 1274 | endif 1275 | endwhile 1276 | endif 1277 | endfunction 1278 | 1279 | function! s:log4(name, msg) 1280 | call setline(4, printf('- %s (%s)', a:msg, a:name)) 1281 | redraw 1282 | endfunction 1283 | 1284 | function! s:update_finish() 1285 | if exists('s:git_terminal_prompt') 1286 | let $GIT_TERMINAL_PROMPT = s:git_terminal_prompt 1287 | endif 1288 | if s:switch_in() 1289 | call append(3, '- Updating ...') | 4 1290 | for [name, spec] in items(filter(copy(s:update.all), 'index(s:update.errors, v:key) < 0 && (s:update.force || s:update.pull || has_key(s:update.new, v:key))')) 1291 | let [pos, _] = s:logpos(name) 1292 | if !pos 1293 | continue 1294 | endif 1295 | if has_key(spec, 'commit') 1296 | call s:log4(name, 'Checking out '.spec.commit) 1297 | let out = s:checkout(spec) 1298 | elseif has_key(spec, 'tag') 1299 | let tag = spec.tag 1300 | if tag =~ '\*' 1301 | let tags = s:lines(s:system('git tag --list '.plug#shellescape(tag).' --sort -version:refname 2>&1', spec.dir)) 1302 | if !v:shell_error && !empty(tags) 1303 | let tag = tags[0] 1304 | call s:log4(name, printf('Latest tag for %s -> %s', spec.tag, tag)) 1305 | call append(3, '') 1306 | endif 1307 | endif 1308 | call s:log4(name, 'Checking out '.tag) 1309 | let out = s:system('git checkout -q '.plug#shellescape(tag).' -- 2>&1', spec.dir) 1310 | else 1311 | let branch = s:git_origin_branch(spec) 1312 | call s:log4(name, 'Merging origin/'.s:esc(branch)) 1313 | let out = s:system('git checkout -q '.plug#shellescape(branch).' -- 2>&1' 1314 | \. (has_key(s:update.new, name) ? '' : ('&& git merge --ff-only '.plug#shellescape('origin/'.branch).' 2>&1')), spec.dir) 1315 | endif 1316 | if !v:shell_error && filereadable(spec.dir.'/.gitmodules') && 1317 | \ (s:update.force || has_key(s:update.new, name) || s:is_updated(spec.dir)) 1318 | call s:log4(name, 'Updating submodules. This may take a while.') 1319 | let out .= s:bang('git submodule update --init --recursive'.s:submodule_opt.' 2>&1', spec.dir) 1320 | endif 1321 | let msg = s:format_message(v:shell_error ? 'x': '-', name, out) 1322 | if v:shell_error 1323 | call add(s:update.errors, name) 1324 | call s:regress_bar() 1325 | silent execute pos 'd _' 1326 | call append(4, msg) | 4 1327 | elseif !empty(out) 1328 | call setline(pos, msg[0]) 1329 | endif 1330 | redraw 1331 | endfor 1332 | silent 4 d _ 1333 | try 1334 | call s:do(s:update.pull, s:update.force, filter(copy(s:update.all), 'index(s:update.errors, v:key) < 0 && has_key(v:val, "do")')) 1335 | catch 1336 | call s:warn('echom', v:exception) 1337 | call s:warn('echo', '') 1338 | return 1339 | endtry 1340 | call s:finish(s:update.pull) 1341 | call setline(1, 'Updated. Elapsed time: ' . split(reltimestr(reltime(s:update.start)))[0] . ' sec.') 1342 | call s:switch_out('normal! gg') 1343 | endif 1344 | endfunction 1345 | 1346 | function! s:job_abort() 1347 | if (!s:nvim && !s:vim8) || !exists('s:jobs') 1348 | return 1349 | endif 1350 | 1351 | for [name, j] in items(s:jobs) 1352 | if s:nvim 1353 | silent! call jobstop(j.jobid) 1354 | elseif s:vim8 1355 | silent! call job_stop(j.jobid) 1356 | endif 1357 | if j.new 1358 | call s:rm_rf(g:plugs[name].dir) 1359 | endif 1360 | endfor 1361 | let s:jobs = {} 1362 | endfunction 1363 | 1364 | function! s:last_non_empty_line(lines) 1365 | let len = len(a:lines) 1366 | for idx in range(len) 1367 | let line = a:lines[len-idx-1] 1368 | if !empty(line) 1369 | return line 1370 | endif 1371 | endfor 1372 | return '' 1373 | endfunction 1374 | 1375 | function! s:job_out_cb(self, data) abort 1376 | let self = a:self 1377 | let data = remove(self.lines, -1) . a:data 1378 | let lines = map(split(data, "\n", 1), 'split(v:val, "\r", 1)[-1]') 1379 | call extend(self.lines, lines) 1380 | " To reduce the number of buffer updates 1381 | let self.tick = get(self, 'tick', -1) + 1 1382 | if !self.running || self.tick % len(s:jobs) == 0 1383 | let bullet = self.running ? (self.new ? '+' : '*') : (self.error ? 'x' : '-') 1384 | let result = self.error ? join(self.lines, "\n") : s:last_non_empty_line(self.lines) 1385 | call s:log(bullet, self.name, result) 1386 | endif 1387 | endfunction 1388 | 1389 | function! s:job_exit_cb(self, data) abort 1390 | let a:self.running = 0 1391 | let a:self.error = a:data != 0 1392 | call s:reap(a:self.name) 1393 | call s:tick() 1394 | endfunction 1395 | 1396 | function! s:job_cb(fn, job, ch, data) 1397 | if !s:plug_window_exists() " plug window closed 1398 | return s:job_abort() 1399 | endif 1400 | call call(a:fn, [a:job, a:data]) 1401 | endfunction 1402 | 1403 | function! s:nvim_cb(job_id, data, event) dict abort 1404 | return (a:event == 'stdout' || a:event == 'stderr') ? 1405 | \ s:job_cb('s:job_out_cb', self, 0, join(a:data, "\n")) : 1406 | \ s:job_cb('s:job_exit_cb', self, 0, a:data) 1407 | endfunction 1408 | 1409 | function! s:spawn(name, cmd, opts) 1410 | let job = { 'name': a:name, 'running': 1, 'error': 0, 'lines': [''], 1411 | \ 'new': get(a:opts, 'new', 0) } 1412 | let s:jobs[a:name] = job 1413 | 1414 | if s:nvim 1415 | if has_key(a:opts, 'dir') 1416 | let job.cwd = a:opts.dir 1417 | endif 1418 | let argv = a:cmd 1419 | call extend(job, { 1420 | \ 'on_stdout': function('s:nvim_cb'), 1421 | \ 'on_stderr': function('s:nvim_cb'), 1422 | \ 'on_exit': function('s:nvim_cb'), 1423 | \ }) 1424 | let jid = s:plug_call('jobstart', argv, job) 1425 | if jid > 0 1426 | let job.jobid = jid 1427 | else 1428 | let job.running = 0 1429 | let job.error = 1 1430 | let job.lines = [jid < 0 ? argv[0].' is not executable' : 1431 | \ 'Invalid arguments (or job table is full)'] 1432 | endif 1433 | elseif s:vim8 1434 | let cmd = join(map(copy(a:cmd), 'plug#shellescape(v:val, {"script": 0})')) 1435 | if has_key(a:opts, 'dir') 1436 | let cmd = s:with_cd(cmd, a:opts.dir, 0) 1437 | endif 1438 | let argv = s:is_win ? ['cmd', '/s', '/c', '"'.cmd.'"'] : ['sh', '-c', cmd] 1439 | let jid = job_start(s:is_win ? join(argv, ' ') : argv, { 1440 | \ 'out_cb': function('s:job_cb', ['s:job_out_cb', job]), 1441 | \ 'err_cb': function('s:job_cb', ['s:job_out_cb', job]), 1442 | \ 'exit_cb': function('s:job_cb', ['s:job_exit_cb', job]), 1443 | \ 'err_mode': 'raw', 1444 | \ 'out_mode': 'raw' 1445 | \}) 1446 | if job_status(jid) == 'run' 1447 | let job.jobid = jid 1448 | else 1449 | let job.running = 0 1450 | let job.error = 1 1451 | let job.lines = ['Failed to start job'] 1452 | endif 1453 | else 1454 | let job.lines = s:lines(call('s:system', has_key(a:opts, 'dir') ? [a:cmd, a:opts.dir] : [a:cmd])) 1455 | let job.error = v:shell_error != 0 1456 | let job.running = 0 1457 | endif 1458 | endfunction 1459 | 1460 | function! s:reap(name) 1461 | let job = s:jobs[a:name] 1462 | if job.error 1463 | call add(s:update.errors, a:name) 1464 | elseif get(job, 'new', 0) 1465 | let s:update.new[a:name] = 1 1466 | endif 1467 | let s:update.bar .= job.error ? 'x' : '=' 1468 | 1469 | let bullet = job.error ? 'x' : '-' 1470 | let result = job.error ? join(job.lines, "\n") : s:last_non_empty_line(job.lines) 1471 | call s:log(bullet, a:name, empty(result) ? 'OK' : result) 1472 | call s:bar() 1473 | 1474 | call remove(s:jobs, a:name) 1475 | endfunction 1476 | 1477 | function! s:bar() 1478 | if s:switch_in() 1479 | let total = len(s:update.all) 1480 | call setline(1, (s:update.pull ? 'Updating' : 'Installing'). 1481 | \ ' plugins ('.len(s:update.bar).'/'.total.')') 1482 | call s:progress_bar(2, s:update.bar, total) 1483 | call s:switch_out() 1484 | endif 1485 | endfunction 1486 | 1487 | function! s:logpos(name) 1488 | let max = line('$') 1489 | for i in range(4, max > 4 ? max : 4) 1490 | if getline(i) =~# '^[-+x*] '.a:name.':' 1491 | for j in range(i + 1, max > 5 ? max : 5) 1492 | if getline(j) !~ '^ ' 1493 | return [i, j - 1] 1494 | endif 1495 | endfor 1496 | return [i, i] 1497 | endif 1498 | endfor 1499 | return [0, 0] 1500 | endfunction 1501 | 1502 | function! s:log(bullet, name, lines) 1503 | if s:switch_in() 1504 | let [b, e] = s:logpos(a:name) 1505 | if b > 0 1506 | silent execute printf('%d,%d d _', b, e) 1507 | if b > winheight('.') 1508 | let b = 4 1509 | endif 1510 | else 1511 | let b = 4 1512 | endif 1513 | " FIXME For some reason, nomodifiable is set after :d in vim8 1514 | setlocal modifiable 1515 | call append(b - 1, s:format_message(a:bullet, a:name, a:lines)) 1516 | call s:switch_out() 1517 | endif 1518 | endfunction 1519 | 1520 | function! s:update_vim() 1521 | let s:jobs = {} 1522 | 1523 | call s:bar() 1524 | call s:tick() 1525 | endfunction 1526 | 1527 | function! s:tick() 1528 | let pull = s:update.pull 1529 | let prog = s:progress_opt(s:nvim || s:vim8) 1530 | while 1 " Without TCO, Vim stack is bound to explode 1531 | if empty(s:update.todo) 1532 | if empty(s:jobs) && !s:update.fin 1533 | call s:update_finish() 1534 | let s:update.fin = 1 1535 | endif 1536 | return 1537 | endif 1538 | 1539 | let name = keys(s:update.todo)[0] 1540 | let spec = remove(s:update.todo, name) 1541 | let new = empty(globpath(spec.dir, '.git', 1)) 1542 | 1543 | call s:log(new ? '+' : '*', name, pull ? 'Updating ...' : 'Installing ...') 1544 | redraw 1545 | 1546 | let has_tag = has_key(spec, 'tag') 1547 | if !new 1548 | let [error, _] = s:git_validate(spec, 0) 1549 | if empty(error) 1550 | if pull 1551 | let cmd = s:git_version_requirement(2) ? ['git', '-c', 'credential.helper=', 'fetch'] : ['git', 'fetch'] 1552 | if has_tag && !empty(globpath(spec.dir, '.git/shallow')) 1553 | call extend(cmd, ['--depth', '99999999']) 1554 | endif 1555 | if !empty(prog) 1556 | call add(cmd, prog) 1557 | endif 1558 | call s:spawn(name, cmd, { 'dir': spec.dir }) 1559 | else 1560 | let s:jobs[name] = { 'running': 0, 'lines': ['Already installed'], 'error': 0 } 1561 | endif 1562 | else 1563 | let s:jobs[name] = { 'running': 0, 'lines': s:lines(error), 'error': 1 } 1564 | endif 1565 | else 1566 | let cmd = ['git', 'clone'] 1567 | if !has_tag 1568 | call extend(cmd, s:clone_opt) 1569 | endif 1570 | if !empty(prog) 1571 | call add(cmd, prog) 1572 | endif 1573 | call s:spawn(name, extend(cmd, [spec.uri, s:trim(spec.dir)]), { 'new': 1 }) 1574 | endif 1575 | 1576 | if !s:jobs[name].running 1577 | call s:reap(name) 1578 | endif 1579 | if len(s:jobs) >= s:update.threads 1580 | break 1581 | endif 1582 | endwhile 1583 | endfunction 1584 | 1585 | function! s:update_python() 1586 | let py_exe = has('python') ? 'python' : 'python3' 1587 | execute py_exe "<< EOF" 1588 | import datetime 1589 | import functools 1590 | import os 1591 | try: 1592 | import queue 1593 | except ImportError: 1594 | import Queue as queue 1595 | import random 1596 | import re 1597 | import shutil 1598 | import signal 1599 | import subprocess 1600 | import tempfile 1601 | import threading as thr 1602 | import time 1603 | import traceback 1604 | import vim 1605 | 1606 | G_NVIM = vim.eval("has('nvim')") == '1' 1607 | G_PULL = vim.eval('s:update.pull') == '1' 1608 | G_RETRIES = int(vim.eval('get(g:, "plug_retries", 2)')) + 1 1609 | G_TIMEOUT = int(vim.eval('get(g:, "plug_timeout", 60)')) 1610 | G_CLONE_OPT = ' '.join(vim.eval('s:clone_opt')) 1611 | G_PROGRESS = vim.eval('s:progress_opt(1)') 1612 | G_LOG_PROB = 1.0 / int(vim.eval('s:update.threads')) 1613 | G_STOP = thr.Event() 1614 | G_IS_WIN = vim.eval('s:is_win') == '1' 1615 | 1616 | class PlugError(Exception): 1617 | def __init__(self, msg): 1618 | self.msg = msg 1619 | class CmdTimedOut(PlugError): 1620 | pass 1621 | class CmdFailed(PlugError): 1622 | pass 1623 | class InvalidURI(PlugError): 1624 | pass 1625 | class Action(object): 1626 | INSTALL, UPDATE, ERROR, DONE = ['+', '*', 'x', '-'] 1627 | 1628 | class Buffer(object): 1629 | def __init__(self, lock, num_plugs, is_pull): 1630 | self.bar = '' 1631 | self.event = 'Updating' if is_pull else 'Installing' 1632 | self.lock = lock 1633 | self.maxy = int(vim.eval('winheight(".")')) 1634 | self.num_plugs = num_plugs 1635 | 1636 | def __where(self, name): 1637 | """ Find first line with name in current buffer. Return line num. """ 1638 | found, lnum = False, 0 1639 | matcher = re.compile('^[-+x*] {0}:'.format(name)) 1640 | for line in vim.current.buffer: 1641 | if matcher.search(line) is not None: 1642 | found = True 1643 | break 1644 | lnum += 1 1645 | 1646 | if not found: 1647 | lnum = -1 1648 | return lnum 1649 | 1650 | def header(self): 1651 | curbuf = vim.current.buffer 1652 | curbuf[0] = self.event + ' plugins ({0}/{1})'.format(len(self.bar), self.num_plugs) 1653 | 1654 | num_spaces = self.num_plugs - len(self.bar) 1655 | curbuf[1] = '[{0}{1}]'.format(self.bar, num_spaces * ' ') 1656 | 1657 | with self.lock: 1658 | vim.command('normal! 2G') 1659 | vim.command('redraw') 1660 | 1661 | def write(self, action, name, lines): 1662 | first, rest = lines[0], lines[1:] 1663 | msg = ['{0} {1}{2}{3}'.format(action, name, ': ' if first else '', first)] 1664 | msg.extend([' ' + line for line in rest]) 1665 | 1666 | try: 1667 | if action == Action.ERROR: 1668 | self.bar += 'x' 1669 | vim.command("call add(s:update.errors, '{0}')".format(name)) 1670 | elif action == Action.DONE: 1671 | self.bar += '=' 1672 | 1673 | curbuf = vim.current.buffer 1674 | lnum = self.__where(name) 1675 | if lnum != -1: # Found matching line num 1676 | del curbuf[lnum] 1677 | if lnum > self.maxy and action in set([Action.INSTALL, Action.UPDATE]): 1678 | lnum = 3 1679 | else: 1680 | lnum = 3 1681 | curbuf.append(msg, lnum) 1682 | 1683 | self.header() 1684 | except vim.error: 1685 | pass 1686 | 1687 | class Command(object): 1688 | CD = 'cd /d' if G_IS_WIN else 'cd' 1689 | 1690 | def __init__(self, cmd, cmd_dir=None, timeout=60, cb=None, clean=None): 1691 | self.cmd = cmd 1692 | if cmd_dir: 1693 | self.cmd = '{0} {1} && {2}'.format(Command.CD, cmd_dir, self.cmd) 1694 | self.timeout = timeout 1695 | self.callback = cb if cb else (lambda msg: None) 1696 | self.clean = clean if clean else (lambda: None) 1697 | self.proc = None 1698 | 1699 | @property 1700 | def alive(self): 1701 | """ Returns true only if command still running. """ 1702 | return self.proc and self.proc.poll() is None 1703 | 1704 | def execute(self, ntries=3): 1705 | """ Execute the command with ntries if CmdTimedOut. 1706 | Returns the output of the command if no Exception. 1707 | """ 1708 | attempt, finished, limit = 0, False, self.timeout 1709 | 1710 | while not finished: 1711 | try: 1712 | attempt += 1 1713 | result = self.try_command() 1714 | finished = True 1715 | return result 1716 | except CmdTimedOut: 1717 | if attempt != ntries: 1718 | self.notify_retry() 1719 | self.timeout += limit 1720 | else: 1721 | raise 1722 | 1723 | def notify_retry(self): 1724 | """ Retry required for command, notify user. """ 1725 | for count in range(3, 0, -1): 1726 | if G_STOP.is_set(): 1727 | raise KeyboardInterrupt 1728 | msg = 'Timeout. Will retry in {0} second{1} ...'.format( 1729 | count, 's' if count != 1 else '') 1730 | self.callback([msg]) 1731 | time.sleep(1) 1732 | self.callback(['Retrying ...']) 1733 | 1734 | def try_command(self): 1735 | """ Execute a cmd & poll for callback. Returns list of output. 1736 | Raises CmdFailed -> return code for Popen isn't 0 1737 | Raises CmdTimedOut -> command exceeded timeout without new output 1738 | """ 1739 | first_line = True 1740 | 1741 | try: 1742 | tfile = tempfile.NamedTemporaryFile(mode='w+b') 1743 | preexec_fn = not G_IS_WIN and os.setsid or None 1744 | self.proc = subprocess.Popen(self.cmd, stdout=tfile, 1745 | stderr=subprocess.STDOUT, 1746 | stdin=subprocess.PIPE, shell=True, 1747 | preexec_fn=preexec_fn) 1748 | thrd = thr.Thread(target=(lambda proc: proc.wait()), args=(self.proc,)) 1749 | thrd.start() 1750 | 1751 | thread_not_started = True 1752 | while thread_not_started: 1753 | try: 1754 | thrd.join(0.1) 1755 | thread_not_started = False 1756 | except RuntimeError: 1757 | pass 1758 | 1759 | while self.alive: 1760 | if G_STOP.is_set(): 1761 | raise KeyboardInterrupt 1762 | 1763 | if first_line or random.random() < G_LOG_PROB: 1764 | first_line = False 1765 | line = '' if G_IS_WIN else nonblock_read(tfile.name) 1766 | if line: 1767 | self.callback([line]) 1768 | 1769 | time_diff = time.time() - os.path.getmtime(tfile.name) 1770 | if time_diff > self.timeout: 1771 | raise CmdTimedOut(['Timeout!']) 1772 | 1773 | thrd.join(0.5) 1774 | 1775 | tfile.seek(0) 1776 | result = [line.decode('utf-8', 'replace').rstrip() for line in tfile] 1777 | 1778 | if self.proc.returncode != 0: 1779 | raise CmdFailed([''] + result) 1780 | 1781 | return result 1782 | except: 1783 | self.terminate() 1784 | raise 1785 | 1786 | def terminate(self): 1787 | """ Terminate process and cleanup. """ 1788 | if self.alive: 1789 | if G_IS_WIN: 1790 | os.kill(self.proc.pid, signal.SIGINT) 1791 | else: 1792 | os.killpg(self.proc.pid, signal.SIGTERM) 1793 | self.clean() 1794 | 1795 | class Plugin(object): 1796 | def __init__(self, name, args, buf_q, lock): 1797 | self.name = name 1798 | self.args = args 1799 | self.buf_q = buf_q 1800 | self.lock = lock 1801 | self.tag = args.get('tag', 0) 1802 | 1803 | def manage(self): 1804 | try: 1805 | if os.path.exists(self.args['dir']): 1806 | self.update() 1807 | else: 1808 | self.install() 1809 | with self.lock: 1810 | thread_vim_command("let s:update.new['{0}'] = 1".format(self.name)) 1811 | except PlugError as exc: 1812 | self.write(Action.ERROR, self.name, exc.msg) 1813 | except KeyboardInterrupt: 1814 | G_STOP.set() 1815 | self.write(Action.ERROR, self.name, ['Interrupted!']) 1816 | except: 1817 | # Any exception except those above print stack trace 1818 | msg = 'Trace:\n{0}'.format(traceback.format_exc().rstrip()) 1819 | self.write(Action.ERROR, self.name, msg.split('\n')) 1820 | raise 1821 | 1822 | def install(self): 1823 | target = self.args['dir'] 1824 | if target[-1] == '\\': 1825 | target = target[0:-1] 1826 | 1827 | def clean(target): 1828 | def _clean(): 1829 | try: 1830 | shutil.rmtree(target) 1831 | except OSError: 1832 | pass 1833 | return _clean 1834 | 1835 | self.write(Action.INSTALL, self.name, ['Installing ...']) 1836 | callback = functools.partial(self.write, Action.INSTALL, self.name) 1837 | cmd = 'git clone {0} {1} {2} {3} 2>&1'.format( 1838 | '' if self.tag else G_CLONE_OPT, G_PROGRESS, self.args['uri'], 1839 | esc(target)) 1840 | com = Command(cmd, None, G_TIMEOUT, callback, clean(target)) 1841 | result = com.execute(G_RETRIES) 1842 | self.write(Action.DONE, self.name, result[-1:]) 1843 | 1844 | def repo_uri(self): 1845 | cmd = 'git rev-parse --abbrev-ref HEAD 2>&1 && git config -f .git/config remote.origin.url' 1846 | command = Command(cmd, self.args['dir'], G_TIMEOUT,) 1847 | result = command.execute(G_RETRIES) 1848 | return result[-1] 1849 | 1850 | def update(self): 1851 | actual_uri = self.repo_uri() 1852 | expect_uri = self.args['uri'] 1853 | regex = re.compile(r'^(?:\w+://)?(?:[^@/]*@)?([^:/]*(?::[0-9]*)?)[:/](.*?)(?:\.git)?/?$') 1854 | ma = regex.match(actual_uri) 1855 | mb = regex.match(expect_uri) 1856 | if ma is None or mb is None or ma.groups() != mb.groups(): 1857 | msg = ['', 1858 | 'Invalid URI: {0}'.format(actual_uri), 1859 | 'Expected {0}'.format(expect_uri), 1860 | 'PlugClean required.'] 1861 | raise InvalidURI(msg) 1862 | 1863 | if G_PULL: 1864 | self.write(Action.UPDATE, self.name, ['Updating ...']) 1865 | callback = functools.partial(self.write, Action.UPDATE, self.name) 1866 | fetch_opt = '--depth 99999999' if self.tag and os.path.isfile(os.path.join(self.args['dir'], '.git/shallow')) else '' 1867 | cmd = 'git fetch {0} {1} 2>&1'.format(fetch_opt, G_PROGRESS) 1868 | com = Command(cmd, self.args['dir'], G_TIMEOUT, callback) 1869 | result = com.execute(G_RETRIES) 1870 | self.write(Action.DONE, self.name, result[-1:]) 1871 | else: 1872 | self.write(Action.DONE, self.name, ['Already installed']) 1873 | 1874 | def write(self, action, name, msg): 1875 | self.buf_q.put((action, name, msg)) 1876 | 1877 | class PlugThread(thr.Thread): 1878 | def __init__(self, tname, args): 1879 | super(PlugThread, self).__init__() 1880 | self.tname = tname 1881 | self.args = args 1882 | 1883 | def run(self): 1884 | thr.current_thread().name = self.tname 1885 | buf_q, work_q, lock = self.args 1886 | 1887 | try: 1888 | while not G_STOP.is_set(): 1889 | name, args = work_q.get_nowait() 1890 | plug = Plugin(name, args, buf_q, lock) 1891 | plug.manage() 1892 | work_q.task_done() 1893 | except queue.Empty: 1894 | pass 1895 | 1896 | class RefreshThread(thr.Thread): 1897 | def __init__(self, lock): 1898 | super(RefreshThread, self).__init__() 1899 | self.lock = lock 1900 | self.running = True 1901 | 1902 | def run(self): 1903 | while self.running: 1904 | with self.lock: 1905 | thread_vim_command('noautocmd normal! a') 1906 | time.sleep(0.33) 1907 | 1908 | def stop(self): 1909 | self.running = False 1910 | 1911 | if G_NVIM: 1912 | def thread_vim_command(cmd): 1913 | vim.session.threadsafe_call(lambda: vim.command(cmd)) 1914 | else: 1915 | def thread_vim_command(cmd): 1916 | vim.command(cmd) 1917 | 1918 | def esc(name): 1919 | return '"' + name.replace('"', '\"') + '"' 1920 | 1921 | def nonblock_read(fname): 1922 | """ Read a file with nonblock flag. Return the last line. """ 1923 | fread = os.open(fname, os.O_RDONLY | os.O_NONBLOCK) 1924 | buf = os.read(fread, 100000).decode('utf-8', 'replace') 1925 | os.close(fread) 1926 | 1927 | line = buf.rstrip('\r\n') 1928 | left = max(line.rfind('\r'), line.rfind('\n')) 1929 | if left != -1: 1930 | left += 1 1931 | line = line[left:] 1932 | 1933 | return line 1934 | 1935 | def main(): 1936 | thr.current_thread().name = 'main' 1937 | nthreads = int(vim.eval('s:update.threads')) 1938 | plugs = vim.eval('s:update.todo') 1939 | mac_gui = vim.eval('s:mac_gui') == '1' 1940 | 1941 | lock = thr.Lock() 1942 | buf = Buffer(lock, len(plugs), G_PULL) 1943 | buf_q, work_q = queue.Queue(), queue.Queue() 1944 | for work in plugs.items(): 1945 | work_q.put(work) 1946 | 1947 | start_cnt = thr.active_count() 1948 | for num in range(nthreads): 1949 | tname = 'PlugT-{0:02}'.format(num) 1950 | thread = PlugThread(tname, (buf_q, work_q, lock)) 1951 | thread.start() 1952 | if mac_gui: 1953 | rthread = RefreshThread(lock) 1954 | rthread.start() 1955 | 1956 | while not buf_q.empty() or thr.active_count() != start_cnt: 1957 | try: 1958 | action, name, msg = buf_q.get(True, 0.25) 1959 | buf.write(action, name, ['OK'] if not msg else msg) 1960 | buf_q.task_done() 1961 | except queue.Empty: 1962 | pass 1963 | except KeyboardInterrupt: 1964 | G_STOP.set() 1965 | 1966 | if mac_gui: 1967 | rthread.stop() 1968 | rthread.join() 1969 | 1970 | main() 1971 | EOF 1972 | endfunction 1973 | 1974 | function! s:update_ruby() 1975 | ruby << EOF 1976 | module PlugStream 1977 | SEP = ["\r", "\n", nil] 1978 | def get_line 1979 | buffer = '' 1980 | loop do 1981 | char = readchar rescue return 1982 | if SEP.include? char.chr 1983 | buffer << $/ 1984 | break 1985 | else 1986 | buffer << char 1987 | end 1988 | end 1989 | buffer 1990 | end 1991 | end unless defined?(PlugStream) 1992 | 1993 | def esc arg 1994 | %["#{arg.gsub('"', '\"')}"] 1995 | end 1996 | 1997 | def killall pid 1998 | pids = [pid] 1999 | if /mswin|mingw|bccwin/ =~ RUBY_PLATFORM 2000 | pids.each { |pid| Process.kill 'INT', pid.to_i rescue nil } 2001 | else 2002 | unless `which pgrep 2> /dev/null`.empty? 2003 | children = pids 2004 | until children.empty? 2005 | children = children.map { |pid| 2006 | `pgrep -P #{pid}`.lines.map { |l| l.chomp } 2007 | }.flatten 2008 | pids += children 2009 | end 2010 | end 2011 | pids.each { |pid| Process.kill 'TERM', pid.to_i rescue nil } 2012 | end 2013 | end 2014 | 2015 | def compare_git_uri a, b 2016 | regex = %r{^(?:\w+://)?(?:[^@/]*@)?([^:/]*(?::[0-9]*)?)[:/](.*?)(?:\.git)?/?$} 2017 | regex.match(a).to_a.drop(1) == regex.match(b).to_a.drop(1) 2018 | end 2019 | 2020 | require 'thread' 2021 | require 'fileutils' 2022 | require 'timeout' 2023 | running = true 2024 | iswin = VIM::evaluate('s:is_win').to_i == 1 2025 | pull = VIM::evaluate('s:update.pull').to_i == 1 2026 | base = VIM::evaluate('g:plug_home') 2027 | all = VIM::evaluate('s:update.todo') 2028 | limit = VIM::evaluate('get(g:, "plug_timeout", 60)') 2029 | tries = VIM::evaluate('get(g:, "plug_retries", 2)') + 1 2030 | nthr = VIM::evaluate('s:update.threads').to_i 2031 | maxy = VIM::evaluate('winheight(".")').to_i 2032 | vim7 = VIM::evaluate('v:version').to_i <= 703 && RUBY_PLATFORM =~ /darwin/ 2033 | cd = iswin ? 'cd /d' : 'cd' 2034 | tot = VIM::evaluate('len(s:update.todo)') || 0 2035 | bar = '' 2036 | skip = 'Already installed' 2037 | mtx = Mutex.new 2038 | take1 = proc { mtx.synchronize { running && all.shift } } 2039 | logh = proc { 2040 | cnt = bar.length 2041 | $curbuf[1] = "#{pull ? 'Updating' : 'Installing'} plugins (#{cnt}/#{tot})" 2042 | $curbuf[2] = '[' + bar.ljust(tot) + ']' 2043 | VIM::command('normal! 2G') 2044 | VIM::command('redraw') 2045 | } 2046 | where = proc { |name| (1..($curbuf.length)).find { |l| $curbuf[l] =~ /^[-+x*] #{name}:/ } } 2047 | log = proc { |name, result, type| 2048 | mtx.synchronize do 2049 | ing = ![true, false].include?(type) 2050 | bar += type ? '=' : 'x' unless ing 2051 | b = case type 2052 | when :install then '+' when :update then '*' 2053 | when true, nil then '-' else 2054 | VIM::command("call add(s:update.errors, '#{name}')") 2055 | 'x' 2056 | end 2057 | result = 2058 | if type || type.nil? 2059 | ["#{b} #{name}: #{result.lines.to_a.last || 'OK'}"] 2060 | elsif result =~ /^Interrupted|^Timeout/ 2061 | ["#{b} #{name}: #{result}"] 2062 | else 2063 | ["#{b} #{name}"] + result.lines.map { |l| " " << l } 2064 | end 2065 | if lnum = where.call(name) 2066 | $curbuf.delete lnum 2067 | lnum = 4 if ing && lnum > maxy 2068 | end 2069 | result.each_with_index do |line, offset| 2070 | $curbuf.append((lnum || 4) - 1 + offset, line.gsub(/\e\[./, '').chomp) 2071 | end 2072 | logh.call 2073 | end 2074 | } 2075 | bt = proc { |cmd, name, type, cleanup| 2076 | tried = timeout = 0 2077 | begin 2078 | tried += 1 2079 | timeout += limit 2080 | fd = nil 2081 | data = '' 2082 | if iswin 2083 | Timeout::timeout(timeout) do 2084 | tmp = VIM::evaluate('tempname()') 2085 | system("(#{cmd}) > #{tmp}") 2086 | data = File.read(tmp).chomp 2087 | File.unlink tmp rescue nil 2088 | end 2089 | else 2090 | fd = IO.popen(cmd).extend(PlugStream) 2091 | first_line = true 2092 | log_prob = 1.0 / nthr 2093 | while line = Timeout::timeout(timeout) { fd.get_line } 2094 | data << line 2095 | log.call name, line.chomp, type if name && (first_line || rand < log_prob) 2096 | first_line = false 2097 | end 2098 | fd.close 2099 | end 2100 | [$? == 0, data.chomp] 2101 | rescue Timeout::Error, Interrupt => e 2102 | if fd && !fd.closed? 2103 | killall fd.pid 2104 | fd.close 2105 | end 2106 | cleanup.call if cleanup 2107 | if e.is_a?(Timeout::Error) && tried < tries 2108 | 3.downto(1) do |countdown| 2109 | s = countdown > 1 ? 's' : '' 2110 | log.call name, "Timeout. Will retry in #{countdown} second#{s} ...", type 2111 | sleep 1 2112 | end 2113 | log.call name, 'Retrying ...', type 2114 | retry 2115 | end 2116 | [false, e.is_a?(Interrupt) ? "Interrupted!" : "Timeout!"] 2117 | end 2118 | } 2119 | main = Thread.current 2120 | threads = [] 2121 | watcher = Thread.new { 2122 | if vim7 2123 | while VIM::evaluate('getchar(1)') 2124 | sleep 0.1 2125 | end 2126 | else 2127 | require 'io/console' # >= Ruby 1.9 2128 | nil until IO.console.getch == 3.chr 2129 | end 2130 | mtx.synchronize do 2131 | running = false 2132 | threads.each { |t| t.raise Interrupt } unless vim7 2133 | end 2134 | threads.each { |t| t.join rescue nil } 2135 | main.kill 2136 | } 2137 | refresh = Thread.new { 2138 | while true 2139 | mtx.synchronize do 2140 | break unless running 2141 | VIM::command('noautocmd normal! a') 2142 | end 2143 | sleep 0.2 2144 | end 2145 | } if VIM::evaluate('s:mac_gui') == 1 2146 | 2147 | clone_opt = VIM::evaluate('s:clone_opt').join(' ') 2148 | progress = VIM::evaluate('s:progress_opt(1)') 2149 | nthr.times do 2150 | mtx.synchronize do 2151 | threads << Thread.new { 2152 | while pair = take1.call 2153 | name = pair.first 2154 | dir, uri, tag = pair.last.values_at *%w[dir uri tag] 2155 | exists = File.directory? dir 2156 | ok, result = 2157 | if exists 2158 | chdir = "#{cd} #{iswin ? dir : esc(dir)}" 2159 | ret, data = bt.call "#{chdir} && git rev-parse --abbrev-ref HEAD 2>&1 && git config -f .git/config remote.origin.url", nil, nil, nil 2160 | current_uri = data.lines.to_a.last 2161 | if !ret 2162 | if data =~ /^Interrupted|^Timeout/ 2163 | [false, data] 2164 | else 2165 | [false, [data.chomp, "PlugClean required."].join($/)] 2166 | end 2167 | elsif !compare_git_uri(current_uri, uri) 2168 | [false, ["Invalid URI: #{current_uri}", 2169 | "Expected: #{uri}", 2170 | "PlugClean required."].join($/)] 2171 | else 2172 | if pull 2173 | log.call name, 'Updating ...', :update 2174 | fetch_opt = (tag && File.exist?(File.join(dir, '.git/shallow'))) ? '--depth 99999999' : '' 2175 | bt.call "#{chdir} && git fetch #{fetch_opt} #{progress} 2>&1", name, :update, nil 2176 | else 2177 | [true, skip] 2178 | end 2179 | end 2180 | else 2181 | d = esc dir.sub(%r{[\\/]+$}, '') 2182 | log.call name, 'Installing ...', :install 2183 | bt.call "git clone #{clone_opt unless tag} #{progress} #{uri} #{d} 2>&1", name, :install, proc { 2184 | FileUtils.rm_rf dir 2185 | } 2186 | end 2187 | mtx.synchronize { VIM::command("let s:update.new['#{name}'] = 1") } if !exists && ok 2188 | log.call name, result, ok 2189 | end 2190 | } if running 2191 | end 2192 | end 2193 | threads.each { |t| t.join rescue nil } 2194 | logh.call 2195 | refresh.kill if refresh 2196 | watcher.kill 2197 | EOF 2198 | endfunction 2199 | 2200 | function! s:shellesc_cmd(arg, script) 2201 | let escaped = substitute('"'.a:arg.'"', '[&|<>()@^!"]', '^&', 'g') 2202 | return substitute(escaped, '%', (a:script ? '%' : '^') . '&', 'g') 2203 | endfunction 2204 | 2205 | function! s:shellesc_ps1(arg) 2206 | return "'".substitute(escape(a:arg, '\"'), "'", "''", 'g')."'" 2207 | endfunction 2208 | 2209 | function! s:shellesc_sh(arg) 2210 | return "'".substitute(a:arg, "'", "'\\\\''", 'g')."'" 2211 | endfunction 2212 | 2213 | " Escape the shell argument based on the shell. 2214 | " Vim and Neovim's shellescape() are insufficient. 2215 | " 1. shellslash determines whether to use single/double quotes. 2216 | " Double-quote escaping is fragile for cmd.exe. 2217 | " 2. It does not work for powershell. 2218 | " 3. It does not work for *sh shells if the command is executed 2219 | " via cmd.exe (ie. cmd.exe /c sh -c command command_args) 2220 | " 4. It does not support batchfile syntax. 2221 | " 2222 | " Accepts an optional dictionary with the following keys: 2223 | " - shell: same as Vim/Neovim 'shell' option. 2224 | " If unset, fallback to 'cmd.exe' on Windows or 'sh'. 2225 | " - script: If truthy and shell is cmd.exe, escape for batchfile syntax. 2226 | function! plug#shellescape(arg, ...) 2227 | if a:arg =~# '^[A-Za-z0-9_/:.-]\+$' 2228 | return a:arg 2229 | endif 2230 | let opts = a:0 > 0 && type(a:1) == s:TYPE.dict ? a:1 : {} 2231 | let shell = get(opts, 'shell', s:is_win ? 'cmd.exe' : 'sh') 2232 | let script = get(opts, 'script', 1) 2233 | if shell =~# 'cmd\(\.exe\)\?$' 2234 | return s:shellesc_cmd(a:arg, script) 2235 | elseif s:is_powershell(shell) 2236 | return s:shellesc_ps1(a:arg) 2237 | endif 2238 | return s:shellesc_sh(a:arg) 2239 | endfunction 2240 | 2241 | function! s:glob_dir(path) 2242 | return map(filter(s:glob(a:path, '**'), 'isdirectory(v:val)'), 's:dirpath(v:val)') 2243 | endfunction 2244 | 2245 | function! s:progress_bar(line, bar, total) 2246 | call setline(a:line, '[' . s:lpad(a:bar, a:total) . ']') 2247 | endfunction 2248 | 2249 | function! s:compare_git_uri(a, b) 2250 | " See `git help clone' 2251 | " https:// [user@] github.com[:port] / junegunn/vim-plug [.git] 2252 | " [git@] github.com[:port] : junegunn/vim-plug [.git] 2253 | " file:// / junegunn/vim-plug [/] 2254 | " / junegunn/vim-plug [/] 2255 | let pat = '^\%(\w\+://\)\='.'\%([^@/]*@\)\='.'\([^:/]*\%(:[0-9]*\)\=\)'.'[:/]'.'\(.\{-}\)'.'\%(\.git\)\=/\?$' 2256 | let ma = matchlist(a:a, pat) 2257 | let mb = matchlist(a:b, pat) 2258 | return ma[1:2] ==# mb[1:2] 2259 | endfunction 2260 | 2261 | function! s:format_message(bullet, name, message) 2262 | if a:bullet != 'x' 2263 | return [printf('%s %s: %s', a:bullet, a:name, s:lastline(a:message))] 2264 | else 2265 | let lines = map(s:lines(a:message), '" ".v:val') 2266 | return extend([printf('x %s:', a:name)], lines) 2267 | endif 2268 | endfunction 2269 | 2270 | function! s:with_cd(cmd, dir, ...) 2271 | let script = a:0 > 0 ? a:1 : 1 2272 | return printf('cd%s %s && %s', s:is_win ? ' /d' : '', plug#shellescape(a:dir, {'script': script}), a:cmd) 2273 | endfunction 2274 | 2275 | function! s:system(cmd, ...) 2276 | let batchfile = '' 2277 | try 2278 | let [sh, shellcmdflag, shrd] = s:chsh(1) 2279 | if type(a:cmd) == s:TYPE.list 2280 | " Neovim's system() supports list argument to bypass the shell 2281 | " but it cannot set the working directory for the command. 2282 | " Assume that the command does not rely on the shell. 2283 | if has('nvim') && a:0 == 0 2284 | return system(a:cmd) 2285 | endif 2286 | let cmd = join(map(copy(a:cmd), 'plug#shellescape(v:val, {"shell": &shell, "script": 0})')) 2287 | if s:is_powershell(&shell) 2288 | let cmd = '& ' . cmd 2289 | endif 2290 | else 2291 | let cmd = a:cmd 2292 | endif 2293 | if a:0 > 0 2294 | let cmd = s:with_cd(cmd, a:1, type(a:cmd) != s:TYPE.list) 2295 | endif 2296 | if s:is_win && type(a:cmd) != s:TYPE.list 2297 | let [batchfile, cmd] = s:batchfile(cmd) 2298 | endif 2299 | return system(cmd) 2300 | finally 2301 | let [&shell, &shellcmdflag, &shellredir] = [sh, shellcmdflag, shrd] 2302 | if s:is_win && filereadable(batchfile) 2303 | call delete(batchfile) 2304 | endif 2305 | endtry 2306 | endfunction 2307 | 2308 | function! s:system_chomp(...) 2309 | let ret = call('s:system', a:000) 2310 | return v:shell_error ? '' : substitute(ret, '\n$', '', '') 2311 | endfunction 2312 | 2313 | function! s:git_validate(spec, check_branch) 2314 | let err = '' 2315 | if isdirectory(a:spec.dir) 2316 | let result = [s:git_local_branch(a:spec.dir), s:git_origin_url(a:spec.dir)] 2317 | let remote = result[-1] 2318 | if empty(remote) 2319 | let err = join([remote, 'PlugClean required.'], "\n") 2320 | elseif !s:compare_git_uri(remote, a:spec.uri) 2321 | let err = join(['Invalid URI: '.remote, 2322 | \ 'Expected: '.a:spec.uri, 2323 | \ 'PlugClean required.'], "\n") 2324 | elseif a:check_branch && has_key(a:spec, 'commit') 2325 | let sha = s:git_revision(a:spec.dir) 2326 | if empty(sha) 2327 | let err = join(add(result, 'PlugClean required.'), "\n") 2328 | elseif !s:hash_match(sha, a:spec.commit) 2329 | let err = join([printf('Invalid HEAD (expected: %s, actual: %s)', 2330 | \ a:spec.commit[:6], sha[:6]), 2331 | \ 'PlugUpdate required.'], "\n") 2332 | endif 2333 | elseif a:check_branch 2334 | let current_branch = result[0] 2335 | " Check tag 2336 | let origin_branch = s:git_origin_branch(a:spec) 2337 | if has_key(a:spec, 'tag') 2338 | let tag = s:system_chomp('git describe --exact-match --tags HEAD 2>&1', a:spec.dir) 2339 | if a:spec.tag !=# tag && a:spec.tag !~ '\*' 2340 | let err = printf('Invalid tag: %s (expected: %s). Try PlugUpdate.', 2341 | \ (empty(tag) ? 'N/A' : tag), a:spec.tag) 2342 | endif 2343 | " Check branch 2344 | elseif origin_branch !=# current_branch 2345 | let err = printf('Invalid branch: %s (expected: %s). Try PlugUpdate.', 2346 | \ current_branch, origin_branch) 2347 | endif 2348 | if empty(err) 2349 | let [ahead, behind] = split(s:lastline(s:system([ 2350 | \ 'git', 'rev-list', '--count', '--left-right', 2351 | \ printf('HEAD...origin/%s', origin_branch) 2352 | \ ], a:spec.dir)), '\t') 2353 | if !v:shell_error && ahead 2354 | if behind 2355 | " Only mention PlugClean if diverged, otherwise it's likely to be 2356 | " pushable (and probably not that messed up). 2357 | let err = printf( 2358 | \ "Diverged from origin/%s (%d commit(s) ahead and %d commit(s) behind!\n" 2359 | \ .'Backup local changes and run PlugClean and PlugUpdate to reinstall it.', origin_branch, ahead, behind) 2360 | else 2361 | let err = printf("Ahead of origin/%s by %d commit(s).\n" 2362 | \ .'Cannot update until local changes are pushed.', 2363 | \ origin_branch, ahead) 2364 | endif 2365 | endif 2366 | endif 2367 | endif 2368 | else 2369 | let err = 'Not found' 2370 | endif 2371 | return [err, err =~# 'PlugClean'] 2372 | endfunction 2373 | 2374 | function! s:rm_rf(dir) 2375 | if isdirectory(a:dir) 2376 | return s:system(s:is_win 2377 | \ ? 'rmdir /S /Q '.plug#shellescape(a:dir) 2378 | \ : ['rm', '-rf', a:dir]) 2379 | endif 2380 | endfunction 2381 | 2382 | function! s:clean(force) 2383 | call s:prepare() 2384 | call append(0, 'Searching for invalid plugins in '.g:plug_home) 2385 | call append(1, '') 2386 | 2387 | " List of valid directories 2388 | let dirs = [] 2389 | let errs = {} 2390 | let [cnt, total] = [0, len(g:plugs)] 2391 | for [name, spec] in items(g:plugs) 2392 | if !s:is_managed(name) 2393 | call add(dirs, spec.dir) 2394 | else 2395 | let [err, clean] = s:git_validate(spec, 1) 2396 | if clean 2397 | let errs[spec.dir] = s:lines(err)[0] 2398 | else 2399 | call add(dirs, spec.dir) 2400 | endif 2401 | endif 2402 | let cnt += 1 2403 | call s:progress_bar(2, repeat('=', cnt), total) 2404 | normal! 2G 2405 | redraw 2406 | endfor 2407 | 2408 | let allowed = {} 2409 | for dir in dirs 2410 | let allowed[s:dirpath(s:plug_fnamemodify(dir, ':h:h'))] = 1 2411 | let allowed[dir] = 1 2412 | for child in s:glob_dir(dir) 2413 | let allowed[child] = 1 2414 | endfor 2415 | endfor 2416 | 2417 | let todo = [] 2418 | let found = sort(s:glob_dir(g:plug_home)) 2419 | while !empty(found) 2420 | let f = remove(found, 0) 2421 | if !has_key(allowed, f) && isdirectory(f) 2422 | call add(todo, f) 2423 | call append(line('$'), '- ' . f) 2424 | if has_key(errs, f) 2425 | call append(line('$'), ' ' . errs[f]) 2426 | endif 2427 | let found = filter(found, 'stridx(v:val, f) != 0') 2428 | end 2429 | endwhile 2430 | 2431 | 4 2432 | redraw 2433 | if empty(todo) 2434 | call append(line('$'), 'Already clean.') 2435 | else 2436 | let s:clean_count = 0 2437 | call append(3, ['Directories to delete:', '']) 2438 | redraw! 2439 | if a:force || s:ask_no_interrupt('Delete all directories?') 2440 | call s:delete([6, line('$')], 1) 2441 | else 2442 | call setline(4, 'Cancelled.') 2443 | nnoremap d :set opfunc=delete_opg@ 2444 | nmap dd d_ 2445 | xnoremap d :call delete_op(visualmode(), 1) 2446 | echo 'Delete the lines (d{motion}) to delete the corresponding directories' 2447 | endif 2448 | endif 2449 | 4 2450 | setlocal nomodifiable 2451 | endfunction 2452 | 2453 | function! s:delete_op(type, ...) 2454 | call s:delete(a:0 ? [line("'<"), line("'>")] : [line("'["), line("']")], 0) 2455 | endfunction 2456 | 2457 | function! s:delete(range, force) 2458 | let [l1, l2] = a:range 2459 | let force = a:force 2460 | let err_count = 0 2461 | while l1 <= l2 2462 | let line = getline(l1) 2463 | if line =~ '^- ' && isdirectory(line[2:]) 2464 | execute l1 2465 | redraw! 2466 | let answer = force ? 1 : s:ask('Delete '.line[2:].'?', 1) 2467 | let force = force || answer > 1 2468 | if answer 2469 | let err = s:rm_rf(line[2:]) 2470 | setlocal modifiable 2471 | if empty(err) 2472 | call setline(l1, '~'.line[1:]) 2473 | let s:clean_count += 1 2474 | else 2475 | delete _ 2476 | call append(l1 - 1, s:format_message('x', line[1:], err)) 2477 | let l2 += len(s:lines(err)) 2478 | let err_count += 1 2479 | endif 2480 | let msg = printf('Removed %d directories.', s:clean_count) 2481 | if err_count > 0 2482 | let msg .= printf(' Failed to remove %d directories.', err_count) 2483 | endif 2484 | call setline(4, msg) 2485 | setlocal nomodifiable 2486 | endif 2487 | endif 2488 | let l1 += 1 2489 | endwhile 2490 | endfunction 2491 | 2492 | function! s:upgrade() 2493 | echo 'Downloading the latest version of vim-plug' 2494 | redraw 2495 | let tmp = s:plug_tempname() 2496 | let new = tmp . '/plug.vim' 2497 | 2498 | try 2499 | let out = s:system(['git', 'clone', '--depth', '1', s:plug_src, tmp]) 2500 | if v:shell_error 2501 | return s:err('Error upgrading vim-plug: '. out) 2502 | endif 2503 | 2504 | if readfile(s:me) ==# readfile(new) 2505 | echo 'vim-plug is already up-to-date' 2506 | return 0 2507 | else 2508 | call rename(s:me, s:me . '.old') 2509 | call rename(new, s:me) 2510 | unlet g:loaded_plug 2511 | echo 'vim-plug has been upgraded' 2512 | return 1 2513 | endif 2514 | finally 2515 | silent! call s:rm_rf(tmp) 2516 | endtry 2517 | endfunction 2518 | 2519 | function! s:upgrade_specs() 2520 | for spec in values(g:plugs) 2521 | let spec.frozen = get(spec, 'frozen', 0) 2522 | endfor 2523 | endfunction 2524 | 2525 | function! s:status() 2526 | call s:prepare() 2527 | call append(0, 'Checking plugins') 2528 | call append(1, '') 2529 | 2530 | let ecnt = 0 2531 | let unloaded = 0 2532 | let [cnt, total] = [0, len(g:plugs)] 2533 | for [name, spec] in items(g:plugs) 2534 | let is_dir = isdirectory(spec.dir) 2535 | if has_key(spec, 'uri') 2536 | if is_dir 2537 | let [err, _] = s:git_validate(spec, 1) 2538 | let [valid, msg] = [empty(err), empty(err) ? 'OK' : err] 2539 | else 2540 | let [valid, msg] = [0, 'Not found. Try PlugInstall.'] 2541 | endif 2542 | else 2543 | if is_dir 2544 | let [valid, msg] = [1, 'OK'] 2545 | else 2546 | let [valid, msg] = [0, 'Not found.'] 2547 | endif 2548 | endif 2549 | let cnt += 1 2550 | let ecnt += !valid 2551 | " `s:loaded` entry can be missing if PlugUpgraded 2552 | if is_dir && get(s:loaded, name, -1) == 0 2553 | let unloaded = 1 2554 | let msg .= ' (not loaded)' 2555 | endif 2556 | call s:progress_bar(2, repeat('=', cnt), total) 2557 | call append(3, s:format_message(valid ? '-' : 'x', name, msg)) 2558 | normal! 2G 2559 | redraw 2560 | endfor 2561 | call setline(1, 'Finished. '.ecnt.' error(s).') 2562 | normal! gg 2563 | setlocal nomodifiable 2564 | if unloaded 2565 | echo "Press 'L' on each line to load plugin, or 'U' to update" 2566 | nnoremap L :call status_load(line('.')) 2567 | xnoremap L :call status_load(line('.')) 2568 | end 2569 | endfunction 2570 | 2571 | function! s:extract_name(str, prefix, suffix) 2572 | return matchstr(a:str, '^'.a:prefix.' \zs[^:]\+\ze:.*'.a:suffix.'$') 2573 | endfunction 2574 | 2575 | function! s:status_load(lnum) 2576 | let line = getline(a:lnum) 2577 | let name = s:extract_name(line, '-', '(not loaded)') 2578 | if !empty(name) 2579 | call plug#load(name) 2580 | setlocal modifiable 2581 | call setline(a:lnum, substitute(line, ' (not loaded)$', '', '')) 2582 | setlocal nomodifiable 2583 | endif 2584 | endfunction 2585 | 2586 | function! s:status_update() range 2587 | let lines = getline(a:firstline, a:lastline) 2588 | let names = filter(map(lines, 's:extract_name(v:val, "[x-]", "")'), '!empty(v:val)') 2589 | if !empty(names) 2590 | echo 2591 | execute 'PlugUpdate' join(names) 2592 | endif 2593 | endfunction 2594 | 2595 | function! s:is_preview_window_open() 2596 | silent! wincmd P 2597 | if &previewwindow 2598 | wincmd p 2599 | return 1 2600 | endif 2601 | endfunction 2602 | 2603 | function! s:find_name(lnum) 2604 | for lnum in reverse(range(1, a:lnum)) 2605 | let line = getline(lnum) 2606 | if empty(line) 2607 | return '' 2608 | endif 2609 | let name = s:extract_name(line, '-', '') 2610 | if !empty(name) 2611 | return name 2612 | endif 2613 | endfor 2614 | return '' 2615 | endfunction 2616 | 2617 | function! s:preview_commit() 2618 | if b:plug_preview < 0 2619 | let b:plug_preview = !s:is_preview_window_open() 2620 | endif 2621 | 2622 | let sha = matchstr(getline('.'), '^ \X*\zs[0-9a-f]\{7,9}') 2623 | if empty(sha) 2624 | let name = matchstr(getline('.'), '^- \zs[^:]*\ze:$') 2625 | if empty(name) 2626 | return 2627 | endif 2628 | let title = 'HEAD@{1}..' 2629 | let command = 'git diff --no-color HEAD@{1}' 2630 | else 2631 | let title = sha 2632 | let command = 'git show --no-color --pretty=medium '.sha 2633 | let name = s:find_name(line('.')) 2634 | endif 2635 | 2636 | if empty(name) || !has_key(g:plugs, name) || !isdirectory(g:plugs[name].dir) 2637 | return 2638 | endif 2639 | 2640 | if exists('g:plug_pwindow') && !s:is_preview_window_open() 2641 | execute g:plug_pwindow 2642 | execute 'e' title 2643 | else 2644 | execute 'pedit' title 2645 | wincmd P 2646 | endif 2647 | setlocal previewwindow filetype=git buftype=nofile bufhidden=wipe nobuflisted modifiable 2648 | let batchfile = '' 2649 | try 2650 | let [sh, shellcmdflag, shrd] = s:chsh(1) 2651 | let cmd = 'cd '.plug#shellescape(g:plugs[name].dir).' && '.command 2652 | if s:is_win 2653 | let [batchfile, cmd] = s:batchfile(cmd) 2654 | endif 2655 | execute 'silent %!' cmd 2656 | finally 2657 | let [&shell, &shellcmdflag, &shellredir] = [sh, shellcmdflag, shrd] 2658 | if s:is_win && filereadable(batchfile) 2659 | call delete(batchfile) 2660 | endif 2661 | endtry 2662 | setlocal nomodifiable 2663 | nnoremap q :q 2664 | wincmd p 2665 | endfunction 2666 | 2667 | function! s:section(flags) 2668 | call search('\(^[x-] \)\@<=[^:]\+:', a:flags) 2669 | endfunction 2670 | 2671 | function! s:format_git_log(line) 2672 | let indent = ' ' 2673 | let tokens = split(a:line, nr2char(1)) 2674 | if len(tokens) != 5 2675 | return indent.substitute(a:line, '\s*$', '', '') 2676 | endif 2677 | let [graph, sha, refs, subject, date] = tokens 2678 | let tag = matchstr(refs, 'tag: [^,)]\+') 2679 | let tag = empty(tag) ? ' ' : ' ('.tag.') ' 2680 | return printf('%s%s%s%s%s (%s)', indent, graph, sha, tag, subject, date) 2681 | endfunction 2682 | 2683 | function! s:append_ul(lnum, text) 2684 | call append(a:lnum, ['', a:text, repeat('-', len(a:text))]) 2685 | endfunction 2686 | 2687 | function! s:diff() 2688 | call s:prepare() 2689 | call append(0, ['Collecting changes ...', '']) 2690 | let cnts = [0, 0] 2691 | let bar = '' 2692 | let total = filter(copy(g:plugs), 's:is_managed(v:key) && isdirectory(v:val.dir)') 2693 | call s:progress_bar(2, bar, len(total)) 2694 | for origin in [1, 0] 2695 | let plugs = reverse(sort(items(filter(copy(total), (origin ? '' : '!').'(has_key(v:val, "commit") || has_key(v:val, "tag"))')))) 2696 | if empty(plugs) 2697 | continue 2698 | endif 2699 | call s:append_ul(2, origin ? 'Pending updates:' : 'Last update:') 2700 | for [k, v] in plugs 2701 | let branch = s:git_origin_branch(v) 2702 | if len(branch) 2703 | let range = origin ? '..origin/'.branch : 'HEAD@{1}..' 2704 | let cmd = ['git', 'log', '--graph', '--color=never'] 2705 | if s:git_version_requirement(2, 10, 0) 2706 | call add(cmd, '--no-show-signature') 2707 | endif 2708 | call extend(cmd, ['--pretty=format:%x01%h%x01%d%x01%s%x01%cr', range]) 2709 | if has_key(v, 'rtp') 2710 | call extend(cmd, ['--', v.rtp]) 2711 | endif 2712 | let diff = s:system_chomp(cmd, v.dir) 2713 | if !empty(diff) 2714 | let ref = has_key(v, 'tag') ? (' (tag: '.v.tag.')') : has_key(v, 'commit') ? (' '.v.commit) : '' 2715 | call append(5, extend(['', '- '.k.':'.ref], map(s:lines(diff), 's:format_git_log(v:val)'))) 2716 | let cnts[origin] += 1 2717 | endif 2718 | endif 2719 | let bar .= '=' 2720 | call s:progress_bar(2, bar, len(total)) 2721 | normal! 2G 2722 | redraw 2723 | endfor 2724 | if !cnts[origin] 2725 | call append(5, ['', 'N/A']) 2726 | endif 2727 | endfor 2728 | call setline(1, printf('%d plugin(s) updated.', cnts[0]) 2729 | \ . (cnts[1] ? printf(' %d plugin(s) have pending updates.', cnts[1]) : '')) 2730 | 2731 | if cnts[0] || cnts[1] 2732 | nnoremap (plug-preview) :silent! call preview_commit() 2733 | if empty(maparg("\", 'n')) 2734 | nmap (plug-preview) 2735 | endif 2736 | if empty(maparg('o', 'n')) 2737 | nmap o (plug-preview) 2738 | endif 2739 | endif 2740 | if cnts[0] 2741 | nnoremap X :call revert() 2742 | echo "Press 'X' on each block to revert the update" 2743 | endif 2744 | normal! gg 2745 | setlocal nomodifiable 2746 | endfunction 2747 | 2748 | function! s:revert() 2749 | if search('^Pending updates', 'bnW') 2750 | return 2751 | endif 2752 | 2753 | let name = s:find_name(line('.')) 2754 | if empty(name) || !has_key(g:plugs, name) || 2755 | \ input(printf('Revert the update of %s? (y/N) ', name)) !~? '^y' 2756 | return 2757 | endif 2758 | 2759 | call s:system('git reset --hard HEAD@{1} && git checkout '.plug#shellescape(g:plugs[name].branch).' --', g:plugs[name].dir) 2760 | setlocal modifiable 2761 | normal! "_dap 2762 | setlocal nomodifiable 2763 | echo 'Reverted' 2764 | endfunction 2765 | 2766 | function! s:snapshot(force, ...) abort 2767 | call s:prepare() 2768 | setf vim 2769 | call append(0, ['" Generated by vim-plug', 2770 | \ '" '.strftime("%c"), 2771 | \ '" :source this file in vim to restore the snapshot', 2772 | \ '" or execute: vim -S snapshot.vim', 2773 | \ '', '', 'PlugUpdate!']) 2774 | 1 2775 | let anchor = line('$') - 3 2776 | let names = sort(keys(filter(copy(g:plugs), 2777 | \'has_key(v:val, "uri") && isdirectory(v:val.dir)'))) 2778 | for name in reverse(names) 2779 | let sha = has_key(g:plugs[name], 'commit') ? g:plugs[name].commit : s:git_revision(g:plugs[name].dir) 2780 | if !empty(sha) 2781 | call append(anchor, printf("silent! let g:plugs['%s'].commit = '%s'", name, sha)) 2782 | redraw 2783 | endif 2784 | endfor 2785 | 2786 | if a:0 > 0 2787 | let fn = s:plug_expand(a:1) 2788 | if filereadable(fn) && !(a:force || s:ask(a:1.' already exists. Overwrite?')) 2789 | return 2790 | endif 2791 | call writefile(getline(1, '$'), fn) 2792 | echo 'Saved as '.a:1 2793 | silent execute 'e' s:esc(fn) 2794 | setf vim 2795 | endif 2796 | endfunction 2797 | 2798 | function! s:split_rtp() 2799 | return split(&rtp, '\\\@:p:h'))) 8 | 9 | function s:fallback(val, default) 10 | return empty(a:val) ? a:default : a:val 11 | endfunction 12 | let g:jetpack_copy_method = s:fallback(getenv('JETPACK_COPY_METHOD'), 'system') 13 | let g:jetpack_download_method = s:fallback(getenv('JETPACK_DOWNLOAD_METHOD'), 'git') 14 | 15 | let g:vimhome = substitute(expand(':p:h'), '\', '/', 'g') 16 | let s:optdir = g:vimhome . '/pack/jetpack/opt' 17 | let s:srcdir = g:vimhome . '/pack/jetpack/src' 18 | 19 | call jetpack#begin(g:vimhome) 20 | Jetpack 'preservim/nerdtree' 21 | Jetpack 'vim-airline/vim-airline' 22 | Jetpack 'dense-analysis/ale' 23 | Jetpack 'vim-syntastic/syntastic' 24 | Jetpack 'codota/TabNine' 25 | Jetpack 'vimwiki/vimwiki' 26 | Jetpack 'easymotion/vim-easymotion' 27 | Jetpack 'itchyny/lightline.vim' 28 | Jetpack 'mhinz/vim-startify' 29 | Jetpack 'preservim/nerdcommenter' 30 | Jetpack 'lervag/vimtex' 31 | Jetpack 'Yggdroot/indentLine' 32 | Jetpack 'neomake/neomake' 33 | Jetpack 'mhinz/vim-signify' 34 | Jetpack 'tpope/vim-dispatch' 35 | Jetpack 'tpope/vim-fugitive' 36 | Jetpack 'tpope/vim-surround' 37 | Jetpack 'tpope/vim-repeat' 38 | Jetpack 'tpope/vim-unimpaired' 39 | Jetpack 'tpope/vim-rhubarb' 40 | Jetpack 'wellle/targets.vim' 41 | Jetpack 'lukas-reineke/indent-blankline.nvim' 42 | Jetpack 'soywod/himalaya' 43 | Jetpack 'preservim/vimux' 44 | Jetpack 'Xuyuanp/nerdtree-git-plugin' 45 | Jetpack 'liuchengxu/vim-clap' 46 | Jetpack 'dhruvasagar/vim-table-mode' 47 | Jetpack 'liuchengxu/vim-which-key' 48 | Jetpack 'AndrewRadev/splitjoin.vim' 49 | Jetpack 'liuchengxu/vista.vim' 50 | Jetpack 'skywind3000/asyncrun.vim' 51 | Jetpack 'luochen1990/rainbow' 52 | Jetpack 'preservim/vim-pencil' 53 | Jetpack 'tomtom/tcomment_vim' 54 | Jetpack 'jreybert/vimagit' 55 | Jetpack 'andymass/vim-matchup' 56 | Jetpack 'glepnir/dashboard-nvim' 57 | Jetpack 'unblevable/quick-scope' 58 | Jetpack 'maralla/completor.vim' 59 | Jetpack 'rhysd/git-messenger.vim' 60 | Jetpack 'mhinz/vim-grepper' 61 | Jetpack 'haya14busa/incsearch.vim' 62 | Jetpack 'matze/vim-move' 63 | "Jetpack 'wellle/context.vim' 64 | Jetpack 'rhysd/vim-grammarous' 65 | Jetpack 'jacoborus/tender.vim' 66 | Jetpack 'rhysd/vim-clang-format' 67 | Jetpack 'skywind3000/vim-quickui' 68 | Jetpack 'rhysd/clever-f.vim' 69 | Jetpack 'gelguy/wilder.nvim' 70 | Jetpack 'romainl/Apprentice' 71 | Jetpack 'ackyshake/VimCompletesMe' 72 | Jetpack 'skywind3000/asynctasks.vim' 73 | Jetpack 'thinca/vim-quickrun' 74 | Jetpack 'pearofducks/ansible-vim' 75 | Jetpack 'srcery-colors/srcery-vim' 76 | Jetpack 'preservim/vim-wordy' 77 | Jetpack 'JuliaEditorSupport/julia-vim' 78 | Jetpack 'hrsh7th/vim-vsnip' 79 | Jetpack 'pineapplegiant/spaceduck' 80 | Jetpack 'markonm/traces.vim' 81 | Jetpack 'slashmili/alchemist.vim' 82 | Jetpack 'rhysd/committia.vim' 83 | Jetpack 'jamessan/vim-gnupg' 84 | Jetpack 'drewtempelmeyer/palenight.vim' 85 | Jetpack 'mcchrish/nnn.vim' 86 | Jetpack 'euclio/vim-markdown-composer' 87 | Jetpack 'APZelos/blamer.nvim' 88 | Jetpack 'AndrewRadev/switch.vim' 89 | Jetpack 'shawncplus/phpcomplete.vim' 90 | Jetpack 'preservim/vim-colors-pencil' 91 | Jetpack 'dodie/vim-disapprove-deep-indentation' 92 | Jetpack 'romainl/vim-qf' 93 | Jetpack 'mzlogin/vim-markdown-toc' 94 | Jetpack 'itchyny/vim-cursorword' 95 | Jetpack 'wellle/tmux-complete.vim' 96 | Jetpack 'liquidz/vim-iced' 97 | Jetpack 'Rigellute/rigel' 98 | Jetpack 'arithran/vim-pizza' 99 | Jetpack 'AndrewRadev/sideways.vim' 100 | Jetpack 'camspiers/lens.vim' 101 | Jetpack 'wesQ3/vim-windowswap' 102 | Jetpack 'SidOfc/mkdx' 103 | Jetpack '907th/vim-auto-save' 104 | Jetpack 'thiagoalessio/rainbow_levels.vim' 105 | Jetpack 'ojroques/vim-oscyank' 106 | Jetpack 'sillybun/vim-repl' 107 | Jetpack 'WolfgangMehner/vim-plugins' 108 | Jetpack 'jupyter-vim/jupyter-vim' 109 | Jetpack 'bagrat/vim-buffet' 110 | Jetpack 'romainl/vim-cool' 111 | Jetpack 'vim-crystal/vim-crystal' 112 | Jetpack 'tmsvg/pear-tree' 113 | Jetpack 'lukas-reineke/lsp-format.nvim' 114 | Jetpack 'ctjhoa/spacevim' 115 | Jetpack 'aperezdc/vim-template' 116 | Jetpack 'lfv89/vim-interestingwords' 117 | Jetpack 'kristijanhusak/vim-dadbod-completion' 118 | Jetpack 'whiteinge/diffconflicts' 119 | Jetpack 'AndrewRadev/tagalong.vim' 120 | call jetpack#end() 121 | 122 | execute 'autocmd SourcePost' expand(':p:h') 'echomsg "loaded"' 123 | -------------------------------------------------------------------------------- /benchmark/requirements.txt: -------------------------------------------------------------------------------- 1 | matplotlib 2 | pandas 3 | -------------------------------------------------------------------------------- /benchmark/run.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import os 3 | 4 | cmd = 'nvim -u "{}" --headless --startuptime "{}" +qa!' 5 | for m in ['jetpack', 'plug']: 6 | if os.path.exists(m + '.log'): 7 | os.unlink(m + '.log') 8 | for i in range(100): 9 | print('{:4d} {}'.format(i, cmd.format('./' + m + '.vim', './' + m + '.log'))) 10 | os.system(cmd.format('./' + m + '.vim', './' + m + '.log')) 11 | -------------------------------------------------------------------------------- /benchmark/setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import os 3 | 4 | print('nvim -u ./jetpack.vim --headless +JetpackSync +qa') 5 | os.system('nvim -u ./jetpack.vim --headless +JetpackSync +qa') 6 | print('nvim -u ./plug.vim --headless +PlugInstall +qa') 7 | os.system('nvim -u ./plug.vim --headless +PlugInstall +qa') 8 | -------------------------------------------------------------------------------- /benchmark/stat.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import sys 3 | import re 4 | import pandas as pd 5 | import matplotlib.pyplot as plt 6 | 7 | targets = ['jetpack', 'plug'] 8 | data = [] 9 | 10 | for target in targets: 11 | ts = [] 12 | with open(target+'.log', 'r') as f: 13 | line = f.readline() 14 | while line: 15 | if re.match(r'.*neomake/log\.vim', line): 16 | ts.append(float(re.sub(r'\s.*', '', line))) 17 | line = f.readline() 18 | data.append(ts) 19 | 20 | # transpose data 21 | data = list(map(list, zip(*data))) 22 | df = pd.DataFrame(data=data, columns=targets) 23 | 24 | with plt.xkcd(): 25 | plt.figure() 26 | df.plot.box() 27 | plt.savefig('benchmark.png') 28 | plt.close('all') 29 | 30 | print(df.describe()) 31 | -------------------------------------------------------------------------------- /doc/jetpack.txt: -------------------------------------------------------------------------------- 1 | jetpack.txt 2 | 3 | ================================================================================ 4 | 5 | JETPACK *jetpack* 6 | 7 | The lightning-fast minimalist plugin manager for Vim/ Neovim. vim-jetpack is 8 | a jetpack for the most of vimmers. Unbelievably, it is faster than vimrc uses 9 | the built-in plugin manager only. 10 | 11 | -------------------------------------------------------------------------------- 12 | 13 | INSTALLATION *jetpack-installation* 14 | 15 | The installation is very simple. 16 | You just need to download a single file and put it on the runtimepath. 17 | 18 | https://raw.githubusercontent.com/tani/vim-jetpack/master/plugin/jetpack.vim 19 | 20 | The following is an example of installation with cURL command. 21 | 22 | Vim for Linux and macOS 23 | > 24 | curl -fLo ~/.vim/pack/jetpack/opt/vim-jetpack/plugin/jetpack.vim --create-dirs https://raw.githubusercontent.com/tani/vim-jetpack/master/plugin/jetpack.vim 25 | 26 | Neovim for Linux and macOS 27 | > 28 | curl -fLo ~/.local/share/nvim/site/pack/jetpack/opt/vim-jetpack/plugin/jetpack.vim --create-dirs https://raw.githubusercontent.com/tani/vim-jetpack/master/plugin/jetpack.vim 29 | 30 | Vim for Windows 31 | > 32 | curl -fLo %USERPROFILE%\vimfiles\pack\jetpack\opt\vim-jetpack\plugin\jetpack.vim --create-dirs https://raw.githubusercontent.com/tani/vim-jetpack/master/plugin/jetpack.vim 33 | 34 | Neovim for Windows 35 | > 36 | curl -fLo %USERPROFILE%\AppData\Local\nvim-data\site\pack\jetpack\opt\vim-jetpack\plugin\jetpack.vim --create-dirs https://raw.githubusercontent.com/tani/vim-jetpack/master/plugin/jetpack.vim 37 | 38 | -------------------------------------------------------------------------------- 39 | 40 | USAGE *jetpack-usage* 41 | 42 | Jetpack was initially implemented as a successor of vim-plug. 43 | Nowadays, we also provide compatibility layers for other plugin managers, 44 | dein.vim, packer.nvim.nvim, and paq.nvim.nvim. 45 | You can use Jetpack as a drop-in replacement of them. 46 | In personal opinion, vim-plug style is the most stable and easy to use. 47 | 48 | VIM-PLUG STYLE *jetpack-vim-plug* 49 | > 50 | call jetpack#begin() 51 | Jetpack 'user/repo', { ...opts } 52 | call jetpack#end() 53 | 54 | options 55 | `opt` *jetpack-vim-plug-opt* (`bool`): 56 | Lazy loading option for plugins using `packadd`. 57 | `dir` *jetpack-vim-plug-dir* (`string`): 58 | Specifies the directory where the plugin should be placed. 59 | `rtp` *jetpack-vim-plug-rtp* (`string`): 60 | Specifies the subdirectory where the plugin should be loaded. 61 | `do` *jetpack-vim-plug-do* (`string` or `function`): 62 | Executes a shell command or a function after the plugin is installed. 63 | `as` *jetpack-vim-plug-as* (`string`): 64 | Assigns an alias to the plugin. 65 | `on` *jetpack-vim-plug-on* (`string` or `list`): 66 | Loads the plugin for the given keys, command or event. 67 | `for` *jetpack-vim-plug-for* (`string` or `list`): 68 | Loads the plugin for the given filetype. 69 | `branch` *jetpack-vim-plug-branch* (`string`): 70 | Downloads the specified revision of the plugin from the branch. 71 | `commit` *jetpack-vim-plug-commit* (`string`): 72 | Downloads the specified revision of the plugin from the commit. 73 | `tag` *jetpack-vim-plug-tag* (`string`): 74 | Downloads the specified revision of the plugin from the tag. 75 | `frozen` *jetpack-vim-plug-frozen* (`bool`): 76 | Prevents the plugin from being updated. 77 | 78 | DEIN.VIM *jetpack-dein.vim* 79 | > 80 | call jetpack#begin() 81 | call jetpack#load_toml('path/to/plugins.toml') 82 | call jetpack#add('user/repo', { ... opts }) 83 | call jetpack#end() 84 | > 85 | [[plugin]] 86 | repo = 'user/repo' 87 | opt = true 88 | 89 | [[plugin]] 90 | repo = 'user/other' 91 | tag = 'tag-name' 92 | 93 | options 94 | `opt` *jetpack-dein.vim-opt* (`bool`): 95 | Lazy loading option for plugins using Dein's `lazy` option. 96 | `path` *jetpack-dein.vim-path* (`string`): 97 | Specifies the directory where the plugin should be placed. 98 | `rtp` *jetpack-dein.vim-rtp* (`string`): 99 | Specifies the subdirectory where the plugin should be loaded. 100 | `build` *jetpack-dein.vim-build* (`string` or `function`): 101 | Executes a shell command or a function to build the plugin. 102 | `name` *jetpack-dein.vim-name* (`string`): 103 | Assigns a name to the plugin. 104 | `on_cmd` *jetpack-dein.vim-on_cmd* (`string` or `list`): 105 | Loads the plugin for the given command. 106 | `on_map` *jetpack-dein.vim-on_map* (`string` or `list`): 107 | Loads the plugin for the given key mapping. 108 | `on_event` *jetpack-dein.vim-on_event* (`string` or `list`): 109 | Loads the plugin for the given event. 110 | `on_ft` *jetpack-dein.vim-on_ft* (`string` or `list`): 111 | Loads the plugin for the given filetype. 112 | `branch` *jetpack-dein.vim-branch* (`string`): 113 | Downloads the specified revision of the plugin from the branch. 114 | `commit` *jetpack-dein.vim-commit* (`string`): 115 | Downloads the specified revision of the plugin from the commit. 116 | `tag` *jetpack-dein.vim-tag* (`string`): 117 | Downloads the specified revision of the plugin from the tag. 118 | `depends` *jetpack-dein.vim-depends* (`string` or `list`): 119 | Specifies the dependencies required for the plugin. 120 | `on_post_source` *jetpack-dein.vim-on_post_source* (`string` or `list`): 121 | Loads the plugin after the plugin(s) source has been loaded. 122 | `on_source` *jetpack-dein.vim-on_source* (`string` or `list`): 123 | Loads the plugin before the plugin(s) source has been loaded. 124 | `frozen` *jetpack-dein.vim-frozen* (`bool`): 125 | Prevents the plugin from being updated. 126 | `hook_post_source` *jetpack-dein.vim-hook_post_source* (`string`): 127 | Executes a code after the plugin source has been loaded. 128 | `hook_source` *jetpack-dein.vim-hook_source* (`string`): 129 | Executes a code before the plugin source has been loaded. 130 | `hook_add` *jetpack-dein.vim-hook_add* (`string`): 131 | Executes a code after the plugin is added to the runtimepath. 132 | 133 | PACKER.NVIM *jetpack-packer.nvim* 134 | > 135 | require('jetpack.packer').startup(function(use) 136 | use({ 'user/repo', ... opts }) 137 | end) 138 | 139 | options 140 | `opt` *jetpack-packer.nvim-opt* (`bool`): 141 | Lazy loading option for plugins. 142 | `dir` *jetpack-packer.nvim-dir* (`string`): 143 | Specifies the directory where the plugin should be placed. 144 | `rtp` *jetpack-packer.nvim-rtp* (`string`): 145 | Specifies the subdirectory where the plugin should be loaded. 146 | `run` *jetpack-packer.nvim-run* (`string` or `function`): 147 | Executes a shell command or a function after the plugin is installed. 148 | `as` *jetpack-packer.nvim-as* (`string`): 149 | Assigns a different name to the plugin for use in the Vim runtimepath. 150 | `cmd` *jetpack-packer.nvim-cmd* (`string` or `table`): 151 | Loads the plugin for the given command. 152 | `keys` *jetpack-packer.nvim-keys* (`string` or `table`): 153 | Loads the plugin for the given key mapping. 154 | `event` *jetpack-packer.nvim-event* (`string` or `table`): 155 | Loads the plugin for the given event. 156 | `ft` *jetpack-packer.nvim-ft* (`string` or `table`): 157 | Loads the plugin for the given filetype. 158 | `branch` *jetpack-packer.nvim-branch* (`string`): 159 | Downloads the specified revision of the plugin from the branch. 160 | `commit` *jetpack-packer.nvim-commit* (`string`): 161 | Downloads the specified revision of the plugin from the commit. 162 | `tag` *jetpack-packer.nvim-tag* (`string`): 163 | Downloads the specified revision of the plugin from the tag. 164 | `requires` *jetpack-packer.nvim-requires* (`string` or `table`): 165 | Specifies the plugins required for the plugin. 166 | `after` *jetpack-packer.nvim-after* (`string` or `table`): 167 | Specifies the plugins that should be loaded after the plugin. 168 | `before` *jetpack-packer.nvim-before* (`string` or `table`): 169 | Specifies the plugins that should be loaded before the plugin. 170 | `lock` *jetpack-packer.nvim-lock* (`bool`): 171 | Prevents the plugin from being updated. 172 | `config` *jetpack-packer.nvim-config* (`string` or `function`): 173 | Executes a Lua code or a function after the plugin is loaded. 174 | `setup` *jetpack-packer.nvim-setup* (`string` or `function`): 175 | Executes a Lua code or a function before the plugin is loaded. 176 | 177 | -------------------------------------------------------------------------------- 178 | 179 | API *jetpack-api* 180 | 181 | FUNCTION *jetpack-function* 182 | 183 | *jetpack#begin()* 184 | The function setups jetpack plugins. All plugin declarations should be 185 | placed after this function. You can give `path` if you want to use another 186 | directory to manage plugins. 187 | 188 | *jetpack#add()* 189 | repo is a pair of string concatenated with `/` such as `tani/vim-jetpack`. 190 | `options` is a dictionary. See below. 191 | 192 | *jetpack#sync()* 193 | The function performs to install, update, and bundle all plugins. 194 | The function is everything all you need to know. 195 | You must run this function after a change of your configuration. 196 | 197 | *jetpack#end()* 198 | The function loads declared plugins. All plugin declarations should be 199 | placed before this function. 200 | 201 | *jetpack#tap()* 202 | It returns a truthy value if the plugin is available, otherwise it 203 | returns a falsy value. 204 | 205 | *jetpack#names()* 206 | It returns the list of plugin names registered including unavailable 207 | plugins. 208 | 209 | *jetpack#get()* 210 | It returns metadata of the plugin if possible, otherwise it returns `{}`. 211 | This is the same as `dein#get` of `dein.vim`. 212 | 213 | *jetpack#load()* 214 | This is a wrapper function for |:packadd|; since it fires config 215 | options, etc., it is recommended to use this instead of |:packadd|. 216 | 217 | *jetpack#load_toml()* 218 | It loads |jetpack-dein.vim| style package configuration writtein in TOML. 219 | 220 | LUA *jetpack-lua* 221 | 222 | *jetpack.paq()* 223 | This function takes a table that is a list of plugin specifiers. 224 | 225 | *jetpack.packer.startup()* 226 | This function takes a function that takes a function `use`. 227 | `use` takes a plugin specifier. 228 | 229 | *jetpack.packer.add()* 230 | This function is an alias of |jetpack.paq()|. 231 | 232 | *jetpack.packer.init()* 233 | This function takes a table associating configuration parameters. 234 | * `package_root` -- `$VIMHOME/pack` 235 | 236 | VARIABLE *jetpack-variable* 237 | 238 | *g:jetpack_download_method* 239 | * The default value is `'git'`. 240 | Consider using `'curl'` or `'wget'` 241 | if `'git'` is not installed in your system. 242 | * `'git'` Use `'git'` to download plugins. 243 | * `'curl'` Use `'curl'` to download plugins. 244 | * `'wget'` Use `'wget'` to download plugins. 245 | 246 | *g:jetpack_njobs* 247 | * The default value is `8`. 248 | Jetpack runs `n` commands to download plugins in parallel. 249 | 250 | COMMAND *jetpack-command* 251 | 252 | *JetpackSync* 253 | This is the command version of |jetpack#sync()|. 254 | 255 | *Jetpack* 256 | This is the command version of |jetpack#add()|. 257 | 258 | EVENT *jetpack-event* 259 | 260 | *JetpackPre* (`User JetpackPre:{{plugin-name}}`) 261 | *JetpackPost* (`User JetpackPost:{{plugin-name}}`) 262 | This event will be fired if the specified plugin is loaded. 263 | 264 | -------------------------------------------------------------------------------- 265 | 266 | TIPS *jetpack-tips* 267 | 268 | BOOTSTRAP *jetpack-bootstrap* 269 | 270 | Vim 271 | > 272 | let s:jetpackfile = expand(':p:h') .. '/pack/jetpack/opt/vim-jetpack/plugin/jetpack.vim' 273 | let s:jetpackurl = "https://raw.githubusercontent.com/tani/vim-jetpack/master/plugin/jetpack.vim" 274 | if !filereadable(s:jetpackfile) 275 | call system(printf('curl -fsSLo %s --create-dirs %s', s:jetpackfile, s:jetpackurl)) 276 | endif 277 | 278 | Neovim 279 | > 280 | let s:jetpackfile = stdpath('data') .. '/site/pack/jetpack/opt/vim-jetpack/plugin/jetpack.vim' 281 | let s:jetpackurl = "https://raw.githubusercontent.com/tani/vim-jetpack/master/plugin/jetpack.vim" 282 | if !filereadable(s:jetpackfile) 283 | call system(printf('curl -fsSLo %s --create-dirs %s', s:jetpackfile, s:jetpackurl)) 284 | endif 285 | 286 | Neovim with Lua 287 | > 288 | local jetpackfile = vim.fn.stdpath('data') .. '/site/pack/jetpack/opt/vim-jetpack/plugin/jetpack.vim' 289 | local jetpackurl = "https://raw.githubusercontent.com/tani/vim-jetpack/master/plugin/jetpack.vim" 290 | if vim.fn.filereadable(jetpackfile) == 0 then 291 | vim.fn.system(string.format('curl -fsSLo %s --create-dirs %s', jetpackfile, jetpackurl)) 292 | end 293 | 294 | AUTOMATIC-INSTALLATION *jetpack-automatic-installation* 295 | 296 | Vim and Neovim 297 | > 298 | for name in jetpack#names() 299 | if !jetpack#tap(name) 300 | call jetpack#sync() 301 | break 302 | endif 303 | endfor 304 | 305 | Neovim with Lua 306 | > 307 | local jetpack = require('jetpack') 308 | for _, name in ipairs(jetpack.names()) do 309 | if not jetpack.tap(name) then 310 | jetpack.sync() 311 | break 312 | end 313 | end 314 | 315 | TREESITTER *jetpack-treesitter* 316 | 317 | Set the different directory to download parsers. 318 | > 319 | local parser_install_dir = vim.fn.stdpath "data" .. "/treesitter" 320 | vim.opt.runtimepath:append(parser_install_dir) 321 | require("nvim-treesitter.configs").setup { 322 | parser_install_dir = parser_install_dir, 323 | ... 324 | } 325 | -------------------------------------------------------------------------------- 326 | 327 | LICENSE *jetpack-license* 328 | 329 | Copyright (c) 2022 -- 2023 Masaya Taniguchi 330 | 331 | The software is released under the MIT License, 332 | see the header of the source code. 333 | 334 | ================================================================================ 335 | vim:tw=78:fo=tcq2mM:ts=8:ft=help:norl 336 | -------------------------------------------------------------------------------- /plugin/jetpack.vim: -------------------------------------------------------------------------------- 1 | "=================================== Jetpack ================================== 2 | "Copyright (c) 2022 TANIGUCHI Masaya 3 | " 4 | "Permission is hereby granted, free of charge, to any person obtaining a copy 5 | "of this software and associated documentation files (the "Software"), to deal 6 | "in the Software without restriction, including without limitation the rights 7 | "to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | "copies of the Software, and to permit persons to whom the Software is 9 | "furnished to do so, subject to the following conditions: 10 | " 11 | "The above copyright notice and this permission notice shall be included in all 12 | "copies or substantial portions of the Software. 13 | " 14 | "THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | "FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | "AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | "LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | "OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | "SOFTWARE. 21 | "============================================================================== 22 | 23 | if exists('g:loaded_jetpack') 24 | finish 25 | endif 26 | let g:loaded_jetpack = 1 27 | 28 | if has('nvim') 29 | function! jetpack#execute(code) abort 30 | return v:lua.vim.cmd(a:code) 31 | endfunction 32 | elseif has('lua') 33 | function! jetpack#execute(code) abort 34 | let g:jetpack_code = a:code 35 | lua vim.command(vim.eval('g:jetpack_code')) 36 | endfunction 37 | elseif has('patch-8.2.4594') 38 | function! jetpack#execute(code) abort 39 | let c = bufnr() 40 | let t = bufadd('') 41 | execute 'silent buffer' t 42 | call setline(1, split(a:code, "\n")) 43 | source 44 | execute 'silent bwipeout!' t 45 | execute 'silent buffer' c 46 | endfunction 47 | else 48 | function! jetpack#execute(code) abort 49 | let temp = tempname() 50 | call writefile(split(a:code, "\n"), temp) 51 | execute 'source' temp 52 | call delete(temp) 53 | endfunction 54 | endif 55 | 56 | let g:jetpack_njobs = get(g:, 'jetpack_njobs', 8) 57 | 58 | let g:jetpack_download_method = 59 | \ get(g:, 'jetpack_download_method', has('ivim') ? 'curl' : 'git') 60 | " curl: Use CURL to download 61 | " wget: Use Wget to download 62 | 63 | let s:cmds = {} 64 | let s:maps = {} 65 | let s:declared_packages = {} 66 | 67 | let s:status = { 68 | \ 'pending': 'pending', 69 | \ 'skipped': 'skipped', 70 | \ 'installed': 'installed', 71 | \ 'installing': 'installing', 72 | \ 'updated': 'updated', 73 | \ 'updating': 'updating', 74 | \ 'switched': 'switched', 75 | \ 'copied': 'copied' 76 | \ } 77 | 78 | function! jetpack#parse_toml(lines) abort 79 | let plugins = [] 80 | let plugin = {} 81 | let key = '' 82 | let multiline = '' 83 | for line in a:lines 84 | if !empty(multiline) 85 | let plugin[key] .= line . (multiline =~ ']' ? "" : "\n") 86 | if line =~ multiline 87 | if multiline == ']' 88 | let plugin[key] = eval(plugin[key]) 89 | else 90 | let plugin[key] = substitute(plugin[key], multiline, '', 'g') 91 | endif 92 | let multiline = '' 93 | endif 94 | elseif trim(line) =~ '^#\|^$' 95 | elseif line =~ '\[\[plugins\]\]' 96 | call add(plugins, deepcopy(plugin)) 97 | let plugin = {} 98 | elseif line =~ '\(\w\+\)\s*=\s*' 99 | let key = substitute(line, '\(\w\+\)\s*=\s*.*', '\1', '') 100 | let raw = substitute(line, '\w\+\s*=\s*', '', '') 101 | if raw =~ "\\(\"\"\"\\|'''\\)\\(.*\\)\\1" 102 | let plugin[key] = substitute(raw, "\\(\"\"\"\\|'''\\)\\(.*\\)\\1", '\2', '') 103 | elseif raw =~ '"""' || raw =~ "'''" 104 | let multiline = raw =~ '"""' ? '"""' : "'''" 105 | let plugin[key] = raw 106 | elseif raw =~ '\[.*\]' 107 | let plugin[key] = eval(raw) 108 | elseif raw =~ '\[' 109 | let multiline = ']' 110 | let plugin[key] = raw 111 | else 112 | let plugin[key] = eval(trim(raw) =~ 'true\|false' ? 'v:'.raw : raw) 113 | endif 114 | endif 115 | endfor 116 | call add(plugins, plugin) 117 | return filter(plugins,{ _, val -> !empty(val) }) 118 | endfunction 119 | 120 | function! jetpack#make_progressbar(n) abort 121 | return '[' . join(map(range(0, 100, 3), {_, v -> v < a:n ? '=' : ' '}), '') . ']' 122 | endfunction 123 | 124 | function! jetpack#jobstatus(job) abort 125 | if has('nvim') 126 | return jobwait([a:job], 0)[0] == -1 ? 'run' : 'dead' 127 | endif 128 | return job_status(a:job) 129 | endfunction 130 | 131 | function! jetpack#jobcount(jobs) abort 132 | return len(filter(copy(a:jobs), { _, val -> jetpack#jobstatus(val) ==# 'run' })) 133 | endfunction 134 | 135 | function! jetpack#jobwait(jobs, njobs) abort 136 | let running = jetpack#jobcount(a:jobs) 137 | while running > a:njobs 138 | let running = jetpack#jobcount(a:jobs) 139 | endwhile 140 | endfunction 141 | 142 | " Original: https://github.com/lambdalisue/vital-Whisky/blob/90c71/autoload/vital/__vital__/System/Job/Vim.vim#L46 143 | " License: https://github.com/lambdalisue/vital-Whisky/blob/90c71/LICENSE 144 | function! jetpack#nvim_exit_cb(cmd, buf, cb, job, st) abort 145 | let ch = job_getchannel(a:job) 146 | while ch_status(ch) ==# 'open' | sleep 1ms | endwhile 147 | while ch_status(ch) ==# 'buffered' | sleep 1ms | endwhile 148 | if a:st != 0 149 | echoerr '`'.join(a:cmd, ' ').'`:'.join(a:buf, "\n") 150 | endif 151 | call a:cb(join(a:buf, "\n")) 152 | endfunction 153 | 154 | if has('nvim') 155 | function! jetpack#jobstart(cmd, cb) abort 156 | let buf = [] 157 | return jobstart(a:cmd, { 158 | \ 'on_stdout': { _, data -> extend(buf, data) }, 159 | \ 'on_stderr': { _, data -> extend(buf, data) }, 160 | \ 'on_exit': { _, st -> st != 0 ? execute("echoerr '`'.join(a:cmd, ' ').'`:'.join(buf, '')") : a:cb(join(buf, '')) } 161 | \ }) 162 | endfunction 163 | 164 | function! jetpack#system(cmd) abort 165 | return a:cmd->split(" ")->system() 166 | endfunction 167 | else 168 | function! jetpack#jobstart(cmd, cb) abort 169 | let buf = [] 170 | return job_start(a:cmd, { 171 | \ 'out_mode': 'raw', 172 | \ 'out_cb': { _, data -> extend(buf, split(data, "\n")) }, 173 | \ 'err_mode': 'raw', 174 | \ 'err_cb': { _, data -> extend(buf, split(data, "\n")) }, 175 | \ 'exit_cb': function('jetpack#nvim_exit_cb', [a:cmd, buf, a:cb]) 176 | \ }) 177 | endfunction 178 | 179 | function! jetpack#system(cmd) abort 180 | let buf = [] 181 | let job = job_start(a:cmd, { 182 | \ 'out_cb': { _, data -> extend(buf, split(data, "\n")) } 183 | \}) 184 | call jetpack#jobwait([job], 0) 185 | return buf->join("\n") 186 | endfunction 187 | endif 188 | 189 | function! jetpack#initialize_buffer() abort 190 | execute 'silent! bdelete!' bufnr('JetpackStatus') 191 | silent 40vnew +setlocal\ buftype=nofile\ nobuflisted\ nonumber\ norelativenumber\ signcolumn=no\ noswapfile\ nowrap JetpackStatus 192 | syntax clear 193 | syntax match jetpackProgress /^[a-z]*ing/ 194 | syntax match jetpackComplete /^[a-z]*ed/ 195 | syntax keyword jetpackSkipped ^skipped 196 | highlight def link jetpackProgress DiffChange 197 | highlight def link jetpackComplete DiffAdd 198 | highlight def link jetpackSkipped DiffDelete 199 | redraw 200 | endfunction 201 | 202 | function! jetpack#show_progress(title) abort 203 | let buf = bufnr('JetpackStatus') 204 | call deletebufline(buf, 1, '$') 205 | let processed = len(filter(copy(s:declared_packages), { _, val -> val.status[-1] =~# 'ed' })) 206 | call setbufline(buf, 1, printf('%s (%d / %d)', a:title, processed, len(s:declared_packages))) 207 | call appendbufline(buf, '$', jetpack#make_progressbar((0.0 + processed) / len(s:declared_packages) * 100)) 208 | for [pkg_name, pkg] in items(s:declared_packages) 209 | call appendbufline(buf, '$', printf('%s %s', pkg.status[-1], pkg_name)) 210 | endfor 211 | redraw 212 | endfunction 213 | 214 | function! jetpack#show_result() abort 215 | let buf = bufnr('JetpackStatus') 216 | call deletebufline(buf, 1, '$') 217 | call setbufline(buf, 1, 'Result') 218 | call appendbufline(buf, '$', jetpack#make_progressbar(100)) 219 | for [pkg_name, pkg] in items(s:declared_packages) 220 | if index(pkg.status, s:status.installed) >= 0 221 | call appendbufline(buf, '$', printf('installed %s', pkg_name)) 222 | elseif index(pkg.status, s:status.updated) >= 0 223 | call appendbufline(buf, '$', printf('updated %s', pkg_name)) 224 | else 225 | call appendbufline(buf, '$', printf('skipped %s', pkg_name)) 226 | endif 227 | let output = substitute(pkg.output, '\r\n\|\r', '\n', 'g') 228 | let output = substitute(output, '^From.\{-}\zs\n\s*', '/compare/', '') 229 | for line in split(output, '\n') 230 | call appendbufline(buf, '$', printf(' %s', line)) 231 | endfor 232 | endfor 233 | redraw 234 | endfunction 235 | 236 | function! jetpack#clean_plugins() abort 237 | for [pkg_name, pkg] in items(s:available_packages) 238 | if !has_key(s:declared_packages, pkg_name) && empty(pkg.local) && empty(pkg.dir) 239 | call delete(pkg.path, 'rf') 240 | endif 241 | endfor 242 | if g:jetpack_download_method !=# 'git' 243 | return 244 | endif 245 | for [pkg_name, pkg] in items(s:declared_packages) 246 | if !isdirectory(pkg.path . '/.git') 247 | call delete(pkg.path, 'rf') 248 | continue 249 | endif 250 | if isdirectory(pkg.path) 251 | call jetpack#system(printf('git -C %s reset --hard', pkg.path)) 252 | let branch = trim(jetpack#system(printf('git -C %s rev-parse --abbrev-ref %s', pkg.path, pkg.commit))) 253 | if v:shell_error && !empty(pkg.commit) 254 | call delete(pkg.path, 'rf') 255 | continue 256 | endif 257 | if !empty(pkg.branch) && pkg.branch !=# branch 258 | call delete(pkg.path, 'rf') 259 | continue 260 | endif 261 | if !empty(pkg.tag) && pkg.tag !=# branch 262 | call delete(pkg.path, 'rf') 263 | continue 264 | endif 265 | endif 266 | endfor 267 | endfunction 268 | 269 | function! jetpack#make_download_cmd(pkg) abort 270 | let download_method = g:jetpack_download_method 271 | if a:pkg.url =~? '\.tar\.gz$' 272 | let download_method = 'curl' 273 | endif 274 | if download_method ==# 'git' 275 | if isdirectory(a:pkg.path) 276 | return [join(['git', '-C', a:pkg.path, 'pull', '--rebase'], ' ')] 277 | else 278 | let git_cmd = ['git', 'clone'] 279 | if a:pkg.commit ==# 'HEAD' 280 | call extend(git_cmd, ['--depth', '1', '--recursive']) 281 | endif 282 | if !empty(a:pkg.branch) 283 | call extend(git_cmd, ['-b', a:pkg.branch]) 284 | endif 285 | if !empty(a:pkg.tag) 286 | call extend(git_cmd, ['-b', a:pkg.tag]) 287 | endif 288 | call extend(git_cmd, [a:pkg.url, a:pkg.path]) 289 | if has('unix') 290 | let rmdir_cmd = 'rm -rf ' . a:pkg.path 291 | let mkdir_cmd = 'mkdir -p ' . a:pkg.path 292 | else 293 | let rmdir_cmd = '(if exist ' . a:pkg.path . ' rmdir /s /q ' . a:pkg.path . ')' 294 | let mkdir_cmd = 'mkdir ' . a:pkg.path 295 | endif 296 | return [rmdir_cmd, mkdir_cmd, join(git_cmd, ' ')] 297 | endif 298 | else 299 | let temp = tempname() 300 | if !empty(a:pkg.tag) 301 | let label = a:pkg.tag 302 | elseif !empty(a:pkg.branch) 303 | let label = a:pkg.branch 304 | else 305 | let label = a:pkg.commit 306 | endif 307 | if download_method ==# 'curl' 308 | let curl_flags = has('ivim') ? ' -kfsSL ' : ' -fsSL ' 309 | if a:pkg.url =~? '\.tar\.gz$' 310 | let download_cmd = 'curl' . curl_flags . a:pkg.url . ' -o ' . temp 311 | else 312 | let download_cmd = 'curl' . curl_flags . a:pkg.url . '/archive/' . label . '.tar.gz' . ' -o ' . temp 313 | endif 314 | elseif download_method ==# 'wget' 315 | if a:pkg.url =~? '\.tar\.gz$' 316 | let download_cmd = 'wget ' . a:pkg.url . ' -O ' . temp 317 | else 318 | let download_cmd = 'wget ' . a:pkg.url . '/archive/' . label . '.tar.gz' . ' -O ' . temp 319 | endif 320 | else 321 | throw 'g:jetpack_download_method: ' . download_method . ' is not a valid value' 322 | endif 323 | let extract_cmd = 'tar -zxf ' . temp . ' -C ' . a:pkg.path . ' --strip-components 1' 324 | if has('unix') 325 | let rmdir_cmd_1 = 'rm -rf ' . a:pkg.path 326 | let rmdir_cmd_2 = 'rm ' . temp 327 | let mkdir_cmd = 'mkdir -p ' . a:pkg.path 328 | else 329 | let rmdir_cmd_1 = '(if exist ' . a:pkg.path . ' rmdir /s /q ' . a:pkg.path . ')' 330 | let rmdir_cmd_2 = '(if exist ' . temp . ' del ' . temp . ')' 331 | let mkdir_cmd = 'mkdir ' . a:pkg.path 332 | endif 333 | return [rmdir_cmd_1, mkdir_cmd, download_cmd, extract_cmd, rmdir_cmd_2] 334 | endif 335 | endfunction 336 | 337 | function! jetpack#download_plugins() abort 338 | let jobs = [] 339 | for [pkg_name, pkg] in items(s:declared_packages) 340 | call add(pkg.status, s:status.pending) 341 | endfor 342 | for [pkg_name, pkg] in items(s:declared_packages) 343 | if pkg.local 344 | continue 345 | endif 346 | call jetpack#show_progress('Install Plugins') 347 | if isdirectory(pkg.path) 348 | if pkg.frozen 349 | call add(pkg.status, s:status.skipped) 350 | continue 351 | endif 352 | call add(pkg.status, s:status.updating) 353 | let status = s:status.updated 354 | else 355 | call add(pkg.status, s:status.installing) 356 | let status = s:status.installed 357 | endif 358 | let cmds = jetpack#make_download_cmd(pkg) 359 | if executable('sh') || executable('cmd.exe') 360 | let cmd = [ 361 | \ (has('unix') ? 'sh' : 'cmd.exe'), 362 | \ (has('unix') ? '-c' : '/c'), 363 | \ join(cmds, ' && ') 364 | \ ] 365 | let job = jetpack#jobstart(cmd, function({status, pkg, output -> [ 366 | \ add(pkg.status, status), 367 | \ execute("let pkg.output = output") 368 | \ ]}, [status, pkg])) 369 | call add(jobs, job) 370 | call jetpack#jobwait(jobs, g:jetpack_njobs) 371 | else 372 | let pkg.output = join(map(cmds, { _, cmd -> jetpack#system(cmd) }), "\n") 373 | call add(pkg.status, status) 374 | endif 375 | endfor 376 | call jetpack#jobwait(jobs, 0) 377 | endfunction 378 | 379 | function! jetpack#switch_plugins() abort 380 | if g:jetpack_download_method !=# 'git' 381 | return 382 | endif 383 | for [pkg_name, pkg] in items(s:declared_packages) 384 | call add(pkg.status, s:status.pending) 385 | endfor 386 | for [pkg_name, pkg] in items(s:declared_packages) 387 | call jetpack#show_progress('Switch Plugins') 388 | if !isdirectory(pkg.path) 389 | call add(pkg.status, s:status.skipped) 390 | continue 391 | else 392 | call add(pkg.status, s:status.switched) 393 | endif 394 | call jetpack#system(printf('git -C %s checkout %s', pkg.path, pkg.commit)) 395 | endfor 396 | endfunction 397 | 398 | function! jetpack#postupdate_plugins() abort 399 | for [pkg_name, pkg] in items(s:declared_packages) 400 | if empty(pkg.do) || pkg.output =~# 'Already up to date.' 401 | continue 402 | endif 403 | call jetpack#load(pkg_name) 404 | let pwd = chdir(pkg.path) 405 | if type(pkg.do) == v:t_func 406 | call pkg.do() 407 | elseif type(pkg.do) == v:t_string 408 | if pkg.do =~# '^:' 409 | execute pkg.do 410 | else 411 | call jetpack#system(pkg.do) 412 | endif 413 | endif 414 | call chdir(pwd) 415 | endfor 416 | for dir in glob(s:optdir . '/*/doc', '', 1) 417 | execute 'silent! helptags' dir 418 | endfor 419 | call mkdir(s:optdir . '/_/plugin', 'p') 420 | call mkdir(s:optdir . '/_/after/plugin', 'p') 421 | call writefile([ 422 | \ 'autocmd Jetpack User JetpackPre:init ++once :', 423 | \ 'doautocmd User JetpackPre:init' 424 | \ ], s:optdir . '/_/plugin/hook.vim') 425 | call writefile([ 426 | \ 'autocmd Jetpack User JetpackPost:init ++once :', 427 | \ 'doautocmd User JetpackPost:init' 428 | \ ], s:optdir . '/_/after/plugin/hook.vim') 429 | endfunction 430 | 431 | function! jetpack#sync() abort 432 | call jetpack#initialize_buffer() 433 | call jetpack#clean_plugins() 434 | call jetpack#download_plugins() 435 | call jetpack#switch_plugins() 436 | call jetpack#show_result() 437 | let s:available_packages = deepcopy(s:declared_packages) 438 | for pkg in values(s:available_packages) | unlet pkg.do | endfor 439 | call writefile([json_encode(s:available_packages)], s:optdir . '/available_packages.json') 440 | call jetpack#postupdate_plugins() 441 | if has('nvim') && !empty(luaeval('vim.loader')) 442 | lua vim.loader.reset() 443 | endif 444 | endfunction 445 | 446 | " Original: https://github.com/junegunn/vim-plug/blob/e3001/plug.vim#L479-L529 447 | " License: MIT, https://raw.githubusercontent.com/junegunn/vim-plug/e3001/LICENSE 448 | if has('win32') 449 | function! jetpack#is_local_plug(repo) abort 450 | return a:repo =~? '^[a-z]:\|^[%~]' 451 | endfunction 452 | else 453 | function! jetpack#is_local_plug(repo) abort 454 | return a:repo[0] =~# '[/$~]' 455 | endfunction 456 | endif 457 | 458 | function! jetpack#is_opt(pkg) abort 459 | return !empty(a:pkg.dependers_before) 460 | \ || !empty(a:pkg.dependers_after) 461 | \ || !empty(a:pkg.cmd) 462 | \ || !empty(a:pkg.keys) 463 | \ || !empty(a:pkg.event) 464 | endfunction 465 | 466 | function! jetpack#gets(pkg, keys, default) abort 467 | let values = [] 468 | for key in a:keys 469 | if has_key(a:pkg, key) 470 | if type(a:pkg[key]) == v:t_list 471 | call extend(values, a:pkg[key]) 472 | else 473 | call add(values, a:pkg[key]) 474 | endif 475 | endif 476 | endfor 477 | return empty(values) ? a:default : values 478 | endfunction 479 | 480 | function! jetpack#add(plugin, ...) abort 481 | let opts = a:0 > 0 ? a:1 : {} 482 | let name = jetpack#gets(opts, ['as', 'name'], [fnamemodify(a:plugin, ':t')])[0] 483 | if has_key(s:declared_packages, name) 484 | return 485 | endif 486 | let local = jetpack#is_local_plug(a:plugin) 487 | let url = local ? expand(a:plugin) : (a:plugin !~# '.\+://' ? 'https://github.com/' : '') . a:plugin 488 | let path = s:optdir . '/' . substitute(url, '.\+/\(.\+\)', '\1', '') 489 | let path = expand(local ? a:plugin : jetpack#gets(opts, ['dir', 'path'], [path])[0]) 490 | let dependees = jetpack#gets(opts, ['requires', 'depends'], []) 491 | call map(dependees, { _, r -> r =~# '/' ? substitute(r, '.*/', '', '') : r }) 492 | let dependers_before = jetpack#gets(opts, ['before', 'on_source'], []) 493 | call map(dependers_before, { _, r -> r =~# '/' ? substitute(r, '.*/', '', '') : r }) 494 | let dependers_after = jetpack#gets(opts, ['after', 'on_post_source'], []) 495 | call map(dependers_after, { _, r -> r =~# '/' ? substitute(r, '.*/', '', '') : r }) 496 | let keys_on = jetpack#gets(opts, ['on'], []) 497 | call filter(keys_on, { _, k -> k =~? '^' }) 498 | let keys = keys_on + jetpack#gets(opts, ['keys', 'on_map'], []) 499 | let cmd_on = jetpack#gets(opts, ['on'], []) 500 | call filter(cmd_on, { _, k -> k =~? '^[A-Z]' }) 501 | let cmd = cmd_on + jetpack#gets(opts, ['cmd', 'on_cmd'], []) 502 | let event = jetpack#gets(opts, ['on', 'event', 'on_event'], []) 503 | call filter(event, { _, v -> exists('##' . substitute(v, ' .*', '', ''))}) 504 | let filetypes = jetpack#gets(opts, ['for', 'ft', 'on_ft'], []) 505 | call extend(event, map(filetypes, {_, ft -> 'FileType ' . ft})) 506 | let pkg = { 507 | \ 'keys': keys, 508 | \ 'cmd': cmd, 509 | \ 'event': event, 510 | \ 'url': url, 511 | \ 'local': local, 512 | \ 'branch': get(opts, 'branch', ''), 513 | \ 'tag': get(opts, 'tag', ''), 514 | \ 'commit': get(opts, 'commit', 'HEAD'), 515 | \ 'rtp': get(opts, 'rtp', ''), 516 | \ 'do': jetpack#gets(opts, ['do', 'run', 'build'], [''])[0], 517 | \ 'frozen': jetpack#gets(opts, ['frozen', 'lock'], [v:false])[0], 518 | \ 'dir': jetpack#gets(opts, ['dir', 'path'], [''])[0], 519 | \ 'path': path, 520 | \ 'status': [s:status.pending], 521 | \ 'output': '', 522 | \ 'hook_add': get(opts, 'hook_add', ''), 523 | \ 'hook_source': get(opts, 'hook_source', ''), 524 | \ 'hook_post_source': get(opts, 'hook_post_source', ''), 525 | \ 'dependees': dependees, 526 | \ 'dependers_before': dependers_before, 527 | \ 'dependers_after': dependers_after, 528 | \ } 529 | let pkg.opt = get(opts, 'opt', jetpack#is_opt(pkg)) 530 | let s:declared_packages[name] = pkg 531 | call jetpack#execute(pkg.hook_add) 532 | endfunction 533 | 534 | function! jetpack#load_toml(path) abort 535 | let lines = readfile(a:path) 536 | for pkg in jetpack#parse_toml(lines) 537 | call jetpack#add(pkg.repo, pkg) 538 | endfor 539 | endfunction 540 | 541 | function! jetpack#begin(...) abort 542 | " In lua, passing nil and no argument are synonymous, but in practice, v:null is passed. 543 | if a:0 > 0 && a:1 != v:null 544 | let s:home = expand(a:1) 545 | execute 'set runtimepath^=' . expand(s:home) 546 | execute 'set packpath^=' . expand(s:home) 547 | elseif has('nvim') 548 | let s:home = stdpath('data') . '/' . 'site' 549 | elseif has('win32') 550 | let s:home = expand('~/vimfiles') 551 | else 552 | let s:home = expand('~/.vim') 553 | endif 554 | let s:cmds = {} 555 | let s:maps = {} 556 | let s:declared_packages = {} 557 | let s:optdir = s:home . '/pack/jetpack/opt' 558 | let runtimepath = split(&runtimepath, ',') 559 | let runtimepath = filter(runtimepath, {_, v -> v !~# s:optdir}) 560 | let &runtimepath = join(runtimepath, ',') 561 | let available_packages_file = s:optdir . '/available_packages.json' 562 | let available_packages_text = 563 | \ filereadable(available_packages_file) 564 | \ ? join(readfile(available_packages_file)) : "{}" 565 | let s:available_packages = json_decode(available_packages_text) 566 | augroup Jetpack 567 | autocmd! 568 | augroup END 569 | command! -nargs=+ -bar Jetpack call jetpack#add() 570 | endfunction 571 | 572 | function! jetpack#doautocmd(ord, pkg_name) abort 573 | let pkg = jetpack#get(a:pkg_name) 574 | if jetpack#tap(a:pkg_name) || (pkg.local && isdirectory(pkg.path . '/' . pkg.rtp)) 575 | let pattern_a = 'jetpack_' . a:pkg_name . '_' . a:ord 576 | let pattern_a = substitute(pattern_a, '\W\+', '_', 'g') 577 | let pattern_a = substitute(pattern_a, '\(^\|_\)\(.\)', '\u\2', 'g') 578 | let pattern_b = 'Jetpack' . substitute(a:ord, '.*', '\u\0', '') . ':'. a:pkg_name 579 | for pattern in [pattern_a, pattern_b] 580 | if exists('#User#' . pattern) 581 | execute 'doautocmd User' pattern 582 | endif 583 | endfor 584 | endif 585 | endfunction 586 | 587 | function! jetpack#load_plugin(pkg_name) abort 588 | let pkg = jetpack#get(a:pkg_name) 589 | for dep_name in pkg.dependees 590 | call jetpack#load_plugin(dep_name) 591 | endfor 592 | let &runtimepath = pkg.path . '/' . pkg.rtp . ',' . &runtimepath 593 | if v:vim_did_enter 594 | call jetpack#doautocmd('pre', a:pkg_name) 595 | for file in glob(pkg.path . '/' . pkg.rtp . '/plugin/**/*.vim', '', 1) 596 | execute 'source' file 597 | endfor 598 | for file in glob(pkg.path . '/' . pkg.rtp . '/plugin/**/*.lua', '', 1) 599 | execute 'luafile' file 600 | endfor 601 | else 602 | let cmd = 'call jetpack#doautocmd("pre", "'.a:pkg_name.'")' 603 | execute 'autocmd Jetpack User JetpackPre:init ++once' cmd 604 | endif 605 | endfunction 606 | 607 | function! jetpack#load_after_plugin(pkg_name) abort 608 | let pkg = jetpack#get(a:pkg_name) 609 | let &runtimepath = &runtimepath . ',' . pkg.path . '/' . pkg.rtp 610 | if v:vim_did_enter 611 | for file in glob(pkg.path . '/' . pkg.rtp . '/after/plugin/**/*.vim', '', 1) 612 | execute 'source' file 613 | endfor 614 | for file in glob(pkg.path . '/' . pkg.rtp . '/after/plugin/**/*.lua', '', 1) 615 | execute 'luafile' file 616 | endfor 617 | call jetpack#doautocmd('post', a:pkg_name) 618 | else 619 | let cmd = 'call jetpack#doautocmd("post", "'.a:pkg_name.'")' 620 | execute 'autocmd Jetpack User JetpackPost:init ++once' cmd 621 | endif 622 | for dep_name in pkg.dependees 623 | call jetpack#load_after_plugin(dep_name) 624 | endfor 625 | endfunction 626 | 627 | function! jetpack#check_dependees(pkg_name) abort 628 | if !jetpack#tap(a:pkg_name) 629 | return v:false 630 | endif 631 | let pkg = jetpack#get(a:pkg_name) 632 | for dep_name in pkg.dependees 633 | if !jetpack#check_dependees(dep_name) 634 | return v:false 635 | endif 636 | endfor 637 | return v:true 638 | endfunction 639 | 640 | function! jetpack#load(pkg_name) abort 641 | if !jetpack#check_dependees(a:pkg_name) 642 | return v:false 643 | endif 644 | call jetpack#load_plugin(a:pkg_name) 645 | call jetpack#load_after_plugin(a:pkg_name) 646 | return v:true 647 | endfunction 648 | 649 | " Original: https://github.com/junegunn/vim-plug/blob/e3001/plug.vim#L683-L693 650 | " License: MIT, https://raw.githubusercontent.com/junegunn/vim-plug/e3001/LICENSE 651 | function! jetpack#load_map(map, names, with_prefix, prefix) 652 | for name in a:names 653 | call jetpack#load(name) 654 | endfor 655 | let extra = '' 656 | let code = getchar(0) 657 | while (code != 0 && code != 27) 658 | let extra .= nr2char(code) 659 | let code = getchar(0) 660 | endwhile 661 | if a:with_prefix 662 | let prefix = v:count ? v:count : '' 663 | let prefix .= '"'.v:register.a:prefix 664 | if mode(1) ==# 'no' 665 | if v:operator ==# 'c' 666 | let prefix = "\" . prefix 667 | endif 668 | let prefix .= v:operator 669 | endif 670 | call feedkeys(prefix, 'n') 671 | endif 672 | call feedkeys(substitute(a:map, '^', "\", 'i') . extra) 673 | endfunction 674 | 675 | function! jetpack#load_cmd(cmd, names, ...) abort 676 | execute 'delcommand' a:cmd 677 | for name in a:names 678 | call jetpack#load(name) 679 | endfor 680 | let args = a:0>0 ? join(a:000, ' ') : '' 681 | try 682 | execute a:cmd args 683 | catch /.*/ 684 | echohl ErrorMsg 685 | echomsg v:exception 686 | echohl None 687 | endtry 688 | endfunction 689 | 690 | function! jetpack#end() abort 691 | let runtimepath = [] 692 | delcommand Jetpack 693 | command! -bar JetpackSync call jetpack#sync() 694 | 695 | syntax off 696 | filetype plugin indent off 697 | 698 | if !has_key(s:declared_packages, 'vim-jetpack') 699 | echomsg 'vim-jetpack is not declared. Please add jetpack#add("tani/vim-jetpack") .' 700 | endif 701 | 702 | if sort(keys(s:declared_packages)) != sort(keys(s:available_packages)) 703 | echomsg 'Some packages are not synchronized. Run :JetpackSync' 704 | endif 705 | 706 | for [pkg_name, pkg] in items(s:declared_packages) 707 | for dep_name in pkg.dependers_before 708 | let cmd = 'call jetpack#load("'.pkg_name.'")' 709 | let pattern = 'JetpackPre:'.dep_name 710 | execute 'autocmd Jetpack User' pattern '++once' cmd 711 | endfor 712 | let slug = substitute(pkg_name, '\W\+', '_', 'g') 713 | let s:loaded_count_{slug} = len(pkg.dependers_after) 714 | for dep_name in pkg.dependers_after 715 | let cmd = 'if s:loaded_count_'.slug.' == 1 '. 716 | \ '| call jetpack#load("'.pkg_name.'") '. 717 | \ '| else'. 718 | \ '| let s:loaded_count_'.slug.' -= 1 '. 719 | \ '| endif' 720 | let pattern = 'JetpackPost:'.dep_name 721 | execute 'autocmd Jetpack User' pattern '++once' cmd 722 | endfor 723 | for it in pkg.keys 724 | let s:maps[it] = add(get(s:maps, it, []), pkg_name) 725 | execute printf('inoremap %s :call jetpack#load_map("%s", %s, 0, "")', it, it, s:maps[it]) 726 | execute printf('nnoremap %s :call jetpack#load_map("%s", %s, 1, "")', it, it, s:maps[it]) 727 | execute printf('vnoremap %s :call jetpack#load_map("%s", %s, 1, "gv")', it, it, s:maps[it]) 728 | execute printf('onoremap %s :call jetpack#load_map("%s", %s, 1, "")', it, it, s:maps[it]) 729 | endfor 730 | for it in pkg.event 731 | let cmd = 'call jetpack#load("'.pkg_name.'")' 732 | let [event, pattern] = split(it . (it =~# ' ' ? '' : ' *'), ' ') 733 | execute 'autocmd Jetpack' event pattern '++once' cmd 734 | endfor 735 | for it in pkg.cmd 736 | let cmd_name = substitute(it, '^:', '', '') 737 | let s:cmds[cmd_name] = add(get(s:cmds, cmd_name, []), pkg_name) 738 | let cmd = printf('call jetpack#load_cmd("%s", %s, )', cmd_name, s:cmds[cmd_name]) 739 | execute 'command! -range -nargs=*' cmd_name ':' cmd 740 | endfor 741 | if !empty(pkg.hook_source) 742 | let pattern = 'JetpackPre:'.pkg_name 743 | let cmd = 'call jetpack#execute(s:declared_packages["'.pkg_name.'"].hook_source)' 744 | execute 'autocmd Jetpack User' pattern '++once' cmd 745 | endif 746 | if !empty(pkg.hook_post_source) 747 | let pattern = 'JetpackPost:'.pkg_name 748 | let cmd = 'call jetpack#execute(s:declared_packages["'.pkg_name.'"].hook_post_source)' 749 | execute 'autocmd Jetpack User' pattern '++once' cmd 750 | endif 751 | if pkg.opt 752 | for file in glob(pkg.path . '/ftdetect/*.vim', '', 1) 753 | "echomsg '[[source' file ']]' 754 | execute 'source' file 755 | endfor 756 | for file in glob(pkg.path . '/ftdetect/*.lua', '', 1) 757 | "echomsg '[[luafile' file ']]' 758 | execute 'luafile' file 759 | endfor 760 | else 761 | let runtimepath = extend([pkg.path . '/' . pkg.rtp], runtimepath) 762 | let runtimepath = extend(runtimepath, [pkg.path . '/' . pkg.rtp . '/after']) 763 | let cmd = 'call jetpack#doautocmd("pre", "'.pkg_name.'")' 764 | execute 'autocmd Jetpack User JetpackPre:init ++once' cmd 765 | let cmd = 'call jetpack#doautocmd("post", "'.pkg_name.'")' 766 | execute 'autocmd Jetpack User JetpackPost:init ++once' cmd 767 | endif 768 | endfor 769 | let runtimepath = extend([s:optdir . '/_'], runtimepath) 770 | let runtimepath = extend(runtimepath, [s:optdir . '/_/after']) 771 | let &runtimepath .= ',' . join(runtimepath, ',') 772 | syntax enable 773 | filetype plugin indent on 774 | if has('nvim') && !empty(luaeval('vim.loader')) 775 | lua vim.loader.enable() 776 | endif 777 | endfunction 778 | 779 | function! jetpack#tap(name) abort 780 | return has_key(s:available_packages, a:name) && has_key(s:declared_packages, a:name) 781 | endfunction 782 | 783 | function! jetpack#names() abort 784 | return keys(s:declared_packages) 785 | endfunction 786 | 787 | function! jetpack#get(name) abort 788 | return get(s:declared_packages, a:name, {}) 789 | endfunction 790 | 791 | if !has('nvim') && !(has('lua') && has('patch-8.2.0775')) 792 | finish 793 | endif 794 | 795 | lua<:p:h:h'))) 4 | 5 | function s:fallback(val, default) 6 | return empty(a:val) ? a:default : a:val 7 | endfunction 8 | 9 | let g:jetpack_download_method = s:fallback(getenv('JETPACK_DOWNLOAD_METHOD'), 'git') 10 | let g:jetpack_njobs = s:fallback(getenv('JETPACK_NJOBS'), 1) 11 | 12 | let s:suite = themis#suite('Jetpack Tests') 13 | let s:assert = themis#helper('assert') 14 | let g:vimhome = substitute(expand(':p:h'), '\', '/', 'g') 15 | let s:optdir = g:vimhome . '/pack/jetpack/opt' 16 | 17 | call delete(g:vimhome . '/pack', 'rf') 18 | 19 | function Git(dir, commands) 20 | for cmd in a:commands 21 | call system('git -C '.a:dir.' '.cmd) 22 | endfor 23 | endfunction 24 | 25 | function Setup(...) 26 | call jetpack#begin(g:vimhome) 27 | for plugin in a:000 28 | if len(plugin) == 2 29 | call jetpack#add(plugin[0], plugin[1]) 30 | else 31 | call jetpack#add(plugin[0]) 32 | endif 33 | endfor 34 | call jetpack#end() 35 | call jetpack#sync() 36 | call jetpack#begin(g:vimhome) 37 | for plugin in a:000 38 | if len(plugin) == 2 39 | call jetpack#add(plugin[0], plugin[1]) 40 | else 41 | call jetpack#add(plugin[0]) 42 | endif 43 | endfor 44 | call jetpack#end() 45 | 46 | for pkg_name in jetpack#names() 47 | if !jetpack#get(pkg_name).opt 48 | call jetpack#load(pkg_name) 49 | endif 50 | endfor 51 | call feedkeys("\", 'n') 52 | endfunction 53 | 54 | function DummyPath(name) 55 | return substitute(fnamemodify(g:vimhome, ':p:h:h').'/data/'.a:name, '\', '/', 'g') 56 | endfunction 57 | 58 | function DummyUrl(name) 59 | return 'file://'.DummyPath(a:name) 60 | endfunction 61 | 62 | let s:counter = 0 63 | function UniqueId() 64 | let s:counter += 1 65 | return 'X'.s:counter 66 | endfunction 67 | 68 | function s:assert.filereadable(file) 69 | if !filereadable(a:file) 70 | call s:assert.fail(a:file . ' is not readable') 71 | endif 72 | endfunction 73 | 74 | function s:assert.notfilereadable(file) 75 | if filereadable(a:file) 76 | call s:assert.fail(a:file . ' is readable') 77 | endif 78 | endfunction 79 | 80 | function s:assert.isdirectory(dir) 81 | if !isdirectory(a:dir) 82 | call s:assert.fail(a:dir . ' is not a directory') 83 | endif 84 | endfunction 85 | 86 | function s:assert.isnotdirectory(dir) 87 | if isdirectory(a:dir) 88 | call s:assert.fail(a:dir . ' is a directory') 89 | endif 90 | endfunction 91 | 92 | function s:assert.loaded(package) 93 | try 94 | let loaded = luaeval('package.loaded[_A]', a:package) 95 | call s:assert.not_equals(loaded, v:null, a:package . ' is not loaded') 96 | catch /.*/ 97 | " Cannot convert given lua type. So, not v:null (it's loaded). 98 | endtry 99 | endfunction 100 | 101 | function s:assert.notloaded(package) 102 | try 103 | let loaded = luaeval('package.loaded[_A]', a:package) 104 | call s:assert.equals(loaded, v:null, a:package . ' is loaded') 105 | catch /.*/ 106 | " Cannot convert given lua type. So, not v:null (it's loaded). 107 | call s:assert.fail(a:package . ' is loaded') 108 | endtry 109 | endfunction 110 | 111 | function s:suite.parse_toml() 112 | let toml =< execute('call Install'.g:id.'()') } }]) 215 | call s:assert.isdirectory(s:optdir.'/'.g:id) 216 | call s:assert.exists('g:loaded_'.g:id) 217 | call s:assert.exists('g:installed_'.g:id) 218 | delfunction Install{g:id} 219 | endfunction 220 | 221 | function s:suite.on_ft() 222 | let g:id = UniqueId() 223 | call mkdir(DummyPath(g:id).'/plugin', 'p') 224 | call writefile(['let g:loaded_'.g:id.' = 1'], DummyPath(g:id).'/plugin/'.g:id.'.vim') 225 | call Git(DummyPath(g:id), ['init', 'add -A', 'commit -m "Initial commit"']) 226 | call Setup([DummyUrl(g:id), { 'on_ft': 'c' }]) 227 | call s:assert.isdirectory(s:optdir.'/'.g:id) 228 | call s:assert.not_exists('g:loaded_'.g:id) 229 | let filetype = &filetype 230 | edit dummy.c 231 | call s:assert.exists('g:loaded_'.g:id) 232 | let &filetype = filetype 233 | endfunction 234 | 235 | function s:suite.on_cmd() 236 | let g:id = UniqueId() 237 | call mkdir(DummyPath(g:id).'/plugin', 'p') 238 | call writefile([ 239 | \ 'let g:loaded_'.g:id.' = 1', 240 | \ 'command! Load'.g:id.' :' 241 | \ ], 242 | \ DummyPath(g:id).'/plugin/'.g:id.'.vim') 243 | call Git(DummyPath(g:id), ['init', 'add -A', 'commit -m "Initial commit"']) 244 | call Setup([DummyUrl(g:id), { 'on_cmd': 'Load'.g:id }]) 245 | call s:assert.isdirectory(s:optdir.'/'.g:id) 246 | call s:assert.not_exists('g:loaded_'.g:id) 247 | silent! execute 'Load'.g:id 248 | call s:assert.exists('g:loaded_'.g:id) 249 | endfunction 250 | 251 | function s:suite.on_map() 252 | let g:id = UniqueId() 253 | call mkdir(DummyPath(g:id).'/plugin', 'p') 254 | call writefile([ 255 | \ 'let g:loaded_'.g:id.' = 1', 256 | \ 'nnoremap ,x :' 257 | \], 258 | \ DummyPath(g:id).'/plugin/'.g:id.'.vim') 259 | call Git(DummyPath(g:id), ['init', 'add -A', 'commit -m "Initial commit"']) 260 | call Setup([DummyUrl(g:id), { 'on_map': ',x' }]) 261 | call s:assert.isdirectory(s:optdir.'/'.g:id) 262 | call s:assert.not_exists('g:loaded_'.g:id) 263 | normal ,x 264 | call s:assert.exists('g:loaded_'.g:id) 265 | endfunction 266 | 267 | function s:suite.on_source() 268 | let g:id1 = UniqueId() 269 | let g:id2 = UniqueId() 270 | call mkdir(DummyPath(g:id1).'/plugin', 'p') 271 | call writefile( 272 | \ ['let g:loaded_'.g:id1.' = 1'], 273 | \ DummyPath(g:id1).'/plugin/'.g:id1.'.vim' 274 | \ ) 275 | call Git(DummyPath(g:id1), ['init', 'add -A', 'commit -m "Initial commit"']) 276 | call mkdir(DummyPath(g:id2).'/plugin', 'p') 277 | call writefile( 278 | \ ['let g:loaded_'.g:id2.' = 1'], 279 | \ DummyPath(g:id2).'/plugin/'.g:id2.'.vim' 280 | \ ) 281 | call Git(DummyPath(g:id2), ['init', 'add -A', 'commit -m "Initial commit"']) 282 | call Setup( 283 | \ [DummyUrl(g:id1), { 'opt': 1 }], 284 | \ [DummyUrl(g:id2), { 'on_source': g:id1 }] 285 | \ ) 286 | call s:assert.isdirectory(s:optdir.'/'.g:id1) 287 | call s:assert.isdirectory(s:optdir.'/'.g:id2) 288 | call s:assert.not_exists('g:loaded_'.g:id1) 289 | call s:assert.not_exists('g:loaded_'.g:id2) 290 | call jetpack#load(g:id1) 291 | call s:assert.exists('g:loaded_'.g:id1) 292 | call s:assert.exists('g:loaded_'.g:id2) 293 | endfunction 294 | 295 | function s:suite.on_post_source() 296 | let g:id1 = UniqueId() 297 | let g:id2 = UniqueId() 298 | call mkdir(DummyPath(g:id1).'/plugin', 'p') 299 | call writefile( 300 | \ ['let g:loaded_'.g:id1.' = 1'], 301 | \ DummyPath(g:id1).'/plugin/'.g:id1.'.vim' 302 | \ ) 303 | call Git(DummyPath(g:id1), ['init', 'add -A', 'commit -m "Initial commit"']) 304 | call mkdir(DummyPath(g:id2).'/plugin', 'p') 305 | call writefile( 306 | \ ['let g:loaded_'.g:id2.' = 1'], 307 | \ DummyPath(g:id2).'/plugin/'.g:id2.'.vim' 308 | \ ) 309 | call Git(DummyPath(g:id2), ['init', 'add -A', 'commit -m "Initial commit"']) 310 | call Setup( 311 | \ [DummyUrl(g:id1), { 'opt': 1 }], 312 | \ [DummyUrl(g:id2), { 'on_post_source': g:id1 }] 313 | \ ) 314 | call s:assert.isdirectory(s:optdir.'/'.g:id1) 315 | call s:assert.isdirectory(s:optdir.'/'.g:id2) 316 | call s:assert.not_exists('g:loaded_'.g:id1) 317 | call s:assert.not_exists('g:loaded_'.g:id2) 318 | call jetpack#load(g:id1) 319 | call s:assert.exists('g:loaded_'.g:id1) 320 | call s:assert.exists('g:loaded_'.g:id2) 321 | endfunction 322 | 323 | function s:suite.on_event() 324 | let g:id = UniqueId() 325 | call mkdir(DummyPath(g:id).'/plugin', 'p') 326 | call writefile( 327 | \ ['let g:loaded_'.g:id.' = 1'], 328 | \ DummyPath(g:id).'/plugin/'.g:id.'.vim' 329 | \ ) 330 | call Git(DummyPath(g:id), ['init', 'add -A', 'commit -m "Initial commit"']) 331 | call Setup([DummyUrl(g:id), { 'on_event': 'User '.g:id }]) 332 | call s:assert.isdirectory(s:optdir.'/'.g:id) 333 | call s:assert.not_exists('g:loaded_'.g:id) 334 | execute 'doautocmd User' g:id 335 | call s:assert.exists('g:loaded_'.g:id) 336 | endfunction 337 | 338 | function s:suite.rtp() 339 | let g:id = UniqueId() 340 | call mkdir(DummyPath(g:id).'/vim/plugin', 'p') 341 | call writefile( 342 | \ ['let g:loaded_'.g:id.' = 1'], 343 | \ DummyPath(g:id).'/vim/plugin/'.g:id.'.vim' 344 | \ ) 345 | call Git(DummyPath(g:id), ['init', 'add -A', 'commit -m "Initial commit"']) 346 | call Setup([DummyUrl(g:id), { 'rtp': 'vim' }]) 347 | call s:assert.filereadable(s:optdir . '/'.g:id.'/vim/plugin/'.g:id.'.vim') 348 | call s:assert.exists('g:loaded_'.g:id) 349 | endfunction 350 | 351 | function s:suite.issue15() 352 | let g:id = UniqueId() 353 | call mkdir(DummyPath(g:id).'/autoload/test', 'p') 354 | call writefile( 355 | \ ['let g:loaded_'.g:id.' = 1'], 356 | \ DummyPath(g:id).'/autoload/test/'.g:id.'.vim' 357 | \ ) 358 | call Git(DummyPath(g:id), ['init', 'add -A', 'commit -m "Initial commit"']) 359 | call Setup([DummyUrl(g:id)]) 360 | call s:assert.isdirectory(s:optdir . '/'.g:id.'/autoload/test') 361 | endfunction 362 | 363 | function s:suite.names() 364 | let g:id = UniqueId() 365 | call mkdir(DummyPath(g:id).'/plugin', 'p') 366 | call writefile( 367 | \ ['let g:loaded_'.g:id.' = 1'], 368 | \ DummyPath(g:id).'/plugin/'.g:id.'.vim' 369 | \ ) 370 | call Git(DummyPath(g:id), ['init', 'add -A', 'commit -m "Initial commit"']) 371 | call Setup([DummyUrl(g:id)]) 372 | call s:assert.equals(jetpack#names(), [g:id]) 373 | endfunction 374 | 375 | function s:suite.tap() 376 | let g:id = UniqueId() 377 | call mkdir(DummyPath(g:id).'/plugin', 'p') 378 | call writefile( 379 | \ ['let g:loaded_'.g:id.' = 1'], 380 | \ DummyPath(g:id).'/plugin/'.g:id.'.vim' 381 | \ ) 382 | call Git(DummyPath(g:id), ['init', 'add -A', 'commit -m "Initial commit"']) 383 | call Setup([DummyUrl(g:id)]) 384 | call s:assert.filereadable(s:optdir . '/'.g:id.'/plugin/'.g:id.'.vim') 385 | call s:assert.true(jetpack#tap(g:id), g:id.' is not installed') 386 | call s:assert.false(jetpack#tap('_'), '_ is installed') 387 | endfunction 388 | 389 | function s:suite.get() 390 | let g:id = UniqueId() 391 | call mkdir(DummyPath(g:id).'/plugin', 'p') 392 | call writefile( 393 | \ ['let g:loaded_'.g:id.' = 1'], 394 | \ DummyPath(g:id).'/plugin/'.g:id.'.vim' 395 | \ ) 396 | call Git(DummyPath(g:id), ['init', 'add -A', 'commit -m "Initial commit"']) 397 | call Setup([DummyUrl(g:id)]) 398 | call s:assert.filereadable(s:optdir . '/'.g:id.'/plugin/'.g:id.'.vim') 399 | let data = jetpack#get(g:id) 400 | call s:assert.equals(type(data), type({})) 401 | call s:assert.false(empty(data), 'data is empty') 402 | endfunction 403 | 404 | function s:suite.frozen_option() 405 | call s:assert.skip('') 406 | endfunction 407 | 408 | function s:suite.tag_option() 409 | let g:id = UniqueId() 410 | call mkdir(DummyPath(g:id).'/plugin', 'p') 411 | call writefile( 412 | \ ['let g:loaded_'.g:id.' = 1'], 413 | \ DummyPath(g:id).'/plugin/'.g:id.'.vim' 414 | \ ) 415 | call Git(DummyPath(g:id), ['init', 'add -A', 'commit -m "Initial commit"', 'tag v1']) 416 | call writefile( 417 | \ ['let g:loaded_'.g:id.' = 2'], 418 | \ DummyPath(g:id).'/plugin/'.g:id.'.vim' 419 | \ ) 420 | call Git(DummyPath(g:id), ['add -A', 'commit -m "Second commit"', 'tag v2']) 421 | call Setup([DummyUrl(g:id), {'tag': 'v1'}]) 422 | call s:assert.filereadable(s:optdir . '/'.g:id.'/plugin/'.g:id.'.vim') 423 | call s:assert.equals(g:loaded_{g:id}, 1) 424 | endfunction 425 | 426 | function s:suite.branch_option() 427 | let g:id = UniqueId() 428 | call mkdir(DummyPath(g:id).'/plugin', 'p') 429 | call writefile( 430 | \ ['let g:loaded_'.g:id.' = 1'], 431 | \ DummyPath(g:id).'/plugin/'.g:id.'.vim' 432 | \ ) 433 | call Git(DummyPath(g:id), ['init', 'add -A', 'commit -m "Initial commit"', 'branch other', 'switch other']) 434 | call writefile( 435 | \ ['let g:loaded_'.g:id.' = 2'], 436 | \ DummyPath(g:id).'/plugin/'.g:id.'.vim' 437 | \ ) 438 | call Git(DummyPath(g:id), ['add -A', 'commit -m "Second commit"', 'switch main']) 439 | call Setup([DummyUrl(g:id), { 'branch': 'other' }]) 440 | call s:assert.filereadable(s:optdir . '/'.g:id.'/plugin/'.g:id.'.vim') 441 | call s:assert.equals(g:loaded_{g:id}, 2) 442 | endfunction 443 | 444 | function s:suite.commit_option() 445 | let g:id = UniqueId() 446 | call mkdir(DummyPath(g:id).'/plugin', 'p') 447 | call writefile( 448 | \ ['let g:loaded_'.g:id.' = 1'], 449 | \ DummyPath(g:id).'/plugin/'.g:id.'.vim' 450 | \ ) 451 | call Git(DummyPath(g:id), ['init', 'add -A', 'commit -m "Initial commit"']) 452 | call writefile( 453 | \ ['let g:loaded_'.g:id.' = 2'], 454 | \ DummyPath(g:id).'/plugin/'.g:id.'.vim' 455 | \ ) 456 | call Git(DummyPath(g:id), ['add -A', 'commit -m "Second commit"']) 457 | let g:ids = systemlist('git -C '.DummyPath(g:id).' log -3 --pretty=format:"%h"') 458 | call Setup([DummyUrl(g:id), { 'commit': g:ids[1] }]) 459 | call s:assert.filereadable(s:optdir . '/'.g:id.'/plugin/'.g:id.'.vim') 460 | call s:assert.equals(g:loaded_{g:id}, 1) 461 | endfunction 462 | 463 | function s:suite.issue70() 464 | let g:id1 = UniqueId() 465 | let g:id2 = UniqueId() 466 | call mkdir(DummyPath(g:id1).'/screenshots', 'p') 467 | call writefile( 468 | \ ['let g:loaded_'.g:id1.' = 1'], 469 | \ DummyPath(g:id1).'/screenshots/'.g:id1.'.vim' 470 | \ ) 471 | call Git(DummyPath(g:id1), ['init', 'add -A', 'commit -m "Initial commit"']) 472 | call mkdir(DummyPath(g:id2), 'p') 473 | call writefile( 474 | \ ['let g:loaded_'.g:id2.' = 1'], 475 | \ DummyPath(g:id2).'/screenshots' 476 | \ ) 477 | call Git(DummyPath(g:id2), ['init', 'add -A', 'commit -m "Initial commit"']) 478 | call Setup([DummyUrl(g:id1)], [DummyUrl(g:id2)]) 479 | call s:assert.isdirectory(s:optdir . '/'.g:id1.'/screenshots') 480 | call s:assert.filereadable(s:optdir. '/'.g:id2.'/screenshots') 481 | endfunction 482 | 483 | function s:suite.local_plugin() 484 | let g:id = UniqueId() 485 | call mkdir(DummyPath(g:id).'/plugin', 'p') 486 | call writefile( 487 | \ ['let g:loaded_'.g:id.' = 1'], 488 | \ DummyPath(g:id).'/plugin/'.g:id.'.vim' 489 | \ ) 490 | call Git(DummyPath(g:id), ['init', 'add -A', 'commit -m "Initial commit"']) 491 | call Setup([DummyPath(g:id)]) 492 | call s:assert.isdirectory(DummyPath(g:id)) 493 | call s:assert.exists('g:loaded_'.g:id) 494 | endfunction 495 | 496 | function s:suite.curl() 497 | let g:jetpack_download_method = 'curl' 498 | call Setup(['tani/vim-jetpack', {'opt': 1}]) 499 | call s:assert.isdirectory(s:optdir . '/vim-jetpack') 500 | let g:jetpack_download_method = 'git' 501 | endfunction 502 | 503 | function s:suite.ftdetect() 504 | let g:id = UniqueId() 505 | call mkdir(DummyPath(g:id).'/ftdetect', 'p') 506 | call writefile( 507 | \ ['autocmd BufRead,BufNewFile *.foo set filetype=foo'], 508 | \ DummyPath(g:id).'/ftdetect/foo.vim' 509 | \ ) 510 | call Git(DummyPath(g:id), ['init', 'add -A', 'commit -m "Initial commit"']) 511 | call Setup([DummyUrl(g:id), { 'on_ft': 'foo' }]) 512 | call s:assert.filereadable(s:optdir . '/'.g:id.'/ftdetect/foo.vim') 513 | let filetype = &filetype 514 | edit dummy.foo 515 | call s:assert.equals(&filetype, 'foo') 516 | let &filetype = filetype 517 | endfunction 518 | 519 | if !has('nvim') && !(has('lua') && has('patch-8.2.0775')) 520 | finish 521 | endif 522 | 523 | lua <