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