dotfiles

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README

plug.vim (84862B)


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