.config/vim/autoload/plug.vim (82679B)
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 `<Plug>`-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('<sfile>:p')) 105 set shellslash 106 else 107 let s:me = resolve(expand('<sfile>: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 !empty(&rtp) 246 let home = s:path(split(&rtp, ',')[0]) . '/plugged' 247 else 248 return s:err('Unable to determine plug home. Try calling plug#begin() with a path argument.') 249 endif 250 if s:plug_fnamemodify(home, ':t') ==# 'plugin' && s:plug_fnamemodify(home, ':h') ==# s:first_rtp 251 return s:err('Invalid plug home. '.home.' is a standard Vim runtime path and is not allowed.') 252 endif 253 254 let g:plug_home = home 255 let g:plugs = {} 256 let g:plugs_order = [] 257 let s:triggers = {} 258 259 call s:define_commands() 260 return 1 261 endfunction 262 263 function! s:define_commands() 264 command! -nargs=+ -bar Plug call plug#(<args>) 265 if !executable('git') 266 return s:err('`git` executable not found. Most commands will not be available. To suppress this message, prepend `silent!` to `call plug#begin(...)`.') 267 endif 268 if has('win32') 269 \ && &shellslash 270 \ && (&shell =~# 'cmd\(\.exe\)\?$' || s:is_powershell(&shell)) 271 return s:err('vim-plug does not support shell, ' . &shell . ', when shellslash is set.') 272 endif 273 if !has('nvim') 274 \ && (has('win32') || has('win32unix')) 275 \ && !has('multi_byte') 276 return s:err('Vim needs +multi_byte feature on Windows to run shell commands. Enable +iconv for best results.') 277 endif 278 command! -nargs=* -bar -bang -complete=customlist,s:names PlugInstall call s:install(<bang>0, [<f-args>]) 279 command! -nargs=* -bar -bang -complete=customlist,s:names PlugUpdate call s:update(<bang>0, [<f-args>]) 280 command! -nargs=0 -bar -bang PlugClean call s:clean(<bang>0) 281 command! -nargs=0 -bar PlugUpgrade if s:upgrade() | execute 'source' s:esc(s:me) | endif 282 command! -nargs=0 -bar PlugStatus call s:status() 283 command! -nargs=0 -bar PlugDiff call s:diff() 284 command! -nargs=? -bar -bang -complete=file PlugSnapshot call s:snapshot(<bang>0, <f-args>) 285 endfunction 286 287 function! s:to_a(v) 288 return type(a:v) == s:TYPE.list ? a:v : [a:v] 289 endfunction 290 291 function! s:to_s(v) 292 return type(a:v) == s:TYPE.string ? a:v : join(a:v, "\n") . "\n" 293 endfunction 294 295 function! s:glob(from, pattern) 296 return s:lines(globpath(a:from, a:pattern)) 297 endfunction 298 299 function! s:source(from, ...) 300 let found = 0 301 for pattern in a:000 302 for vim in s:glob(a:from, pattern) 303 execute 'source' s:esc(vim) 304 let found = 1 305 endfor 306 endfor 307 return found 308 endfunction 309 310 function! s:assoc(dict, key, val) 311 let a:dict[a:key] = add(get(a:dict, a:key, []), a:val) 312 endfunction 313 314 function! s:ask(message, ...) 315 call inputsave() 316 echohl WarningMsg 317 let answer = input(a:message.(a:0 ? ' (y/N/a) ' : ' (y/N) ')) 318 echohl None 319 call inputrestore() 320 echo "\r" 321 return (a:0 && answer =~? '^a') ? 2 : (answer =~? '^y') ? 1 : 0 322 endfunction 323 324 function! s:ask_no_interrupt(...) 325 try 326 return call('s:ask', a:000) 327 catch 328 return 0 329 endtry 330 endfunction 331 332 function! s:lazy(plug, opt) 333 return has_key(a:plug, a:opt) && 334 \ (empty(s:to_a(a:plug[a:opt])) || 335 \ !isdirectory(a:plug.dir) || 336 \ len(s:glob(s:rtp(a:plug), 'plugin')) || 337 \ len(s:glob(s:rtp(a:plug), 'after/plugin'))) 338 endfunction 339 340 function! plug#end() 341 if !exists('g:plugs') 342 return s:err('plug#end() called without calling plug#begin() first') 343 endif 344 345 if exists('#PlugLOD') 346 augroup PlugLOD 347 autocmd! 348 augroup END 349 augroup! PlugLOD 350 endif 351 let lod = { 'ft': {}, 'map': {}, 'cmd': {} } 352 353 if exists('g:did_load_filetypes') 354 filetype off 355 endif 356 for name in g:plugs_order 357 if !has_key(g:plugs, name) 358 continue 359 endif 360 let plug = g:plugs[name] 361 if get(s:loaded, name, 0) || !s:lazy(plug, 'on') && !s:lazy(plug, 'for') 362 let s:loaded[name] = 1 363 continue 364 endif 365 366 if has_key(plug, 'on') 367 let s:triggers[name] = { 'map': [], 'cmd': [] } 368 for cmd in s:to_a(plug.on) 369 if cmd =~? '^<Plug>.\+' 370 if empty(mapcheck(cmd)) && empty(mapcheck(cmd, 'i')) 371 call s:assoc(lod.map, cmd, name) 372 endif 373 call add(s:triggers[name].map, cmd) 374 elseif cmd =~# '^[A-Z]' 375 let cmd = substitute(cmd, '!*$', '', '') 376 if exists(':'.cmd) != 2 377 call s:assoc(lod.cmd, cmd, name) 378 endif 379 call add(s:triggers[name].cmd, cmd) 380 else 381 call s:err('Invalid `on` option: '.cmd. 382 \ '. Should start with an uppercase letter or `<Plug>`.') 383 endif 384 endfor 385 endif 386 387 if has_key(plug, 'for') 388 let types = s:to_a(plug.for) 389 if !empty(types) 390 augroup filetypedetect 391 call s:source(s:rtp(plug), 'ftdetect/**/*.vim', 'after/ftdetect/**/*.vim') 392 augroup END 393 endif 394 for type in types 395 call s:assoc(lod.ft, type, name) 396 endfor 397 endif 398 endfor 399 400 for [cmd, names] in items(lod.cmd) 401 execute printf( 402 \ 'command! -nargs=* -range -bang -complete=file %s call s:lod_cmd(%s, "<bang>", <line1>, <line2>, <q-args>, %s)', 403 \ cmd, string(cmd), string(names)) 404 endfor 405 406 for [map, names] in items(lod.map) 407 for [mode, map_prefix, key_prefix] in 408 \ [['i', '<C-O>', ''], ['n', '', ''], ['v', '', 'gv'], ['o', '', '']] 409 execute printf( 410 \ '%snoremap <silent> %s %s:<C-U>call <SID>lod_map(%s, %s, %s, "%s")<CR>', 411 \ mode, map, map_prefix, string(map), string(names), mode != 'i', key_prefix) 412 endfor 413 endfor 414 415 for [ft, names] in items(lod.ft) 416 augroup PlugLOD 417 execute printf('autocmd FileType %s call <SID>lod_ft(%s, %s)', 418 \ ft, string(ft), string(names)) 419 augroup END 420 endfor 421 422 call s:reorg_rtp() 423 filetype plugin indent on 424 if has('vim_starting') 425 if has('syntax') && !exists('g:syntax_on') 426 syntax enable 427 end 428 else 429 call s:reload_plugins() 430 endif 431 endfunction 432 433 function! s:loaded_names() 434 return filter(copy(g:plugs_order), 'get(s:loaded, v:val, 0)') 435 endfunction 436 437 function! s:load_plugin(spec) 438 call s:source(s:rtp(a:spec), 'plugin/**/*.vim', 'after/plugin/**/*.vim') 439 endfunction 440 441 function! s:reload_plugins() 442 for name in s:loaded_names() 443 call s:load_plugin(g:plugs[name]) 444 endfor 445 endfunction 446 447 function! s:trim(str) 448 return substitute(a:str, '[\/]\+$', '', '') 449 endfunction 450 451 function! s:version_requirement(val, min) 452 for idx in range(0, len(a:min) - 1) 453 let v = get(a:val, idx, 0) 454 if v < a:min[idx] | return 0 455 elseif v > a:min[idx] | return 1 456 endif 457 endfor 458 return 1 459 endfunction 460 461 function! s:git_version_requirement(...) 462 if !exists('s:git_version') 463 let s:git_version = map(split(split(s:system(['git', '--version']))[2], '\.'), 'str2nr(v:val)') 464 endif 465 return s:version_requirement(s:git_version, a:000) 466 endfunction 467 468 function! s:progress_opt(base) 469 return a:base && !s:is_win && 470 \ s:git_version_requirement(1, 7, 1) ? '--progress' : '' 471 endfunction 472 473 function! s:rtp(spec) 474 return s:path(a:spec.dir . get(a:spec, 'rtp', '')) 475 endfunction 476 477 if s:is_win 478 function! s:path(path) 479 return s:trim(substitute(a:path, '/', '\', 'g')) 480 endfunction 481 482 function! s:dirpath(path) 483 return s:path(a:path) . '\' 484 endfunction 485 486 function! s:is_local_plug(repo) 487 return a:repo =~? '^[a-z]:\|^[%~]' 488 endfunction 489 490 " Copied from fzf 491 function! s:wrap_cmds(cmds) 492 let cmds = [ 493 \ '@echo off', 494 \ 'setlocal enabledelayedexpansion'] 495 \ + (type(a:cmds) == type([]) ? a:cmds : [a:cmds]) 496 \ + ['endlocal'] 497 if has('iconv') 498 if !exists('s:codepage') 499 let s:codepage = libcallnr('kernel32.dll', 'GetACP', 0) 500 endif 501 return map(cmds, printf('iconv(v:val."\r", "%s", "cp%d")', &encoding, s:codepage)) 502 endif 503 return map(cmds, 'v:val."\r"') 504 endfunction 505 506 function! s:batchfile(cmd) 507 let batchfile = s:plug_tempname().'.bat' 508 call writefile(s:wrap_cmds(a:cmd), batchfile) 509 let cmd = plug#shellescape(batchfile, {'shell': &shell, 'script': 0}) 510 if s:is_powershell(&shell) 511 let cmd = '& ' . cmd 512 endif 513 return [batchfile, cmd] 514 endfunction 515 else 516 function! s:path(path) 517 return s:trim(a:path) 518 endfunction 519 520 function! s:dirpath(path) 521 return substitute(a:path, '[/\\]*$', '/', '') 522 endfunction 523 524 function! s:is_local_plug(repo) 525 return a:repo[0] =~ '[/$~]' 526 endfunction 527 endif 528 529 function! s:err(msg) 530 echohl ErrorMsg 531 echom '[vim-plug] '.a:msg 532 echohl None 533 endfunction 534 535 function! s:warn(cmd, msg) 536 echohl WarningMsg 537 execute a:cmd 'a:msg' 538 echohl None 539 endfunction 540 541 function! s:esc(path) 542 return escape(a:path, ' ') 543 endfunction 544 545 function! s:escrtp(path) 546 return escape(a:path, ' ,') 547 endfunction 548 549 function! s:remove_rtp() 550 for name in s:loaded_names() 551 let rtp = s:rtp(g:plugs[name]) 552 execute 'set rtp-='.s:escrtp(rtp) 553 let after = globpath(rtp, 'after') 554 if isdirectory(after) 555 execute 'set rtp-='.s:escrtp(after) 556 endif 557 endfor 558 endfunction 559 560 function! s:reorg_rtp() 561 if !empty(s:first_rtp) 562 execute 'set rtp-='.s:first_rtp 563 execute 'set rtp-='.s:last_rtp 564 endif 565 566 " &rtp is modified from outside 567 if exists('s:prtp') && s:prtp !=# &rtp 568 call s:remove_rtp() 569 unlet! s:middle 570 endif 571 572 let s:middle = get(s:, 'middle', &rtp) 573 let rtps = map(s:loaded_names(), 's:rtp(g:plugs[v:val])') 574 let afters = filter(map(copy(rtps), 'globpath(v:val, "after")'), '!empty(v:val)') 575 let rtp = join(map(rtps, 'escape(v:val, ",")'), ',') 576 \ . ','.s:middle.',' 577 \ . join(map(afters, 'escape(v:val, ",")'), ',') 578 let &rtp = substitute(substitute(rtp, ',,*', ',', 'g'), '^,\|,$', '', 'g') 579 let s:prtp = &rtp 580 581 if !empty(s:first_rtp) 582 execute 'set rtp^='.s:first_rtp 583 execute 'set rtp+='.s:last_rtp 584 endif 585 endfunction 586 587 function! s:doautocmd(...) 588 if exists('#'.join(a:000, '#')) 589 execute 'doautocmd' ((v:version > 703 || has('patch442')) ? '<nomodeline>' : '') join(a:000) 590 endif 591 endfunction 592 593 function! s:dobufread(names) 594 for name in a:names 595 let path = s:rtp(g:plugs[name]) 596 for dir in ['ftdetect', 'ftplugin', 'after/ftdetect', 'after/ftplugin'] 597 if len(finddir(dir, path)) 598 if exists('#BufRead') 599 doautocmd BufRead 600 endif 601 return 602 endif 603 endfor 604 endfor 605 endfunction 606 607 function! plug#load(...) 608 if a:0 == 0 609 return s:err('Argument missing: plugin name(s) required') 610 endif 611 if !exists('g:plugs') 612 return s:err('plug#begin was not called') 613 endif 614 let names = a:0 == 1 && type(a:1) == s:TYPE.list ? a:1 : a:000 615 let unknowns = filter(copy(names), '!has_key(g:plugs, v:val)') 616 if !empty(unknowns) 617 let s = len(unknowns) > 1 ? 's' : '' 618 return s:err(printf('Unknown plugin%s: %s', s, join(unknowns, ', '))) 619 end 620 let unloaded = filter(copy(names), '!get(s:loaded, v:val, 0)') 621 if !empty(unloaded) 622 for name in unloaded 623 call s:lod([name], ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin']) 624 endfor 625 call s:dobufread(unloaded) 626 return 1 627 end 628 return 0 629 endfunction 630 631 function! s:remove_triggers(name) 632 if !has_key(s:triggers, a:name) 633 return 634 endif 635 for cmd in s:triggers[a:name].cmd 636 execute 'silent! delc' cmd 637 endfor 638 for map in s:triggers[a:name].map 639 execute 'silent! unmap' map 640 execute 'silent! iunmap' map 641 endfor 642 call remove(s:triggers, a:name) 643 endfunction 644 645 function! s:lod(names, types, ...) 646 for name in a:names 647 call s:remove_triggers(name) 648 let s:loaded[name] = 1 649 endfor 650 call s:reorg_rtp() 651 652 for name in a:names 653 let rtp = s:rtp(g:plugs[name]) 654 for dir in a:types 655 call s:source(rtp, dir.'/**/*.vim') 656 endfor 657 if a:0 658 if !s:source(rtp, a:1) && !empty(s:glob(rtp, a:2)) 659 execute 'runtime' a:1 660 endif 661 call s:source(rtp, a:2) 662 endif 663 call s:doautocmd('User', name) 664 endfor 665 endfunction 666 667 function! s:lod_ft(pat, names) 668 let syn = 'syntax/'.a:pat.'.vim' 669 call s:lod(a:names, ['plugin', 'after/plugin'], syn, 'after/'.syn) 670 execute 'autocmd! PlugLOD FileType' a:pat 671 call s:doautocmd('filetypeplugin', 'FileType') 672 call s:doautocmd('filetypeindent', 'FileType') 673 endfunction 674 675 function! s:lod_cmd(cmd, bang, l1, l2, args, names) 676 call s:lod(a:names, ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin']) 677 call s:dobufread(a:names) 678 execute printf('%s%s%s %s', (a:l1 == a:l2 ? '' : (a:l1.','.a:l2)), a:cmd, a:bang, a:args) 679 endfunction 680 681 function! s:lod_map(map, names, with_prefix, prefix) 682 call s:lod(a:names, ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin']) 683 call s:dobufread(a:names) 684 let extra = '' 685 while 1 686 let c = getchar(0) 687 if c == 0 688 break 689 endif 690 let extra .= nr2char(c) 691 endwhile 692 693 if a:with_prefix 694 let prefix = v:count ? v:count : '' 695 let prefix .= '"'.v:register.a:prefix 696 if mode(1) == 'no' 697 if v:operator == 'c' 698 let prefix = "\<esc>" . prefix 699 endif 700 let prefix .= v:operator 701 endif 702 call feedkeys(prefix, 'n') 703 endif 704 call feedkeys(substitute(a:map, '^<Plug>', "\<Plug>", '') . extra) 705 endfunction 706 707 function! plug#(repo, ...) 708 if a:0 > 1 709 return s:err('Invalid number of arguments (1..2)') 710 endif 711 712 try 713 let repo = s:trim(a:repo) 714 let opts = a:0 == 1 ? s:parse_options(a:1) : s:base_spec 715 let name = get(opts, 'as', s:plug_fnamemodify(repo, ':t:s?\.git$??')) 716 let spec = extend(s:infer_properties(name, repo), opts) 717 if !has_key(g:plugs, name) 718 call add(g:plugs_order, name) 719 endif 720 let g:plugs[name] = spec 721 let s:loaded[name] = get(s:loaded, name, 0) 722 catch 723 return s:err(repo . ' ' . v:exception) 724 endtry 725 endfunction 726 727 function! s:parse_options(arg) 728 let opts = copy(s:base_spec) 729 let type = type(a:arg) 730 let opt_errfmt = 'Invalid argument for "%s" option of :Plug (expected: %s)' 731 if type == s:TYPE.string 732 if empty(a:arg) 733 throw printf(opt_errfmt, 'tag', 'string') 734 endif 735 let opts.tag = a:arg 736 elseif type == s:TYPE.dict 737 for opt in ['branch', 'tag', 'commit', 'rtp', 'dir', 'as'] 738 if has_key(a:arg, opt) 739 \ && (type(a:arg[opt]) != s:TYPE.string || empty(a:arg[opt])) 740 throw printf(opt_errfmt, opt, 'string') 741 endif 742 endfor 743 for opt in ['on', 'for'] 744 if has_key(a:arg, opt) 745 \ && type(a:arg[opt]) != s:TYPE.list 746 \ && (type(a:arg[opt]) != s:TYPE.string || empty(a:arg[opt])) 747 throw printf(opt_errfmt, opt, 'string or list') 748 endif 749 endfor 750 if has_key(a:arg, 'do') 751 \ && type(a:arg.do) != s:TYPE.funcref 752 \ && (type(a:arg.do) != s:TYPE.string || empty(a:arg.do)) 753 throw printf(opt_errfmt, 'do', 'string or funcref') 754 endif 755 call extend(opts, a:arg) 756 if has_key(opts, 'dir') 757 let opts.dir = s:dirpath(s:plug_expand(opts.dir)) 758 endif 759 else 760 throw 'Invalid argument type (expected: string or dictionary)' 761 endif 762 return opts 763 endfunction 764 765 function! s:infer_properties(name, repo) 766 let repo = a:repo 767 if s:is_local_plug(repo) 768 return { 'dir': s:dirpath(s:plug_expand(repo)) } 769 else 770 if repo =~ ':' 771 let uri = repo 772 else 773 if repo !~ '/' 774 throw printf('Invalid argument: %s (implicit `vim-scripts'' expansion is deprecated)', repo) 775 endif 776 let fmt = get(g:, 'plug_url_format', 'https://git::@github.com/%s.git') 777 let uri = printf(fmt, repo) 778 endif 779 return { 'dir': s:dirpath(g:plug_home.'/'.a:name), 'uri': uri } 780 endif 781 endfunction 782 783 function! s:install(force, names) 784 call s:update_impl(0, a:force, a:names) 785 endfunction 786 787 function! s:update(force, names) 788 call s:update_impl(1, a:force, a:names) 789 endfunction 790 791 function! plug#helptags() 792 if !exists('g:plugs') 793 return s:err('plug#begin was not called') 794 endif 795 for spec in values(g:plugs) 796 let docd = join([s:rtp(spec), 'doc'], '/') 797 if isdirectory(docd) 798 silent! execute 'helptags' s:esc(docd) 799 endif 800 endfor 801 return 1 802 endfunction 803 804 function! s:syntax() 805 syntax clear 806 syntax region plug1 start=/\%1l/ end=/\%2l/ contains=plugNumber 807 syntax region plug2 start=/\%2l/ end=/\%3l/ contains=plugBracket,plugX 808 syn match plugNumber /[0-9]\+[0-9.]*/ contained 809 syn match plugBracket /[[\]]/ contained 810 syn match plugX /x/ contained 811 syn match plugDash /^-\{1}\ / 812 syn match plugPlus /^+/ 813 syn match plugStar /^*/ 814 syn match plugMessage /\(^- \)\@<=.*/ 815 syn match plugName /\(^- \)\@<=[^ ]*:/ 816 syn match plugSha /\%(: \)\@<=[0-9a-f]\{4,}$/ 817 syn match plugTag /(tag: [^)]\+)/ 818 syn match plugInstall /\(^+ \)\@<=[^:]*/ 819 syn match plugUpdate /\(^* \)\@<=[^:]*/ 820 syn match plugCommit /^ \X*[0-9a-f]\{7,9} .*/ contains=plugRelDate,plugEdge,plugTag 821 syn match plugEdge /^ \X\+$/ 822 syn match plugEdge /^ \X*/ contained nextgroup=plugSha 823 syn match plugSha /[0-9a-f]\{7,9}/ contained 824 syn match plugRelDate /([^)]*)$/ contained 825 syn match plugNotLoaded /(not loaded)$/ 826 syn match plugError /^x.*/ 827 syn region plugDeleted start=/^\~ .*/ end=/^\ze\S/ 828 syn match plugH2 /^.*:\n-\+$/ 829 syn match plugH2 /^-\{2,}/ 830 syn keyword Function PlugInstall PlugStatus PlugUpdate PlugClean 831 hi def link plug1 Title 832 hi def link plug2 Repeat 833 hi def link plugH2 Type 834 hi def link plugX Exception 835 hi def link plugBracket Structure 836 hi def link plugNumber Number 837 838 hi def link plugDash Special 839 hi def link plugPlus Constant 840 hi def link plugStar Boolean 841 842 hi def link plugMessage Function 843 hi def link plugName Label 844 hi def link plugInstall Function 845 hi def link plugUpdate Type 846 847 hi def link plugError Error 848 hi def link plugDeleted Ignore 849 hi def link plugRelDate Comment 850 hi def link plugEdge PreProc 851 hi def link plugSha Identifier 852 hi def link plugTag Constant 853 854 hi def link plugNotLoaded Comment 855 endfunction 856 857 function! s:lpad(str, len) 858 return a:str . repeat(' ', a:len - len(a:str)) 859 endfunction 860 861 function! s:lines(msg) 862 return split(a:msg, "[\r\n]") 863 endfunction 864 865 function! s:lastline(msg) 866 return get(s:lines(a:msg), -1, '') 867 endfunction 868 869 function! s:new_window() 870 execute get(g:, 'plug_window', 'vertical topleft new') 871 endfunction 872 873 function! s:plug_window_exists() 874 let buflist = tabpagebuflist(s:plug_tab) 875 return !empty(buflist) && index(buflist, s:plug_buf) >= 0 876 endfunction 877 878 function! s:switch_in() 879 if !s:plug_window_exists() 880 return 0 881 endif 882 883 if winbufnr(0) != s:plug_buf 884 let s:pos = [tabpagenr(), winnr(), winsaveview()] 885 execute 'normal!' s:plug_tab.'gt' 886 let winnr = bufwinnr(s:plug_buf) 887 execute winnr.'wincmd w' 888 call add(s:pos, winsaveview()) 889 else 890 let s:pos = [winsaveview()] 891 endif 892 893 setlocal modifiable 894 return 1 895 endfunction 896 897 function! s:switch_out(...) 898 call winrestview(s:pos[-1]) 899 setlocal nomodifiable 900 if a:0 > 0 901 execute a:1 902 endif 903 904 if len(s:pos) > 1 905 execute 'normal!' s:pos[0].'gt' 906 execute s:pos[1] 'wincmd w' 907 call winrestview(s:pos[2]) 908 endif 909 endfunction 910 911 function! s:finish_bindings() 912 nnoremap <silent> <buffer> R :call <SID>retry()<cr> 913 nnoremap <silent> <buffer> D :PlugDiff<cr> 914 nnoremap <silent> <buffer> S :PlugStatus<cr> 915 nnoremap <silent> <buffer> U :call <SID>status_update()<cr> 916 xnoremap <silent> <buffer> U :call <SID>status_update()<cr> 917 nnoremap <silent> <buffer> ]] :silent! call <SID>section('')<cr> 918 nnoremap <silent> <buffer> [[ :silent! call <SID>section('b')<cr> 919 endfunction 920 921 function! s:prepare(...) 922 if empty(s:plug_getcwd()) 923 throw 'Invalid current working directory. Cannot proceed.' 924 endif 925 926 for evar in ['$GIT_DIR', '$GIT_WORK_TREE'] 927 if exists(evar) 928 throw evar.' detected. Cannot proceed.' 929 endif 930 endfor 931 932 call s:job_abort() 933 if s:switch_in() 934 if b:plug_preview == 1 935 pc 936 endif 937 enew 938 else 939 call s:new_window() 940 endif 941 942 nnoremap <silent> <buffer> q :call <SID>close_pane()<cr> 943 if a:0 == 0 944 call s:finish_bindings() 945 endif 946 let b:plug_preview = -1 947 let s:plug_tab = tabpagenr() 948 let s:plug_buf = winbufnr(0) 949 call s:assign_name() 950 951 for k in ['<cr>', 'L', 'o', 'X', 'd', 'dd'] 952 execute 'silent! unmap <buffer>' k 953 endfor 954 setlocal buftype=nofile bufhidden=wipe nobuflisted nolist noswapfile nowrap cursorline modifiable nospell 955 if exists('+colorcolumn') 956 setlocal colorcolumn= 957 endif 958 setf vim-plug 959 if exists('g:syntax_on') 960 call s:syntax() 961 endif 962 endfunction 963 964 function! s:close_pane() 965 if b:plug_preview == 1 966 pc 967 let b:plug_preview = -1 968 else 969 bd 970 endif 971 endfunction 972 973 function! s:assign_name() 974 " Assign buffer name 975 let prefix = '[Plugins]' 976 let name = prefix 977 let idx = 2 978 while bufexists(name) 979 let name = printf('%s (%s)', prefix, idx) 980 let idx = idx + 1 981 endwhile 982 silent! execute 'f' fnameescape(name) 983 endfunction 984 985 function! s:chsh(swap) 986 let prev = [&shell, &shellcmdflag, &shellredir] 987 if !s:is_win 988 set shell=sh 989 endif 990 if a:swap 991 if s:is_powershell(&shell) 992 let &shellredir = '2>&1 | Out-File -Encoding UTF8 %s' 993 elseif &shell =~# 'sh' || &shell =~# 'cmd\(\.exe\)\?$' 994 set shellredir=>%s\ 2>&1 995 endif 996 endif 997 return prev 998 endfunction 999 1000 function! s:bang(cmd, ...) 1001 let batchfile = '' 1002 try 1003 let [sh, shellcmdflag, shrd] = s:chsh(a:0) 1004 " FIXME: Escaping is incomplete. We could use shellescape with eval, 1005 " but it won't work on Windows. 1006 let cmd = a:0 ? s:with_cd(a:cmd, a:1) : a:cmd 1007 if s:is_win 1008 let [batchfile, cmd] = s:batchfile(cmd) 1009 endif 1010 let g:_plug_bang = (s:is_win && has('gui_running') ? 'silent ' : '').'!'.escape(cmd, '#!%') 1011 execute "normal! :execute g:_plug_bang\<cr>\<cr>" 1012 finally 1013 unlet g:_plug_bang 1014 let [&shell, &shellcmdflag, &shellredir] = [sh, shellcmdflag, shrd] 1015 if s:is_win && filereadable(batchfile) 1016 call delete(batchfile) 1017 endif 1018 endtry 1019 return v:shell_error ? 'Exit status: ' . v:shell_error : '' 1020 endfunction 1021 1022 function! s:regress_bar() 1023 let bar = substitute(getline(2)[1:-2], '.*\zs=', 'x', '') 1024 call s:progress_bar(2, bar, len(bar)) 1025 endfunction 1026 1027 function! s:is_updated(dir) 1028 return !empty(s:system_chomp(['git', 'log', '--pretty=format:%h', 'HEAD...HEAD@{1}'], a:dir)) 1029 endfunction 1030 1031 function! s:do(pull, force, todo) 1032 for [name, spec] in items(a:todo) 1033 if !isdirectory(spec.dir) 1034 continue 1035 endif 1036 let installed = has_key(s:update.new, name) 1037 let updated = installed ? 0 : 1038 \ (a:pull && index(s:update.errors, name) < 0 && s:is_updated(spec.dir)) 1039 if a:force || installed || updated 1040 execute 'cd' s:esc(spec.dir) 1041 call append(3, '- Post-update hook for '. name .' ... ') 1042 let error = '' 1043 let type = type(spec.do) 1044 if type == s:TYPE.string 1045 if spec.do[0] == ':' 1046 if !get(s:loaded, name, 0) 1047 let s:loaded[name] = 1 1048 call s:reorg_rtp() 1049 endif 1050 call s:load_plugin(spec) 1051 try 1052 execute spec.do[1:] 1053 catch 1054 let error = v:exception 1055 endtry 1056 if !s:plug_window_exists() 1057 cd - 1058 throw 'Warning: vim-plug was terminated by the post-update hook of '.name 1059 endif 1060 else 1061 let error = s:bang(spec.do) 1062 endif 1063 elseif type == s:TYPE.funcref 1064 try 1065 call s:load_plugin(spec) 1066 let status = installed ? 'installed' : (updated ? 'updated' : 'unchanged') 1067 call spec.do({ 'name': name, 'status': status, 'force': a:force }) 1068 catch 1069 let error = v:exception 1070 endtry 1071 else 1072 let error = 'Invalid hook type' 1073 endif 1074 call s:switch_in() 1075 call setline(4, empty(error) ? (getline(4) . 'OK') 1076 \ : ('x' . getline(4)[1:] . error)) 1077 if !empty(error) 1078 call add(s:update.errors, name) 1079 call s:regress_bar() 1080 endif 1081 cd - 1082 endif 1083 endfor 1084 endfunction 1085 1086 function! s:hash_match(a, b) 1087 return stridx(a:a, a:b) == 0 || stridx(a:b, a:a) == 0 1088 endfunction 1089 1090 function! s:checkout(spec) 1091 let sha = a:spec.commit 1092 let output = s:git_revision(a:spec.dir) 1093 if !empty(output) && !s:hash_match(sha, s:lines(output)[0]) 1094 let credential_helper = s:git_version_requirement(2) ? '-c credential.helper= ' : '' 1095 let output = s:system( 1096 \ 'git '.credential_helper.'fetch --depth 999999 && git checkout '.plug#shellescape(sha).' --', a:spec.dir) 1097 endif 1098 return output 1099 endfunction 1100 1101 function! s:finish(pull) 1102 let new_frozen = len(filter(keys(s:update.new), 'g:plugs[v:val].frozen')) 1103 if new_frozen 1104 let s = new_frozen > 1 ? 's' : '' 1105 call append(3, printf('- Installed %d frozen plugin%s', new_frozen, s)) 1106 endif 1107 call append(3, '- Finishing ... ') | 4 1108 redraw 1109 call plug#helptags() 1110 call plug#end() 1111 call setline(4, getline(4) . 'Done!') 1112 redraw 1113 let msgs = [] 1114 if !empty(s:update.errors) 1115 call add(msgs, "Press 'R' to retry.") 1116 endif 1117 if a:pull && len(s:update.new) < len(filter(getline(5, '$'), 1118 \ "v:val =~ '^- ' && v:val !~# 'Already up.to.date'")) 1119 call add(msgs, "Press 'D' to see the updated changes.") 1120 endif 1121 echo join(msgs, ' ') 1122 call s:finish_bindings() 1123 endfunction 1124 1125 function! s:retry() 1126 if empty(s:update.errors) 1127 return 1128 endif 1129 echo 1130 call s:update_impl(s:update.pull, s:update.force, 1131 \ extend(copy(s:update.errors), [s:update.threads])) 1132 endfunction 1133 1134 function! s:is_managed(name) 1135 return has_key(g:plugs[a:name], 'uri') 1136 endfunction 1137 1138 function! s:names(...) 1139 return sort(filter(keys(g:plugs), 'stridx(v:val, a:1) == 0 && s:is_managed(v:val)')) 1140 endfunction 1141 1142 function! s:check_ruby() 1143 silent! ruby require 'thread'; VIM::command("let g:plug_ruby = '#{RUBY_VERSION}'") 1144 if !exists('g:plug_ruby') 1145 redraw! 1146 return s:warn('echom', 'Warning: Ruby interface is broken') 1147 endif 1148 let ruby_version = split(g:plug_ruby, '\.') 1149 unlet g:plug_ruby 1150 return s:version_requirement(ruby_version, [1, 8, 7]) 1151 endfunction 1152 1153 function! s:update_impl(pull, force, args) abort 1154 let sync = index(a:args, '--sync') >= 0 || has('vim_starting') 1155 let args = filter(copy(a:args), 'v:val != "--sync"') 1156 let threads = (len(args) > 0 && args[-1] =~ '^[1-9][0-9]*$') ? 1157 \ remove(args, -1) : get(g:, 'plug_threads', 16) 1158 1159 let managed = filter(copy(g:plugs), 's:is_managed(v:key)') 1160 let todo = empty(args) ? filter(managed, '!v:val.frozen || !isdirectory(v:val.dir)') : 1161 \ filter(managed, 'index(args, v:key) >= 0') 1162 1163 if empty(todo) 1164 return s:warn('echo', 'No plugin to '. (a:pull ? 'update' : 'install')) 1165 endif 1166 1167 if !s:is_win && s:git_version_requirement(2, 3) 1168 let s:git_terminal_prompt = exists('$GIT_TERMINAL_PROMPT') ? $GIT_TERMINAL_PROMPT : '' 1169 let $GIT_TERMINAL_PROMPT = 0 1170 for plug in values(todo) 1171 let plug.uri = substitute(plug.uri, 1172 \ '^https://git::@github\.com', 'https://github.com', '') 1173 endfor 1174 endif 1175 1176 if !isdirectory(g:plug_home) 1177 try 1178 call mkdir(g:plug_home, 'p') 1179 catch 1180 return s:err(printf('Invalid plug directory: %s. '. 1181 \ 'Try to call plug#begin with a valid directory', g:plug_home)) 1182 endtry 1183 endif 1184 1185 if has('nvim') && !exists('*jobwait') && threads > 1 1186 call s:warn('echom', '[vim-plug] Update Neovim for parallel installer') 1187 endif 1188 1189 let use_job = s:nvim || s:vim8 1190 let python = (has('python') || has('python3')) && !use_job 1191 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() 1192 1193 let s:update = { 1194 \ 'start': reltime(), 1195 \ 'all': todo, 1196 \ 'todo': copy(todo), 1197 \ 'errors': [], 1198 \ 'pull': a:pull, 1199 \ 'force': a:force, 1200 \ 'new': {}, 1201 \ 'threads': (python || ruby || use_job) ? min([len(todo), threads]) : 1, 1202 \ 'bar': '', 1203 \ 'fin': 0 1204 \ } 1205 1206 call s:prepare(1) 1207 call append(0, ['', '']) 1208 normal! 2G 1209 silent! redraw 1210 1211 let s:clone_opt = [] 1212 if get(g:, 'plug_shallow', 1) 1213 call extend(s:clone_opt, ['--depth', '1']) 1214 if s:git_version_requirement(1, 7, 10) 1215 call add(s:clone_opt, '--no-single-branch') 1216 endif 1217 endif 1218 1219 if has('win32unix') || has('wsl') 1220 call extend(s:clone_opt, ['-c', 'core.eol=lf', '-c', 'core.autocrlf=input']) 1221 endif 1222 1223 let s:submodule_opt = s:git_version_requirement(2, 8) ? ' --jobs='.threads : '' 1224 1225 " Python version requirement (>= 2.7) 1226 if python && !has('python3') && !ruby && !use_job && s:update.threads > 1 1227 redir => pyv 1228 silent python import platform; print platform.python_version() 1229 redir END 1230 let python = s:version_requirement( 1231 \ map(split(split(pyv)[0], '\.'), 'str2nr(v:val)'), [2, 6]) 1232 endif 1233 1234 if (python || ruby) && s:update.threads > 1 1235 try 1236 let imd = &imd 1237 if s:mac_gui 1238 set noimd 1239 endif 1240 if ruby 1241 call s:update_ruby() 1242 else 1243 call s:update_python() 1244 endif 1245 catch 1246 let lines = getline(4, '$') 1247 let printed = {} 1248 silent! 4,$d _ 1249 for line in lines 1250 let name = s:extract_name(line, '.', '') 1251 if empty(name) || !has_key(printed, name) 1252 call append('$', line) 1253 if !empty(name) 1254 let printed[name] = 1 1255 if line[0] == 'x' && index(s:update.errors, name) < 0 1256 call add(s:update.errors, name) 1257 end 1258 endif 1259 endif 1260 endfor 1261 finally 1262 let &imd = imd 1263 call s:update_finish() 1264 endtry 1265 else 1266 call s:update_vim() 1267 while use_job && sync 1268 sleep 100m 1269 if s:update.fin 1270 break 1271 endif 1272 endwhile 1273 endif 1274 endfunction 1275 1276 function! s:log4(name, msg) 1277 call setline(4, printf('- %s (%s)', a:msg, a:name)) 1278 redraw 1279 endfunction 1280 1281 function! s:update_finish() 1282 if exists('s:git_terminal_prompt') 1283 let $GIT_TERMINAL_PROMPT = s:git_terminal_prompt 1284 endif 1285 if s:switch_in() 1286 call append(3, '- Updating ...') | 4 1287 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))')) 1288 let [pos, _] = s:logpos(name) 1289 if !pos 1290 continue 1291 endif 1292 if has_key(spec, 'commit') 1293 call s:log4(name, 'Checking out '.spec.commit) 1294 let out = s:checkout(spec) 1295 elseif has_key(spec, 'tag') 1296 let tag = spec.tag 1297 if tag =~ '\*' 1298 let tags = s:lines(s:system('git tag --list '.plug#shellescape(tag).' --sort -version:refname 2>&1', spec.dir)) 1299 if !v:shell_error && !empty(tags) 1300 let tag = tags[0] 1301 call s:log4(name, printf('Latest tag for %s -> %s', spec.tag, tag)) 1302 call append(3, '') 1303 endif 1304 endif 1305 call s:log4(name, 'Checking out '.tag) 1306 let out = s:system('git checkout -q '.plug#shellescape(tag).' -- 2>&1', spec.dir) 1307 else 1308 let branch = s:git_origin_branch(spec) 1309 call s:log4(name, 'Merging origin/'.s:esc(branch)) 1310 let out = s:system('git checkout -q '.plug#shellescape(branch).' -- 2>&1' 1311 \. (has_key(s:update.new, name) ? '' : ('&& git merge --ff-only '.plug#shellescape('origin/'.branch).' 2>&1')), spec.dir) 1312 endif 1313 if !v:shell_error && filereadable(spec.dir.'/.gitmodules') && 1314 \ (s:update.force || has_key(s:update.new, name) || s:is_updated(spec.dir)) 1315 call s:log4(name, 'Updating submodules. This may take a while.') 1316 let out .= s:bang('git submodule update --init --recursive'.s:submodule_opt.' 2>&1', spec.dir) 1317 endif 1318 let msg = s:format_message(v:shell_error ? 'x': '-', name, out) 1319 if v:shell_error 1320 call add(s:update.errors, name) 1321 call s:regress_bar() 1322 silent execute pos 'd _' 1323 call append(4, msg) | 4 1324 elseif !empty(out) 1325 call setline(pos, msg[0]) 1326 endif 1327 redraw 1328 endfor 1329 silent 4 d _ 1330 try 1331 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")')) 1332 catch 1333 call s:warn('echom', v:exception) 1334 call s:warn('echo', '') 1335 return 1336 endtry 1337 call s:finish(s:update.pull) 1338 call setline(1, 'Updated. Elapsed time: ' . split(reltimestr(reltime(s:update.start)))[0] . ' sec.') 1339 call s:switch_out('normal! gg') 1340 endif 1341 endfunction 1342 1343 function! s:job_abort() 1344 if (!s:nvim && !s:vim8) || !exists('s:jobs') 1345 return 1346 endif 1347 1348 for [name, j] in items(s:jobs) 1349 if s:nvim 1350 silent! call jobstop(j.jobid) 1351 elseif s:vim8 1352 silent! call job_stop(j.jobid) 1353 endif 1354 if j.new 1355 call s:rm_rf(g:plugs[name].dir) 1356 endif 1357 endfor 1358 let s:jobs = {} 1359 endfunction 1360 1361 function! s:last_non_empty_line(lines) 1362 let len = len(a:lines) 1363 for idx in range(len) 1364 let line = a:lines[len-idx-1] 1365 if !empty(line) 1366 return line 1367 endif 1368 endfor 1369 return '' 1370 endfunction 1371 1372 function! s:job_out_cb(self, data) abort 1373 let self = a:self 1374 let data = remove(self.lines, -1) . a:data 1375 let lines = map(split(data, "\n", 1), 'split(v:val, "\r", 1)[-1]') 1376 call extend(self.lines, lines) 1377 " To reduce the number of buffer updates 1378 let self.tick = get(self, 'tick', -1) + 1 1379 if !self.running || self.tick % len(s:jobs) == 0 1380 let bullet = self.running ? (self.new ? '+' : '*') : (self.error ? 'x' : '-') 1381 let result = self.error ? join(self.lines, "\n") : s:last_non_empty_line(self.lines) 1382 call s:log(bullet, self.name, result) 1383 endif 1384 endfunction 1385 1386 function! s:job_exit_cb(self, data) abort 1387 let a:self.running = 0 1388 let a:self.error = a:data != 0 1389 call s:reap(a:self.name) 1390 call s:tick() 1391 endfunction 1392 1393 function! s:job_cb(fn, job, ch, data) 1394 if !s:plug_window_exists() " plug window closed 1395 return s:job_abort() 1396 endif 1397 call call(a:fn, [a:job, a:data]) 1398 endfunction 1399 1400 function! s:nvim_cb(job_id, data, event) dict abort 1401 return (a:event == 'stdout' || a:event == 'stderr') ? 1402 \ s:job_cb('s:job_out_cb', self, 0, join(a:data, "\n")) : 1403 \ s:job_cb('s:job_exit_cb', self, 0, a:data) 1404 endfunction 1405 1406 function! s:spawn(name, cmd, opts) 1407 let job = { 'name': a:name, 'running': 1, 'error': 0, 'lines': [''], 1408 \ 'new': get(a:opts, 'new', 0) } 1409 let s:jobs[a:name] = job 1410 1411 if s:nvim 1412 if has_key(a:opts, 'dir') 1413 let job.cwd = a:opts.dir 1414 endif 1415 let argv = a:cmd 1416 call extend(job, { 1417 \ 'on_stdout': function('s:nvim_cb'), 1418 \ 'on_stderr': function('s:nvim_cb'), 1419 \ 'on_exit': function('s:nvim_cb'), 1420 \ }) 1421 let jid = s:plug_call('jobstart', argv, job) 1422 if jid > 0 1423 let job.jobid = jid 1424 else 1425 let job.running = 0 1426 let job.error = 1 1427 let job.lines = [jid < 0 ? argv[0].' is not executable' : 1428 \ 'Invalid arguments (or job table is full)'] 1429 endif 1430 elseif s:vim8 1431 let cmd = join(map(copy(a:cmd), 'plug#shellescape(v:val, {"script": 0})')) 1432 if has_key(a:opts, 'dir') 1433 let cmd = s:with_cd(cmd, a:opts.dir, 0) 1434 endif 1435 let argv = s:is_win ? ['cmd', '/s', '/c', '"'.cmd.'"'] : ['sh', '-c', cmd] 1436 let jid = job_start(s:is_win ? join(argv, ' ') : argv, { 1437 \ 'out_cb': function('s:job_cb', ['s:job_out_cb', job]), 1438 \ 'err_cb': function('s:job_cb', ['s:job_out_cb', job]), 1439 \ 'exit_cb': function('s:job_cb', ['s:job_exit_cb', job]), 1440 \ 'err_mode': 'raw', 1441 \ 'out_mode': 'raw' 1442 \}) 1443 if job_status(jid) == 'run' 1444 let job.jobid = jid 1445 else 1446 let job.running = 0 1447 let job.error = 1 1448 let job.lines = ['Failed to start job'] 1449 endif 1450 else 1451 let job.lines = s:lines(call('s:system', has_key(a:opts, 'dir') ? [a:cmd, a:opts.dir] : [a:cmd])) 1452 let job.error = v:shell_error != 0 1453 let job.running = 0 1454 endif 1455 endfunction 1456 1457 function! s:reap(name) 1458 let job = s:jobs[a:name] 1459 if job.error 1460 call add(s:update.errors, a:name) 1461 elseif get(job, 'new', 0) 1462 let s:update.new[a:name] = 1 1463 endif 1464 let s:update.bar .= job.error ? 'x' : '=' 1465 1466 let bullet = job.error ? 'x' : '-' 1467 let result = job.error ? join(job.lines, "\n") : s:last_non_empty_line(job.lines) 1468 call s:log(bullet, a:name, empty(result) ? 'OK' : result) 1469 call s:bar() 1470 1471 call remove(s:jobs, a:name) 1472 endfunction 1473 1474 function! s:bar() 1475 if s:switch_in() 1476 let total = len(s:update.all) 1477 call setline(1, (s:update.pull ? 'Updating' : 'Installing'). 1478 \ ' plugins ('.len(s:update.bar).'/'.total.')') 1479 call s:progress_bar(2, s:update.bar, total) 1480 call s:switch_out() 1481 endif 1482 endfunction 1483 1484 function! s:logpos(name) 1485 let max = line('$') 1486 for i in range(4, max > 4 ? max : 4) 1487 if getline(i) =~# '^[-+x*] '.a:name.':' 1488 for j in range(i + 1, max > 5 ? max : 5) 1489 if getline(j) !~ '^ ' 1490 return [i, j - 1] 1491 endif 1492 endfor 1493 return [i, i] 1494 endif 1495 endfor 1496 return [0, 0] 1497 endfunction 1498 1499 function! s:log(bullet, name, lines) 1500 if s:switch_in() 1501 let [b, e] = s:logpos(a:name) 1502 if b > 0 1503 silent execute printf('%d,%d d _', b, e) 1504 if b > winheight('.') 1505 let b = 4 1506 endif 1507 else 1508 let b = 4 1509 endif 1510 " FIXME For some reason, nomodifiable is set after :d in vim8 1511 setlocal modifiable 1512 call append(b - 1, s:format_message(a:bullet, a:name, a:lines)) 1513 call s:switch_out() 1514 endif 1515 endfunction 1516 1517 function! s:update_vim() 1518 let s:jobs = {} 1519 1520 call s:bar() 1521 call s:tick() 1522 endfunction 1523 1524 function! s:tick() 1525 let pull = s:update.pull 1526 let prog = s:progress_opt(s:nvim || s:vim8) 1527 while 1 " Without TCO, Vim stack is bound to explode 1528 if empty(s:update.todo) 1529 if empty(s:jobs) && !s:update.fin 1530 call s:update_finish() 1531 let s:update.fin = 1 1532 endif 1533 return 1534 endif 1535 1536 let name = keys(s:update.todo)[0] 1537 let spec = remove(s:update.todo, name) 1538 let new = empty(globpath(spec.dir, '.git', 1)) 1539 1540 call s:log(new ? '+' : '*', name, pull ? 'Updating ...' : 'Installing ...') 1541 redraw 1542 1543 let has_tag = has_key(spec, 'tag') 1544 if !new 1545 let [error, _] = s:git_validate(spec, 0) 1546 if empty(error) 1547 if pull 1548 let cmd = s:git_version_requirement(2) ? ['git', '-c', 'credential.helper=', 'fetch'] : ['git', 'fetch'] 1549 if has_tag && !empty(globpath(spec.dir, '.git/shallow')) 1550 call extend(cmd, ['--depth', '99999999']) 1551 endif 1552 if !empty(prog) 1553 call add(cmd, prog) 1554 endif 1555 call s:spawn(name, cmd, { 'dir': spec.dir }) 1556 else 1557 let s:jobs[name] = { 'running': 0, 'lines': ['Already installed'], 'error': 0 } 1558 endif 1559 else 1560 let s:jobs[name] = { 'running': 0, 'lines': s:lines(error), 'error': 1 } 1561 endif 1562 else 1563 let cmd = ['git', 'clone'] 1564 if !has_tag 1565 call extend(cmd, s:clone_opt) 1566 endif 1567 if !empty(prog) 1568 call add(cmd, prog) 1569 endif 1570 call s:spawn(name, extend(cmd, [spec.uri, s:trim(spec.dir)]), { 'new': 1 }) 1571 endif 1572 1573 if !s:jobs[name].running 1574 call s:reap(name) 1575 endif 1576 if len(s:jobs) >= s:update.threads 1577 break 1578 endif 1579 endwhile 1580 endfunction 1581 1582 function! s:update_python() 1583 let py_exe = has('python') ? 'python' : 'python3' 1584 execute py_exe "<< EOF" 1585 import datetime 1586 import functools 1587 import os 1588 try: 1589 import queue 1590 except ImportError: 1591 import Queue as queue 1592 import random 1593 import re 1594 import shutil 1595 import signal 1596 import subprocess 1597 import tempfile 1598 import threading as thr 1599 import time 1600 import traceback 1601 import vim 1602 1603 G_NVIM = vim.eval("has('nvim')") == '1' 1604 G_PULL = vim.eval('s:update.pull') == '1' 1605 G_RETRIES = int(vim.eval('get(g:, "plug_retries", 2)')) + 1 1606 G_TIMEOUT = int(vim.eval('get(g:, "plug_timeout", 60)')) 1607 G_CLONE_OPT = ' '.join(vim.eval('s:clone_opt')) 1608 G_PROGRESS = vim.eval('s:progress_opt(1)') 1609 G_LOG_PROB = 1.0 / int(vim.eval('s:update.threads')) 1610 G_STOP = thr.Event() 1611 G_IS_WIN = vim.eval('s:is_win') == '1' 1612 1613 class PlugError(Exception): 1614 def __init__(self, msg): 1615 self.msg = msg 1616 class CmdTimedOut(PlugError): 1617 pass 1618 class CmdFailed(PlugError): 1619 pass 1620 class InvalidURI(PlugError): 1621 pass 1622 class Action(object): 1623 INSTALL, UPDATE, ERROR, DONE = ['+', '*', 'x', '-'] 1624 1625 class Buffer(object): 1626 def __init__(self, lock, num_plugs, is_pull): 1627 self.bar = '' 1628 self.event = 'Updating' if is_pull else 'Installing' 1629 self.lock = lock 1630 self.maxy = int(vim.eval('winheight(".")')) 1631 self.num_plugs = num_plugs 1632 1633 def __where(self, name): 1634 """ Find first line with name in current buffer. Return line num. """ 1635 found, lnum = False, 0 1636 matcher = re.compile('^[-+x*] {0}:'.format(name)) 1637 for line in vim.current.buffer: 1638 if matcher.search(line) is not None: 1639 found = True 1640 break 1641 lnum += 1 1642 1643 if not found: 1644 lnum = -1 1645 return lnum 1646 1647 def header(self): 1648 curbuf = vim.current.buffer 1649 curbuf[0] = self.event + ' plugins ({0}/{1})'.format(len(self.bar), self.num_plugs) 1650 1651 num_spaces = self.num_plugs - len(self.bar) 1652 curbuf[1] = '[{0}{1}]'.format(self.bar, num_spaces * ' ') 1653 1654 with self.lock: 1655 vim.command('normal! 2G') 1656 vim.command('redraw') 1657 1658 def write(self, action, name, lines): 1659 first, rest = lines[0], lines[1:] 1660 msg = ['{0} {1}{2}{3}'.format(action, name, ': ' if first else '', first)] 1661 msg.extend([' ' + line for line in rest]) 1662 1663 try: 1664 if action == Action.ERROR: 1665 self.bar += 'x' 1666 vim.command("call add(s:update.errors, '{0}')".format(name)) 1667 elif action == Action.DONE: 1668 self.bar += '=' 1669 1670 curbuf = vim.current.buffer 1671 lnum = self.__where(name) 1672 if lnum != -1: # Found matching line num 1673 del curbuf[lnum] 1674 if lnum > self.maxy and action in set([Action.INSTALL, Action.UPDATE]): 1675 lnum = 3 1676 else: 1677 lnum = 3 1678 curbuf.append(msg, lnum) 1679 1680 self.header() 1681 except vim.error: 1682 pass 1683 1684 class Command(object): 1685 CD = 'cd /d' if G_IS_WIN else 'cd' 1686 1687 def __init__(self, cmd, cmd_dir=None, timeout=60, cb=None, clean=None): 1688 self.cmd = cmd 1689 if cmd_dir: 1690 self.cmd = '{0} {1} && {2}'.format(Command.CD, cmd_dir, self.cmd) 1691 self.timeout = timeout 1692 self.callback = cb if cb else (lambda msg: None) 1693 self.clean = clean if clean else (lambda: None) 1694 self.proc = None 1695 1696 @property 1697 def alive(self): 1698 """ Returns true only if command still running. """ 1699 return self.proc and self.proc.poll() is None 1700 1701 def execute(self, ntries=3): 1702 """ Execute the command with ntries if CmdTimedOut. 1703 Returns the output of the command if no Exception. 1704 """ 1705 attempt, finished, limit = 0, False, self.timeout 1706 1707 while not finished: 1708 try: 1709 attempt += 1 1710 result = self.try_command() 1711 finished = True 1712 return result 1713 except CmdTimedOut: 1714 if attempt != ntries: 1715 self.notify_retry() 1716 self.timeout += limit 1717 else: 1718 raise 1719 1720 def notify_retry(self): 1721 """ Retry required for command, notify user. """ 1722 for count in range(3, 0, -1): 1723 if G_STOP.is_set(): 1724 raise KeyboardInterrupt 1725 msg = 'Timeout. Will retry in {0} second{1} ...'.format( 1726 count, 's' if count != 1 else '') 1727 self.callback([msg]) 1728 time.sleep(1) 1729 self.callback(['Retrying ...']) 1730 1731 def try_command(self): 1732 """ Execute a cmd & poll for callback. Returns list of output. 1733 Raises CmdFailed -> return code for Popen isn't 0 1734 Raises CmdTimedOut -> command exceeded timeout without new output 1735 """ 1736 first_line = True 1737 1738 try: 1739 tfile = tempfile.NamedTemporaryFile(mode='w+b') 1740 preexec_fn = not G_IS_WIN and os.setsid or None 1741 self.proc = subprocess.Popen(self.cmd, stdout=tfile, 1742 stderr=subprocess.STDOUT, 1743 stdin=subprocess.PIPE, shell=True, 1744 preexec_fn=preexec_fn) 1745 thrd = thr.Thread(target=(lambda proc: proc.wait()), args=(self.proc,)) 1746 thrd.start() 1747 1748 thread_not_started = True 1749 while thread_not_started: 1750 try: 1751 thrd.join(0.1) 1752 thread_not_started = False 1753 except RuntimeError: 1754 pass 1755 1756 while self.alive: 1757 if G_STOP.is_set(): 1758 raise KeyboardInterrupt 1759 1760 if first_line or random.random() < G_LOG_PROB: 1761 first_line = False 1762 line = '' if G_IS_WIN else nonblock_read(tfile.name) 1763 if line: 1764 self.callback([line]) 1765 1766 time_diff = time.time() - os.path.getmtime(tfile.name) 1767 if time_diff > self.timeout: 1768 raise CmdTimedOut(['Timeout!']) 1769 1770 thrd.join(0.5) 1771 1772 tfile.seek(0) 1773 result = [line.decode('utf-8', 'replace').rstrip() for line in tfile] 1774 1775 if self.proc.returncode != 0: 1776 raise CmdFailed([''] + result) 1777 1778 return result 1779 except: 1780 self.terminate() 1781 raise 1782 1783 def terminate(self): 1784 """ Terminate process and cleanup. """ 1785 if self.alive: 1786 if G_IS_WIN: 1787 os.kill(self.proc.pid, signal.SIGINT) 1788 else: 1789 os.killpg(self.proc.pid, signal.SIGTERM) 1790 self.clean() 1791 1792 class Plugin(object): 1793 def __init__(self, name, args, buf_q, lock): 1794 self.name = name 1795 self.args = args 1796 self.buf_q = buf_q 1797 self.lock = lock 1798 self.tag = args.get('tag', 0) 1799 1800 def manage(self): 1801 try: 1802 if os.path.exists(self.args['dir']): 1803 self.update() 1804 else: 1805 self.install() 1806 with self.lock: 1807 thread_vim_command("let s:update.new['{0}'] = 1".format(self.name)) 1808 except PlugError as exc: 1809 self.write(Action.ERROR, self.name, exc.msg) 1810 except KeyboardInterrupt: 1811 G_STOP.set() 1812 self.write(Action.ERROR, self.name, ['Interrupted!']) 1813 except: 1814 # Any exception except those above print stack trace 1815 msg = 'Trace:\n{0}'.format(traceback.format_exc().rstrip()) 1816 self.write(Action.ERROR, self.name, msg.split('\n')) 1817 raise 1818 1819 def install(self): 1820 target = self.args['dir'] 1821 if target[-1] == '\\': 1822 target = target[0:-1] 1823 1824 def clean(target): 1825 def _clean(): 1826 try: 1827 shutil.rmtree(target) 1828 except OSError: 1829 pass 1830 return _clean 1831 1832 self.write(Action.INSTALL, self.name, ['Installing ...']) 1833 callback = functools.partial(self.write, Action.INSTALL, self.name) 1834 cmd = 'git clone {0} {1} {2} {3} 2>&1'.format( 1835 '' if self.tag else G_CLONE_OPT, G_PROGRESS, self.args['uri'], 1836 esc(target)) 1837 com = Command(cmd, None, G_TIMEOUT, callback, clean(target)) 1838 result = com.execute(G_RETRIES) 1839 self.write(Action.DONE, self.name, result[-1:]) 1840 1841 def repo_uri(self): 1842 cmd = 'git rev-parse --abbrev-ref HEAD 2>&1 && git config -f .git/config remote.origin.url' 1843 command = Command(cmd, self.args['dir'], G_TIMEOUT,) 1844 result = command.execute(G_RETRIES) 1845 return result[-1] 1846 1847 def update(self): 1848 actual_uri = self.repo_uri() 1849 expect_uri = self.args['uri'] 1850 regex = re.compile(r'^(?:\w+://)?(?:[^@/]*@)?([^:/]*(?::[0-9]*)?)[:/](.*?)(?:\.git)?/?$') 1851 ma = regex.match(actual_uri) 1852 mb = regex.match(expect_uri) 1853 if ma is None or mb is None or ma.groups() != mb.groups(): 1854 msg = ['', 1855 'Invalid URI: {0}'.format(actual_uri), 1856 'Expected {0}'.format(expect_uri), 1857 'PlugClean required.'] 1858 raise InvalidURI(msg) 1859 1860 if G_PULL: 1861 self.write(Action.UPDATE, self.name, ['Updating ...']) 1862 callback = functools.partial(self.write, Action.UPDATE, self.name) 1863 fetch_opt = '--depth 99999999' if self.tag and os.path.isfile(os.path.join(self.args['dir'], '.git/shallow')) else '' 1864 cmd = 'git fetch {0} {1} 2>&1'.format(fetch_opt, G_PROGRESS) 1865 com = Command(cmd, self.args['dir'], G_TIMEOUT, callback) 1866 result = com.execute(G_RETRIES) 1867 self.write(Action.DONE, self.name, result[-1:]) 1868 else: 1869 self.write(Action.DONE, self.name, ['Already installed']) 1870 1871 def write(self, action, name, msg): 1872 self.buf_q.put((action, name, msg)) 1873 1874 class PlugThread(thr.Thread): 1875 def __init__(self, tname, args): 1876 super(PlugThread, self).__init__() 1877 self.tname = tname 1878 self.args = args 1879 1880 def run(self): 1881 thr.current_thread().name = self.tname 1882 buf_q, work_q, lock = self.args 1883 1884 try: 1885 while not G_STOP.is_set(): 1886 name, args = work_q.get_nowait() 1887 plug = Plugin(name, args, buf_q, lock) 1888 plug.manage() 1889 work_q.task_done() 1890 except queue.Empty: 1891 pass 1892 1893 class RefreshThread(thr.Thread): 1894 def __init__(self, lock): 1895 super(RefreshThread, self).__init__() 1896 self.lock = lock 1897 self.running = True 1898 1899 def run(self): 1900 while self.running: 1901 with self.lock: 1902 thread_vim_command('noautocmd normal! a') 1903 time.sleep(0.33) 1904 1905 def stop(self): 1906 self.running = False 1907 1908 if G_NVIM: 1909 def thread_vim_command(cmd): 1910 vim.session.threadsafe_call(lambda: vim.command(cmd)) 1911 else: 1912 def thread_vim_command(cmd): 1913 vim.command(cmd) 1914 1915 def esc(name): 1916 return '"' + name.replace('"', '\"') + '"' 1917 1918 def nonblock_read(fname): 1919 """ Read a file with nonblock flag. Return the last line. """ 1920 fread = os.open(fname, os.O_RDONLY | os.O_NONBLOCK) 1921 buf = os.read(fread, 100000).decode('utf-8', 'replace') 1922 os.close(fread) 1923 1924 line = buf.rstrip('\r\n') 1925 left = max(line.rfind('\r'), line.rfind('\n')) 1926 if left != -1: 1927 left += 1 1928 line = line[left:] 1929 1930 return line 1931 1932 def main(): 1933 thr.current_thread().name = 'main' 1934 nthreads = int(vim.eval('s:update.threads')) 1935 plugs = vim.eval('s:update.todo') 1936 mac_gui = vim.eval('s:mac_gui') == '1' 1937 1938 lock = thr.Lock() 1939 buf = Buffer(lock, len(plugs), G_PULL) 1940 buf_q, work_q = queue.Queue(), queue.Queue() 1941 for work in plugs.items(): 1942 work_q.put(work) 1943 1944 start_cnt = thr.active_count() 1945 for num in range(nthreads): 1946 tname = 'PlugT-{0:02}'.format(num) 1947 thread = PlugThread(tname, (buf_q, work_q, lock)) 1948 thread.start() 1949 if mac_gui: 1950 rthread = RefreshThread(lock) 1951 rthread.start() 1952 1953 while not buf_q.empty() or thr.active_count() != start_cnt: 1954 try: 1955 action, name, msg = buf_q.get(True, 0.25) 1956 buf.write(action, name, ['OK'] if not msg else msg) 1957 buf_q.task_done() 1958 except queue.Empty: 1959 pass 1960 except KeyboardInterrupt: 1961 G_STOP.set() 1962 1963 if mac_gui: 1964 rthread.stop() 1965 rthread.join() 1966 1967 main() 1968 EOF 1969 endfunction 1970 1971 function! s:update_ruby() 1972 ruby << EOF 1973 module PlugStream 1974 SEP = ["\r", "\n", nil] 1975 def get_line 1976 buffer = '' 1977 loop do 1978 char = readchar rescue return 1979 if SEP.include? char.chr 1980 buffer << $/ 1981 break 1982 else 1983 buffer << char 1984 end 1985 end 1986 buffer 1987 end 1988 end unless defined?(PlugStream) 1989 1990 def esc arg 1991 %["#{arg.gsub('"', '\"')}"] 1992 end 1993 1994 def killall pid 1995 pids = [pid] 1996 if /mswin|mingw|bccwin/ =~ RUBY_PLATFORM 1997 pids.each { |pid| Process.kill 'INT', pid.to_i rescue nil } 1998 else 1999 unless `which pgrep 2> /dev/null`.empty? 2000 children = pids 2001 until children.empty? 2002 children = children.map { |pid| 2003 `pgrep -P #{pid}`.lines.map { |l| l.chomp } 2004 }.flatten 2005 pids += children 2006 end 2007 end 2008 pids.each { |pid| Process.kill 'TERM', pid.to_i rescue nil } 2009 end 2010 end 2011 2012 def compare_git_uri a, b 2013 regex = %r{^(?:\w+://)?(?:[^@/]*@)?([^:/]*(?::[0-9]*)?)[:/](.*?)(?:\.git)?/?$} 2014 regex.match(a).to_a.drop(1) == regex.match(b).to_a.drop(1) 2015 end 2016 2017 require 'thread' 2018 require 'fileutils' 2019 require 'timeout' 2020 running = true 2021 iswin = VIM::evaluate('s:is_win').to_i == 1 2022 pull = VIM::evaluate('s:update.pull').to_i == 1 2023 base = VIM::evaluate('g:plug_home') 2024 all = VIM::evaluate('s:update.todo') 2025 limit = VIM::evaluate('get(g:, "plug_timeout", 60)') 2026 tries = VIM::evaluate('get(g:, "plug_retries", 2)') + 1 2027 nthr = VIM::evaluate('s:update.threads').to_i 2028 maxy = VIM::evaluate('winheight(".")').to_i 2029 vim7 = VIM::evaluate('v:version').to_i <= 703 && RUBY_PLATFORM =~ /darwin/ 2030 cd = iswin ? 'cd /d' : 'cd' 2031 tot = VIM::evaluate('len(s:update.todo)') || 0 2032 bar = '' 2033 skip = 'Already installed' 2034 mtx = Mutex.new 2035 take1 = proc { mtx.synchronize { running && all.shift } } 2036 logh = proc { 2037 cnt = bar.length 2038 $curbuf[1] = "#{pull ? 'Updating' : 'Installing'} plugins (#{cnt}/#{tot})" 2039 $curbuf[2] = '[' + bar.ljust(tot) + ']' 2040 VIM::command('normal! 2G') 2041 VIM::command('redraw') 2042 } 2043 where = proc { |name| (1..($curbuf.length)).find { |l| $curbuf[l] =~ /^[-+x*] #{name}:/ } } 2044 log = proc { |name, result, type| 2045 mtx.synchronize do 2046 ing = ![true, false].include?(type) 2047 bar += type ? '=' : 'x' unless ing 2048 b = case type 2049 when :install then '+' when :update then '*' 2050 when true, nil then '-' else 2051 VIM::command("call add(s:update.errors, '#{name}')") 2052 'x' 2053 end 2054 result = 2055 if type || type.nil? 2056 ["#{b} #{name}: #{result.lines.to_a.last || 'OK'}"] 2057 elsif result =~ /^Interrupted|^Timeout/ 2058 ["#{b} #{name}: #{result}"] 2059 else 2060 ["#{b} #{name}"] + result.lines.map { |l| " " << l } 2061 end 2062 if lnum = where.call(name) 2063 $curbuf.delete lnum 2064 lnum = 4 if ing && lnum > maxy 2065 end 2066 result.each_with_index do |line, offset| 2067 $curbuf.append((lnum || 4) - 1 + offset, line.gsub(/\e\[./, '').chomp) 2068 end 2069 logh.call 2070 end 2071 } 2072 bt = proc { |cmd, name, type, cleanup| 2073 tried = timeout = 0 2074 begin 2075 tried += 1 2076 timeout += limit 2077 fd = nil 2078 data = '' 2079 if iswin 2080 Timeout::timeout(timeout) do 2081 tmp = VIM::evaluate('tempname()') 2082 system("(#{cmd}) > #{tmp}") 2083 data = File.read(tmp).chomp 2084 File.unlink tmp rescue nil 2085 end 2086 else 2087 fd = IO.popen(cmd).extend(PlugStream) 2088 first_line = true 2089 log_prob = 1.0 / nthr 2090 while line = Timeout::timeout(timeout) { fd.get_line } 2091 data << line 2092 log.call name, line.chomp, type if name && (first_line || rand < log_prob) 2093 first_line = false 2094 end 2095 fd.close 2096 end 2097 [$? == 0, data.chomp] 2098 rescue Timeout::Error, Interrupt => e 2099 if fd && !fd.closed? 2100 killall fd.pid 2101 fd.close 2102 end 2103 cleanup.call if cleanup 2104 if e.is_a?(Timeout::Error) && tried < tries 2105 3.downto(1) do |countdown| 2106 s = countdown > 1 ? 's' : '' 2107 log.call name, "Timeout. Will retry in #{countdown} second#{s} ...", type 2108 sleep 1 2109 end 2110 log.call name, 'Retrying ...', type 2111 retry 2112 end 2113 [false, e.is_a?(Interrupt) ? "Interrupted!" : "Timeout!"] 2114 end 2115 } 2116 main = Thread.current 2117 threads = [] 2118 watcher = Thread.new { 2119 if vim7 2120 while VIM::evaluate('getchar(1)') 2121 sleep 0.1 2122 end 2123 else 2124 require 'io/console' # >= Ruby 1.9 2125 nil until IO.console.getch == 3.chr 2126 end 2127 mtx.synchronize do 2128 running = false 2129 threads.each { |t| t.raise Interrupt } unless vim7 2130 end 2131 threads.each { |t| t.join rescue nil } 2132 main.kill 2133 } 2134 refresh = Thread.new { 2135 while true 2136 mtx.synchronize do 2137 break unless running 2138 VIM::command('noautocmd normal! a') 2139 end 2140 sleep 0.2 2141 end 2142 } if VIM::evaluate('s:mac_gui') == 1 2143 2144 clone_opt = VIM::evaluate('s:clone_opt').join(' ') 2145 progress = VIM::evaluate('s:progress_opt(1)') 2146 nthr.times do 2147 mtx.synchronize do 2148 threads << Thread.new { 2149 while pair = take1.call 2150 name = pair.first 2151 dir, uri, tag = pair.last.values_at *%w[dir uri tag] 2152 exists = File.directory? dir 2153 ok, result = 2154 if exists 2155 chdir = "#{cd} #{iswin ? dir : esc(dir)}" 2156 ret, data = bt.call "#{chdir} && git rev-parse --abbrev-ref HEAD 2>&1 && git config -f .git/config remote.origin.url", nil, nil, nil 2157 current_uri = data.lines.to_a.last 2158 if !ret 2159 if data =~ /^Interrupted|^Timeout/ 2160 [false, data] 2161 else 2162 [false, [data.chomp, "PlugClean required."].join($/)] 2163 end 2164 elsif !compare_git_uri(current_uri, uri) 2165 [false, ["Invalid URI: #{current_uri}", 2166 "Expected: #{uri}", 2167 "PlugClean required."].join($/)] 2168 else 2169 if pull 2170 log.call name, 'Updating ...', :update 2171 fetch_opt = (tag && File.exist?(File.join(dir, '.git/shallow'))) ? '--depth 99999999' : '' 2172 bt.call "#{chdir} && git fetch #{fetch_opt} #{progress} 2>&1", name, :update, nil 2173 else 2174 [true, skip] 2175 end 2176 end 2177 else 2178 d = esc dir.sub(%r{[\\/]+$}, '') 2179 log.call name, 'Installing ...', :install 2180 bt.call "git clone #{clone_opt unless tag} #{progress} #{uri} #{d} 2>&1", name, :install, proc { 2181 FileUtils.rm_rf dir 2182 } 2183 end 2184 mtx.synchronize { VIM::command("let s:update.new['#{name}'] = 1") } if !exists && ok 2185 log.call name, result, ok 2186 end 2187 } if running 2188 end 2189 end 2190 threads.each { |t| t.join rescue nil } 2191 logh.call 2192 refresh.kill if refresh 2193 watcher.kill 2194 EOF 2195 endfunction 2196 2197 function! s:shellesc_cmd(arg, script) 2198 let escaped = substitute('"'.a:arg.'"', '[&|<>()@^!"]', '^&', 'g') 2199 return substitute(escaped, '%', (a:script ? '%' : '^') . '&', 'g') 2200 endfunction 2201 2202 function! s:shellesc_ps1(arg) 2203 return "'".substitute(escape(a:arg, '\"'), "'", "''", 'g')."'" 2204 endfunction 2205 2206 function! s:shellesc_sh(arg) 2207 return "'".substitute(a:arg, "'", "'\\\\''", 'g')."'" 2208 endfunction 2209 2210 " Escape the shell argument based on the shell. 2211 " Vim and Neovim's shellescape() are insufficient. 2212 " 1. shellslash determines whether to use single/double quotes. 2213 " Double-quote escaping is fragile for cmd.exe. 2214 " 2. It does not work for powershell. 2215 " 3. It does not work for *sh shells if the command is executed 2216 " via cmd.exe (ie. cmd.exe /c sh -c command command_args) 2217 " 4. It does not support batchfile syntax. 2218 " 2219 " Accepts an optional dictionary with the following keys: 2220 " - shell: same as Vim/Neovim 'shell' option. 2221 " If unset, fallback to 'cmd.exe' on Windows or 'sh'. 2222 " - script: If truthy and shell is cmd.exe, escape for batchfile syntax. 2223 function! plug#shellescape(arg, ...) 2224 if a:arg =~# '^[A-Za-z0-9_/:.-]\+$' 2225 return a:arg 2226 endif 2227 let opts = a:0 > 0 && type(a:1) == s:TYPE.dict ? a:1 : {} 2228 let shell = get(opts, 'shell', s:is_win ? 'cmd.exe' : 'sh') 2229 let script = get(opts, 'script', 1) 2230 if shell =~# 'cmd\(\.exe\)\?$' 2231 return s:shellesc_cmd(a:arg, script) 2232 elseif s:is_powershell(shell) 2233 return s:shellesc_ps1(a:arg) 2234 endif 2235 return s:shellesc_sh(a:arg) 2236 endfunction 2237 2238 function! s:glob_dir(path) 2239 return map(filter(s:glob(a:path, '**'), 'isdirectory(v:val)'), 's:dirpath(v:val)') 2240 endfunction 2241 2242 function! s:progress_bar(line, bar, total) 2243 call setline(a:line, '[' . s:lpad(a:bar, a:total) . ']') 2244 endfunction 2245 2246 function! s:compare_git_uri(a, b) 2247 " See `git help clone' 2248 " https:// [user@] github.com[:port] / junegunn/vim-plug [.git] 2249 " [git@] github.com[:port] : junegunn/vim-plug [.git] 2250 " file:// / junegunn/vim-plug [/] 2251 " / junegunn/vim-plug [/] 2252 let pat = '^\%(\w\+://\)\='.'\%([^@/]*@\)\='.'\([^:/]*\%(:[0-9]*\)\=\)'.'[:/]'.'\(.\{-}\)'.'\%(\.git\)\=/\?$' 2253 let ma = matchlist(a:a, pat) 2254 let mb = matchlist(a:b, pat) 2255 return ma[1:2] ==# mb[1:2] 2256 endfunction 2257 2258 function! s:format_message(bullet, name, message) 2259 if a:bullet != 'x' 2260 return [printf('%s %s: %s', a:bullet, a:name, s:lastline(a:message))] 2261 else 2262 let lines = map(s:lines(a:message), '" ".v:val') 2263 return extend([printf('x %s:', a:name)], lines) 2264 endif 2265 endfunction 2266 2267 function! s:with_cd(cmd, dir, ...) 2268 let script = a:0 > 0 ? a:1 : 1 2269 return printf('cd%s %s && %s', s:is_win ? ' /d' : '', plug#shellescape(a:dir, {'script': script}), a:cmd) 2270 endfunction 2271 2272 function! s:system(cmd, ...) 2273 let batchfile = '' 2274 try 2275 let [sh, shellcmdflag, shrd] = s:chsh(1) 2276 if type(a:cmd) == s:TYPE.list 2277 " Neovim's system() supports list argument to bypass the shell 2278 " but it cannot set the working directory for the command. 2279 " Assume that the command does not rely on the shell. 2280 if has('nvim') && a:0 == 0 2281 return system(a:cmd) 2282 endif 2283 let cmd = join(map(copy(a:cmd), 'plug#shellescape(v:val, {"shell": &shell, "script": 0})')) 2284 if s:is_powershell(&shell) 2285 let cmd = '& ' . cmd 2286 endif 2287 else 2288 let cmd = a:cmd 2289 endif 2290 if a:0 > 0 2291 let cmd = s:with_cd(cmd, a:1, type(a:cmd) != s:TYPE.list) 2292 endif 2293 if s:is_win && type(a:cmd) != s:TYPE.list 2294 let [batchfile, cmd] = s:batchfile(cmd) 2295 endif 2296 return system(cmd) 2297 finally 2298 let [&shell, &shellcmdflag, &shellredir] = [sh, shellcmdflag, shrd] 2299 if s:is_win && filereadable(batchfile) 2300 call delete(batchfile) 2301 endif 2302 endtry 2303 endfunction 2304 2305 function! s:system_chomp(...) 2306 let ret = call('s:system', a:000) 2307 return v:shell_error ? '' : substitute(ret, '\n$', '', '') 2308 endfunction 2309 2310 function! s:git_validate(spec, check_branch) 2311 let err = '' 2312 if isdirectory(a:spec.dir) 2313 let result = [s:git_local_branch(a:spec.dir), s:git_origin_url(a:spec.dir)] 2314 let remote = result[-1] 2315 if empty(remote) 2316 let err = join([remote, 'PlugClean required.'], "\n") 2317 elseif !s:compare_git_uri(remote, a:spec.uri) 2318 let err = join(['Invalid URI: '.remote, 2319 \ 'Expected: '.a:spec.uri, 2320 \ 'PlugClean required.'], "\n") 2321 elseif a:check_branch && has_key(a:spec, 'commit') 2322 let sha = s:git_revision(a:spec.dir) 2323 if empty(sha) 2324 let err = join(add(result, 'PlugClean required.'), "\n") 2325 elseif !s:hash_match(sha, a:spec.commit) 2326 let err = join([printf('Invalid HEAD (expected: %s, actual: %s)', 2327 \ a:spec.commit[:6], sha[:6]), 2328 \ 'PlugUpdate required.'], "\n") 2329 endif 2330 elseif a:check_branch 2331 let current_branch = result[0] 2332 " Check tag 2333 let origin_branch = s:git_origin_branch(a:spec) 2334 if has_key(a:spec, 'tag') 2335 let tag = s:system_chomp('git describe --exact-match --tags HEAD 2>&1', a:spec.dir) 2336 if a:spec.tag !=# tag && a:spec.tag !~ '\*' 2337 let err = printf('Invalid tag: %s (expected: %s). Try PlugUpdate.', 2338 \ (empty(tag) ? 'N/A' : tag), a:spec.tag) 2339 endif 2340 " Check branch 2341 elseif origin_branch !=# current_branch 2342 let err = printf('Invalid branch: %s (expected: %s). Try PlugUpdate.', 2343 \ current_branch, origin_branch) 2344 endif 2345 if empty(err) 2346 let [ahead, behind] = split(s:lastline(s:system([ 2347 \ 'git', 'rev-list', '--count', '--left-right', 2348 \ printf('HEAD...origin/%s', origin_branch) 2349 \ ], a:spec.dir)), '\t') 2350 if !v:shell_error && ahead 2351 if behind 2352 " Only mention PlugClean if diverged, otherwise it's likely to be 2353 " pushable (and probably not that messed up). 2354 let err = printf( 2355 \ "Diverged from origin/%s (%d commit(s) ahead and %d commit(s) behind!\n" 2356 \ .'Backup local changes and run PlugClean and PlugUpdate to reinstall it.', origin_branch, ahead, behind) 2357 else 2358 let err = printf("Ahead of origin/%s by %d commit(s).\n" 2359 \ .'Cannot update until local changes are pushed.', 2360 \ origin_branch, ahead) 2361 endif 2362 endif 2363 endif 2364 endif 2365 else 2366 let err = 'Not found' 2367 endif 2368 return [err, err =~# 'PlugClean'] 2369 endfunction 2370 2371 function! s:rm_rf(dir) 2372 if isdirectory(a:dir) 2373 return s:system(s:is_win 2374 \ ? 'rmdir /S /Q '.plug#shellescape(a:dir) 2375 \ : ['rm', '-rf', a:dir]) 2376 endif 2377 endfunction 2378 2379 function! s:clean(force) 2380 call s:prepare() 2381 call append(0, 'Searching for invalid plugins in '.g:plug_home) 2382 call append(1, '') 2383 2384 " List of valid directories 2385 let dirs = [] 2386 let errs = {} 2387 let [cnt, total] = [0, len(g:plugs)] 2388 for [name, spec] in items(g:plugs) 2389 if !s:is_managed(name) 2390 call add(dirs, spec.dir) 2391 else 2392 let [err, clean] = s:git_validate(spec, 1) 2393 if clean 2394 let errs[spec.dir] = s:lines(err)[0] 2395 else 2396 call add(dirs, spec.dir) 2397 endif 2398 endif 2399 let cnt += 1 2400 call s:progress_bar(2, repeat('=', cnt), total) 2401 normal! 2G 2402 redraw 2403 endfor 2404 2405 let allowed = {} 2406 for dir in dirs 2407 let allowed[s:dirpath(s:plug_fnamemodify(dir, ':h:h'))] = 1 2408 let allowed[dir] = 1 2409 for child in s:glob_dir(dir) 2410 let allowed[child] = 1 2411 endfor 2412 endfor 2413 2414 let todo = [] 2415 let found = sort(s:glob_dir(g:plug_home)) 2416 while !empty(found) 2417 let f = remove(found, 0) 2418 if !has_key(allowed, f) && isdirectory(f) 2419 call add(todo, f) 2420 call append(line('$'), '- ' . f) 2421 if has_key(errs, f) 2422 call append(line('$'), ' ' . errs[f]) 2423 endif 2424 let found = filter(found, 'stridx(v:val, f) != 0') 2425 end 2426 endwhile 2427 2428 4 2429 redraw 2430 if empty(todo) 2431 call append(line('$'), 'Already clean.') 2432 else 2433 let s:clean_count = 0 2434 call append(3, ['Directories to delete:', '']) 2435 redraw! 2436 if a:force || s:ask_no_interrupt('Delete all directories?') 2437 call s:delete([6, line('$')], 1) 2438 else 2439 call setline(4, 'Cancelled.') 2440 nnoremap <silent> <buffer> d :set opfunc=<sid>delete_op<cr>g@ 2441 nmap <silent> <buffer> dd d_ 2442 xnoremap <silent> <buffer> d :<c-u>call <sid>delete_op(visualmode(), 1)<cr> 2443 echo 'Delete the lines (d{motion}) to delete the corresponding directories' 2444 endif 2445 endif 2446 4 2447 setlocal nomodifiable 2448 endfunction 2449 2450 function! s:delete_op(type, ...) 2451 call s:delete(a:0 ? [line("'<"), line("'>")] : [line("'["), line("']")], 0) 2452 endfunction 2453 2454 function! s:delete(range, force) 2455 let [l1, l2] = a:range 2456 let force = a:force 2457 let err_count = 0 2458 while l1 <= l2 2459 let line = getline(l1) 2460 if line =~ '^- ' && isdirectory(line[2:]) 2461 execute l1 2462 redraw! 2463 let answer = force ? 1 : s:ask('Delete '.line[2:].'?', 1) 2464 let force = force || answer > 1 2465 if answer 2466 let err = s:rm_rf(line[2:]) 2467 setlocal modifiable 2468 if empty(err) 2469 call setline(l1, '~'.line[1:]) 2470 let s:clean_count += 1 2471 else 2472 delete _ 2473 call append(l1 - 1, s:format_message('x', line[1:], err)) 2474 let l2 += len(s:lines(err)) 2475 let err_count += 1 2476 endif 2477 let msg = printf('Removed %d directories.', s:clean_count) 2478 if err_count > 0 2479 let msg .= printf(' Failed to remove %d directories.', err_count) 2480 endif 2481 call setline(4, msg) 2482 setlocal nomodifiable 2483 endif 2484 endif 2485 let l1 += 1 2486 endwhile 2487 endfunction 2488 2489 function! s:upgrade() 2490 echo 'Downloading the latest version of vim-plug' 2491 redraw 2492 let tmp = s:plug_tempname() 2493 let new = tmp . '/plug.vim' 2494 2495 try 2496 let out = s:system(['git', 'clone', '--depth', '1', s:plug_src, tmp]) 2497 if v:shell_error 2498 return s:err('Error upgrading vim-plug: '. out) 2499 endif 2500 2501 if readfile(s:me) ==# readfile(new) 2502 echo 'vim-plug is already up-to-date' 2503 return 0 2504 else 2505 call rename(s:me, s:me . '.old') 2506 call rename(new, s:me) 2507 unlet g:loaded_plug 2508 echo 'vim-plug has been upgraded' 2509 return 1 2510 endif 2511 finally 2512 silent! call s:rm_rf(tmp) 2513 endtry 2514 endfunction 2515 2516 function! s:upgrade_specs() 2517 for spec in values(g:plugs) 2518 let spec.frozen = get(spec, 'frozen', 0) 2519 endfor 2520 endfunction 2521 2522 function! s:status() 2523 call s:prepare() 2524 call append(0, 'Checking plugins') 2525 call append(1, '') 2526 2527 let ecnt = 0 2528 let unloaded = 0 2529 let [cnt, total] = [0, len(g:plugs)] 2530 for [name, spec] in items(g:plugs) 2531 let is_dir = isdirectory(spec.dir) 2532 if has_key(spec, 'uri') 2533 if is_dir 2534 let [err, _] = s:git_validate(spec, 1) 2535 let [valid, msg] = [empty(err), empty(err) ? 'OK' : err] 2536 else 2537 let [valid, msg] = [0, 'Not found. Try PlugInstall.'] 2538 endif 2539 else 2540 if is_dir 2541 let [valid, msg] = [1, 'OK'] 2542 else 2543 let [valid, msg] = [0, 'Not found.'] 2544 endif 2545 endif 2546 let cnt += 1 2547 let ecnt += !valid 2548 " `s:loaded` entry can be missing if PlugUpgraded 2549 if is_dir && get(s:loaded, name, -1) == 0 2550 let unloaded = 1 2551 let msg .= ' (not loaded)' 2552 endif 2553 call s:progress_bar(2, repeat('=', cnt), total) 2554 call append(3, s:format_message(valid ? '-' : 'x', name, msg)) 2555 normal! 2G 2556 redraw 2557 endfor 2558 call setline(1, 'Finished. '.ecnt.' error(s).') 2559 normal! gg 2560 setlocal nomodifiable 2561 if unloaded 2562 echo "Press 'L' on each line to load plugin, or 'U' to update" 2563 nnoremap <silent> <buffer> L :call <SID>status_load(line('.'))<cr> 2564 xnoremap <silent> <buffer> L :call <SID>status_load(line('.'))<cr> 2565 end 2566 endfunction 2567 2568 function! s:extract_name(str, prefix, suffix) 2569 return matchstr(a:str, '^'.a:prefix.' \zs[^:]\+\ze:.*'.a:suffix.'$') 2570 endfunction 2571 2572 function! s:status_load(lnum) 2573 let line = getline(a:lnum) 2574 let name = s:extract_name(line, '-', '(not loaded)') 2575 if !empty(name) 2576 call plug#load(name) 2577 setlocal modifiable 2578 call setline(a:lnum, substitute(line, ' (not loaded)$', '', '')) 2579 setlocal nomodifiable 2580 endif 2581 endfunction 2582 2583 function! s:status_update() range 2584 let lines = getline(a:firstline, a:lastline) 2585 let names = filter(map(lines, 's:extract_name(v:val, "[x-]", "")'), '!empty(v:val)') 2586 if !empty(names) 2587 echo 2588 execute 'PlugUpdate' join(names) 2589 endif 2590 endfunction 2591 2592 function! s:is_preview_window_open() 2593 silent! wincmd P 2594 if &previewwindow 2595 wincmd p 2596 return 1 2597 endif 2598 endfunction 2599 2600 function! s:find_name(lnum) 2601 for lnum in reverse(range(1, a:lnum)) 2602 let line = getline(lnum) 2603 if empty(line) 2604 return '' 2605 endif 2606 let name = s:extract_name(line, '-', '') 2607 if !empty(name) 2608 return name 2609 endif 2610 endfor 2611 return '' 2612 endfunction 2613 2614 function! s:preview_commit() 2615 if b:plug_preview < 0 2616 let b:plug_preview = !s:is_preview_window_open() 2617 endif 2618 2619 let sha = matchstr(getline('.'), '^ \X*\zs[0-9a-f]\{7,9}') 2620 if empty(sha) 2621 return 2622 endif 2623 2624 let name = s:find_name(line('.')) 2625 if empty(name) || !has_key(g:plugs, name) || !isdirectory(g:plugs[name].dir) 2626 return 2627 endif 2628 2629 if exists('g:plug_pwindow') && !s:is_preview_window_open() 2630 execute g:plug_pwindow 2631 execute 'e' sha 2632 else 2633 execute 'pedit' sha 2634 wincmd P 2635 endif 2636 setlocal previewwindow filetype=git buftype=nofile nobuflisted modifiable 2637 let batchfile = '' 2638 try 2639 let [sh, shellcmdflag, shrd] = s:chsh(1) 2640 let cmd = 'cd '.plug#shellescape(g:plugs[name].dir).' && git show --no-color --pretty=medium '.sha 2641 if s:is_win 2642 let [batchfile, cmd] = s:batchfile(cmd) 2643 endif 2644 execute 'silent %!' cmd 2645 finally 2646 let [&shell, &shellcmdflag, &shellredir] = [sh, shellcmdflag, shrd] 2647 if s:is_win && filereadable(batchfile) 2648 call delete(batchfile) 2649 endif 2650 endtry 2651 setlocal nomodifiable 2652 nnoremap <silent> <buffer> q :q<cr> 2653 wincmd p 2654 endfunction 2655 2656 function! s:section(flags) 2657 call search('\(^[x-] \)\@<=[^:]\+:', a:flags) 2658 endfunction 2659 2660 function! s:format_git_log(line) 2661 let indent = ' ' 2662 let tokens = split(a:line, nr2char(1)) 2663 if len(tokens) != 5 2664 return indent.substitute(a:line, '\s*$', '', '') 2665 endif 2666 let [graph, sha, refs, subject, date] = tokens 2667 let tag = matchstr(refs, 'tag: [^,)]\+') 2668 let tag = empty(tag) ? ' ' : ' ('.tag.') ' 2669 return printf('%s%s%s%s%s (%s)', indent, graph, sha, tag, subject, date) 2670 endfunction 2671 2672 function! s:append_ul(lnum, text) 2673 call append(a:lnum, ['', a:text, repeat('-', len(a:text))]) 2674 endfunction 2675 2676 function! s:diff() 2677 call s:prepare() 2678 call append(0, ['Collecting changes ...', '']) 2679 let cnts = [0, 0] 2680 let bar = '' 2681 let total = filter(copy(g:plugs), 's:is_managed(v:key) && isdirectory(v:val.dir)') 2682 call s:progress_bar(2, bar, len(total)) 2683 for origin in [1, 0] 2684 let plugs = reverse(sort(items(filter(copy(total), (origin ? '' : '!').'(has_key(v:val, "commit") || has_key(v:val, "tag"))')))) 2685 if empty(plugs) 2686 continue 2687 endif 2688 call s:append_ul(2, origin ? 'Pending updates:' : 'Last update:') 2689 for [k, v] in plugs 2690 let branch = s:git_origin_branch(v) 2691 if len(branch) 2692 let range = origin ? '..origin/'.branch : 'HEAD@{1}..' 2693 let cmd = ['git', 'log', '--graph', '--color=never'] 2694 if s:git_version_requirement(2, 10, 0) 2695 call add(cmd, '--no-show-signature') 2696 endif 2697 call extend(cmd, ['--pretty=format:%x01%h%x01%d%x01%s%x01%cr', range]) 2698 if has_key(v, 'rtp') 2699 call extend(cmd, ['--', v.rtp]) 2700 endif 2701 let diff = s:system_chomp(cmd, v.dir) 2702 if !empty(diff) 2703 let ref = has_key(v, 'tag') ? (' (tag: '.v.tag.')') : has_key(v, 'commit') ? (' '.v.commit) : '' 2704 call append(5, extend(['', '- '.k.':'.ref], map(s:lines(diff), 's:format_git_log(v:val)'))) 2705 let cnts[origin] += 1 2706 endif 2707 endif 2708 let bar .= '=' 2709 call s:progress_bar(2, bar, len(total)) 2710 normal! 2G 2711 redraw 2712 endfor 2713 if !cnts[origin] 2714 call append(5, ['', 'N/A']) 2715 endif 2716 endfor 2717 call setline(1, printf('%d plugin(s) updated.', cnts[0]) 2718 \ . (cnts[1] ? printf(' %d plugin(s) have pending updates.', cnts[1]) : '')) 2719 2720 if cnts[0] || cnts[1] 2721 nnoremap <silent> <buffer> <plug>(plug-preview) :silent! call <SID>preview_commit()<cr> 2722 if empty(maparg("\<cr>", 'n')) 2723 nmap <buffer> <cr> <plug>(plug-preview) 2724 endif 2725 if empty(maparg('o', 'n')) 2726 nmap <buffer> o <plug>(plug-preview) 2727 endif 2728 endif 2729 if cnts[0] 2730 nnoremap <silent> <buffer> X :call <SID>revert()<cr> 2731 echo "Press 'X' on each block to revert the update" 2732 endif 2733 normal! gg 2734 setlocal nomodifiable 2735 endfunction 2736 2737 function! s:revert() 2738 if search('^Pending updates', 'bnW') 2739 return 2740 endif 2741 2742 let name = s:find_name(line('.')) 2743 if empty(name) || !has_key(g:plugs, name) || 2744 \ input(printf('Revert the update of %s? (y/N) ', name)) !~? '^y' 2745 return 2746 endif 2747 2748 call s:system('git reset --hard HEAD@{1} && git checkout '.plug#shellescape(g:plugs[name].branch).' --', g:plugs[name].dir) 2749 setlocal modifiable 2750 normal! "_dap 2751 setlocal nomodifiable 2752 echo 'Reverted' 2753 endfunction 2754 2755 function! s:snapshot(force, ...) abort 2756 call s:prepare() 2757 setf vim 2758 call append(0, ['" Generated by vim-plug', 2759 \ '" '.strftime("%c"), 2760 \ '" :source this file in vim to restore the snapshot', 2761 \ '" or execute: vim -S snapshot.vim', 2762 \ '', '', 'PlugUpdate!']) 2763 1 2764 let anchor = line('$') - 3 2765 let names = sort(keys(filter(copy(g:plugs), 2766 \'has_key(v:val, "uri") && !has_key(v:val, "commit") && isdirectory(v:val.dir)'))) 2767 for name in reverse(names) 2768 let sha = s:git_revision(g:plugs[name].dir) 2769 if !empty(sha) 2770 call append(anchor, printf("silent! let g:plugs['%s'].commit = '%s'", name, sha)) 2771 redraw 2772 endif 2773 endfor 2774 2775 if a:0 > 0 2776 let fn = s:plug_expand(a:1) 2777 if filereadable(fn) && !(a:force || s:ask(a:1.' already exists. Overwrite?')) 2778 return 2779 endif 2780 call writefile(getline(1, '$'), fn) 2781 echo 'Saved as '.a:1 2782 silent execute 'e' s:esc(fn) 2783 setf vim 2784 endif 2785 endfunction 2786 2787 function! s:split_rtp() 2788 return split(&rtp, '\\\@<!,') 2789 endfunction 2790 2791 let s:first_rtp = s:escrtp(get(s:split_rtp(), 0, '')) 2792 let s:last_rtp = s:escrtp(get(s:split_rtp(), -1, '')) 2793 2794 if exists('g:plugs') 2795 let g:plugs_order = get(g:, 'plugs_order', keys(g:plugs)) 2796 call s:upgrade_specs() 2797 call s:define_commands() 2798 endif 2799 2800 let &cpo = s:cpo_save 2801 unlet s:cpo_save