dotfiles

My dotfiles for Arch Linux
Log | Files | Refs | README

plug.vim (78763B)


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