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