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