dotfiles

Torpy's handcrafted dootfiles.
Log | Files | Refs | README

plug.vim (84223B)


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