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