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