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