dotfiles

beau's configuration files
git clone https://git.beauhilton.com/dotfiles.git
Log | Files | Refs | README

plug.vim.old (76623B)


      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('Call 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 && a:swap
    868     set shell=sh shellredir=>%s\ 2>&1
    869   endif
    870   return prev
    871 endfunction
    872 
    873 function! s:bang(cmd, ...)
    874   let batchfile = ''
    875   try
    876     let [sh, shellcmdflag, shrd] = s:chsh(a:0)
    877     " FIXME: Escaping is incomplete. We could use shellescape with eval,
    878     "        but it won't work on Windows.
    879     let cmd = a:0 ? s:with_cd(a:cmd, a:1) : a:cmd
    880     if s:is_win
    881       let [batchfile, cmd] = s:batchfile(cmd)
    882     endif
    883     let g:_plug_bang = (s:is_win && has('gui_running') ? 'silent ' : '').'!'.escape(cmd, '#!%')
    884     execute "normal! :execute g:_plug_bang\<cr>\<cr>"
    885   finally
    886     unlet g:_plug_bang
    887     let [&shell, &shellcmdflag, &shellredir] = [sh, shellcmdflag, shrd]
    888     if s:is_win && filereadable(batchfile)
    889       call delete(batchfile)
    890     endif
    891   endtry
    892   return v:shell_error ? 'Exit status: ' . v:shell_error : ''
    893 endfunction
    894 
    895 function! s:regress_bar()
    896   let bar = substitute(getline(2)[1:-2], '.*\zs=', 'x', '')
    897   call s:progress_bar(2, bar, len(bar))
    898 endfunction
    899 
    900 function! s:is_updated(dir)
    901   return !empty(s:system_chomp('git log --pretty=format:"%h" "HEAD...HEAD@{1}"', a:dir))
    902 endfunction
    903 
    904 function! s:do(pull, force, todo)
    905   for [name, spec] in items(a:todo)
    906     if !isdirectory(spec.dir)
    907       continue
    908     endif
    909     let installed = has_key(s:update.new, name)
    910     let updated = installed ? 0 :
    911       \ (a:pull && index(s:update.errors, name) < 0 && s:is_updated(spec.dir))
    912     if a:force || installed || updated
    913       execute 'cd' s:esc(spec.dir)
    914       call append(3, '- Post-update hook for '. name .' ... ')
    915       let error = ''
    916       let type = type(spec.do)
    917       if type == s:TYPE.string
    918         if spec.do[0] == ':'
    919           if !get(s:loaded, name, 0)
    920             let s:loaded[name] = 1
    921             call s:reorg_rtp()
    922           endif
    923           call s:load_plugin(spec)
    924           try
    925             execute spec.do[1:]
    926           catch
    927             let error = v:exception
    928           endtry
    929           if !s:plug_window_exists()
    930             cd -
    931             throw 'Warning: vim-plug was terminated by the post-update hook of '.name
    932           endif
    933         else
    934           let error = s:bang(spec.do)
    935         endif
    936       elseif type == s:TYPE.funcref
    937         try
    938           let status = installed ? 'installed' : (updated ? 'updated' : 'unchanged')
    939           call spec.do({ 'name': name, 'status': status, 'force': a:force })
    940         catch
    941           let error = v:exception
    942         endtry
    943       else
    944         let error = 'Invalid hook type'
    945       endif
    946       call s:switch_in()
    947       call setline(4, empty(error) ? (getline(4) . 'OK')
    948                                  \ : ('x' . getline(4)[1:] . error))
    949       if !empty(error)
    950         call add(s:update.errors, name)
    951         call s:regress_bar()
    952       endif
    953       cd -
    954     endif
    955   endfor
    956 endfunction
    957 
    958 function! s:hash_match(a, b)
    959   return stridx(a:a, a:b) == 0 || stridx(a:b, a:a) == 0
    960 endfunction
    961 
    962 function! s:checkout(spec)
    963   let sha = a:spec.commit
    964   let output = s:system('git rev-parse HEAD', a:spec.dir)
    965   if !v:shell_error && !s:hash_match(sha, s:lines(output)[0])
    966     let output = s:system(
    967           \ 'git fetch --depth 999999 && git checkout '.plug#shellescape(sha).' --', a:spec.dir)
    968   endif
    969   return output
    970 endfunction
    971 
    972 function! s:finish(pull)
    973   let new_frozen = len(filter(keys(s:update.new), 'g:plugs[v:val].frozen'))
    974   if new_frozen
    975     let s = new_frozen > 1 ? 's' : ''
    976     call append(3, printf('- Installed %d frozen plugin%s', new_frozen, s))
    977   endif
    978   call append(3, '- Finishing ... ') | 4
    979   redraw
    980   call plug#helptags()
    981   call plug#end()
    982   call setline(4, getline(4) . 'Done!')
    983   redraw
    984   let msgs = []
    985   if !empty(s:update.errors)
    986     call add(msgs, "Press 'R' to retry.")
    987   endif
    988   if a:pull && len(s:update.new) < len(filter(getline(5, '$'),
    989                 \ "v:val =~ '^- ' && v:val !~# 'Already up.to.date'"))
    990     call add(msgs, "Press 'D' to see the updated changes.")
    991   endif
    992   echo join(msgs, ' ')
    993   call s:finish_bindings()
    994 endfunction
    995 
    996 function! s:retry()
    997   if empty(s:update.errors)
    998     return
    999   endif
   1000   echo
   1001   call s:update_impl(s:update.pull, s:update.force,
   1002         \ extend(copy(s:update.errors), [s:update.threads]))
   1003 endfunction
   1004 
   1005 function! s:is_managed(name)
   1006   return has_key(g:plugs[a:name], 'uri')
   1007 endfunction
   1008 
   1009 function! s:names(...)
   1010   return sort(filter(keys(g:plugs), 'stridx(v:val, a:1) == 0 && s:is_managed(v:val)'))
   1011 endfunction
   1012 
   1013 function! s:check_ruby()
   1014   silent! ruby require 'thread'; VIM::command("let g:plug_ruby = '#{RUBY_VERSION}'")
   1015   if !exists('g:plug_ruby')
   1016     redraw!
   1017     return s:warn('echom', 'Warning: Ruby interface is broken')
   1018   endif
   1019   let ruby_version = split(g:plug_ruby, '\.')
   1020   unlet g:plug_ruby
   1021   return s:version_requirement(ruby_version, [1, 8, 7])
   1022 endfunction
   1023 
   1024 function! s:update_impl(pull, force, args) abort
   1025   let sync = index(a:args, '--sync') >= 0 || has('vim_starting')
   1026   let args = filter(copy(a:args), 'v:val != "--sync"')
   1027   let threads = (len(args) > 0 && args[-1] =~ '^[1-9][0-9]*$') ?
   1028                   \ remove(args, -1) : get(g:, 'plug_threads', 16)
   1029 
   1030   let managed = filter(copy(g:plugs), 's:is_managed(v:key)')
   1031   let todo = empty(args) ? filter(managed, '!v:val.frozen || !isdirectory(v:val.dir)') :
   1032                          \ filter(managed, 'index(args, v:key) >= 0')
   1033 
   1034   if empty(todo)
   1035     return s:warn('echo', 'No plugin to '. (a:pull ? 'update' : 'install'))
   1036   endif
   1037 
   1038   if !s:is_win && s:git_version_requirement(2, 3)
   1039     let s:git_terminal_prompt = exists('$GIT_TERMINAL_PROMPT') ? $GIT_TERMINAL_PROMPT : ''
   1040     let $GIT_TERMINAL_PROMPT = 0
   1041     for plug in values(todo)
   1042       let plug.uri = substitute(plug.uri,
   1043             \ '^https://git::@github\.com', 'https://github.com', '')
   1044     endfor
   1045   endif
   1046 
   1047   if !isdirectory(g:plug_home)
   1048     try
   1049       call mkdir(g:plug_home, 'p')
   1050     catch
   1051       return s:err(printf('Invalid plug directory: %s. '.
   1052               \ 'Try to call plug#begin with a valid directory', g:plug_home))
   1053     endtry
   1054   endif
   1055 
   1056   if has('nvim') && !exists('*jobwait') && threads > 1
   1057     call s:warn('echom', '[vim-plug] Update Neovim for parallel installer')
   1058   endif
   1059 
   1060   let use_job = s:nvim || s:vim8
   1061   let python = (has('python') || has('python3')) && !use_job
   1062   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()
   1063 
   1064   let s:update = {
   1065     \ 'start':   reltime(),
   1066     \ 'all':     todo,
   1067     \ 'todo':    copy(todo),
   1068     \ 'errors':  [],
   1069     \ 'pull':    a:pull,
   1070     \ 'force':   a:force,
   1071     \ 'new':     {},
   1072     \ 'threads': (python || ruby || use_job) ? min([len(todo), threads]) : 1,
   1073     \ 'bar':     '',
   1074     \ 'fin':     0
   1075   \ }
   1076 
   1077   call s:prepare(1)
   1078   call append(0, ['', ''])
   1079   normal! 2G
   1080   silent! redraw
   1081 
   1082   let s:clone_opt = get(g:, 'plug_shallow', 1) ?
   1083         \ '--depth 1' . (s:git_version_requirement(1, 7, 10) ? ' --no-single-branch' : '') : ''
   1084 
   1085   if has('win32unix') || has('wsl')
   1086     let s:clone_opt .= ' -c core.eol=lf -c core.autocrlf=input'
   1087   endif
   1088 
   1089   let s:submodule_opt = s:git_version_requirement(2, 8) ? ' --jobs='.threads : ''
   1090 
   1091   " Python version requirement (>= 2.7)
   1092   if python && !has('python3') && !ruby && !use_job && s:update.threads > 1
   1093     redir => pyv
   1094     silent python import platform; print platform.python_version()
   1095     redir END
   1096     let python = s:version_requirement(
   1097           \ map(split(split(pyv)[0], '\.'), 'str2nr(v:val)'), [2, 6])
   1098   endif
   1099 
   1100   if (python || ruby) && s:update.threads > 1
   1101     try
   1102       let imd = &imd
   1103       if s:mac_gui
   1104         set noimd
   1105       endif
   1106       if ruby
   1107         call s:update_ruby()
   1108       else
   1109         call s:update_python()
   1110       endif
   1111     catch
   1112       let lines = getline(4, '$')
   1113       let printed = {}
   1114       silent! 4,$d _
   1115       for line in lines
   1116         let name = s:extract_name(line, '.', '')
   1117         if empty(name) || !has_key(printed, name)
   1118           call append('$', line)
   1119           if !empty(name)
   1120             let printed[name] = 1
   1121             if line[0] == 'x' && index(s:update.errors, name) < 0
   1122               call add(s:update.errors, name)
   1123             end
   1124           endif
   1125         endif
   1126       endfor
   1127     finally
   1128       let &imd = imd
   1129       call s:update_finish()
   1130     endtry
   1131   else
   1132     call s:update_vim()
   1133     while use_job && sync
   1134       sleep 100m
   1135       if s:update.fin
   1136         break
   1137       endif
   1138     endwhile
   1139   endif
   1140 endfunction
   1141 
   1142 function! s:log4(name, msg)
   1143   call setline(4, printf('- %s (%s)', a:msg, a:name))
   1144   redraw
   1145 endfunction
   1146 
   1147 function! s:update_finish()
   1148   if exists('s:git_terminal_prompt')
   1149     let $GIT_TERMINAL_PROMPT = s:git_terminal_prompt
   1150   endif
   1151   if s:switch_in()
   1152     call append(3, '- Updating ...') | 4
   1153     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))'))
   1154       let [pos, _] = s:logpos(name)
   1155       if !pos
   1156         continue
   1157       endif
   1158       if has_key(spec, 'commit')
   1159         call s:log4(name, 'Checking out '.spec.commit)
   1160         let out = s:checkout(spec)
   1161       elseif has_key(spec, 'tag')
   1162         let tag = spec.tag
   1163         if tag =~ '\*'
   1164           let tags = s:lines(s:system('git tag --list '.plug#shellescape(tag).' --sort -version:refname 2>&1', spec.dir))
   1165           if !v:shell_error && !empty(tags)
   1166             let tag = tags[0]
   1167             call s:log4(name, printf('Latest tag for %s -> %s', spec.tag, tag))
   1168             call append(3, '')
   1169           endif
   1170         endif
   1171         call s:log4(name, 'Checking out '.tag)
   1172         let out = s:system('git checkout -q '.plug#shellescape(tag).' -- 2>&1', spec.dir)
   1173       else
   1174         let branch = get(spec, 'branch', 'master')
   1175         call s:log4(name, 'Merging origin/'.s:esc(branch))
   1176         let out = s:system('git checkout -q '.plug#shellescape(branch).' -- 2>&1'
   1177               \. (has_key(s:update.new, name) ? '' : ('&& git merge --ff-only '.plug#shellescape('origin/'.branch).' 2>&1')), spec.dir)
   1178       endif
   1179       if !v:shell_error && filereadable(spec.dir.'/.gitmodules') &&
   1180             \ (s:update.force || has_key(s:update.new, name) || s:is_updated(spec.dir))
   1181         call s:log4(name, 'Updating submodules. This may take a while.')
   1182         let out .= s:bang('git submodule update --init --recursive'.s:submodule_opt.' 2>&1', spec.dir)
   1183       endif
   1184       let msg = s:format_message(v:shell_error ? 'x': '-', name, out)
   1185       if v:shell_error
   1186         call add(s:update.errors, name)
   1187         call s:regress_bar()
   1188         silent execute pos 'd _'
   1189         call append(4, msg) | 4
   1190       elseif !empty(out)
   1191         call setline(pos, msg[0])
   1192       endif
   1193       redraw
   1194     endfor
   1195     silent 4 d _
   1196     try
   1197       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")'))
   1198     catch
   1199       call s:warn('echom', v:exception)
   1200       call s:warn('echo', '')
   1201       return
   1202     endtry
   1203     call s:finish(s:update.pull)
   1204     call setline(1, 'Updated. Elapsed time: ' . split(reltimestr(reltime(s:update.start)))[0] . ' sec.')
   1205     call s:switch_out('normal! gg')
   1206   endif
   1207 endfunction
   1208 
   1209 function! s:job_abort()
   1210   if (!s:nvim && !s:vim8) || !exists('s:jobs')
   1211     return
   1212   endif
   1213 
   1214   for [name, j] in items(s:jobs)
   1215     if s:nvim
   1216       silent! call jobstop(j.jobid)
   1217     elseif s:vim8
   1218       silent! call job_stop(j.jobid)
   1219     endif
   1220     if j.new
   1221       call s:rm_rf(g:plugs[name].dir)
   1222     endif
   1223   endfor
   1224   let s:jobs = {}
   1225 endfunction
   1226 
   1227 function! s:last_non_empty_line(lines)
   1228   let len = len(a:lines)
   1229   for idx in range(len)
   1230     let line = a:lines[len-idx-1]
   1231     if !empty(line)
   1232       return line
   1233     endif
   1234   endfor
   1235   return ''
   1236 endfunction
   1237 
   1238 function! s:job_out_cb(self, data) abort
   1239   let self = a:self
   1240   let data = remove(self.lines, -1) . a:data
   1241   let lines = map(split(data, "\n", 1), 'split(v:val, "\r", 1)[-1]')
   1242   call extend(self.lines, lines)
   1243   " To reduce the number of buffer updates
   1244   let self.tick = get(self, 'tick', -1) + 1
   1245   if !self.running || self.tick % len(s:jobs) == 0
   1246     let bullet = self.running ? (self.new ? '+' : '*') : (self.error ? 'x' : '-')
   1247     let result = self.error ? join(self.lines, "\n") : s:last_non_empty_line(self.lines)
   1248     call s:log(bullet, self.name, result)
   1249   endif
   1250 endfunction
   1251 
   1252 function! s:job_exit_cb(self, data) abort
   1253   let a:self.running = 0
   1254   let a:self.error = a:data != 0
   1255   call s:reap(a:self.name)
   1256   call s:tick()
   1257 endfunction
   1258 
   1259 function! s:job_cb(fn, job, ch, data)
   1260   if !s:plug_window_exists() " plug window closed
   1261     return s:job_abort()
   1262   endif
   1263   call call(a:fn, [a:job, a:data])
   1264 endfunction
   1265 
   1266 function! s:nvim_cb(job_id, data, event) dict abort
   1267   return a:event == 'stdout' ?
   1268     \ s:job_cb('s:job_out_cb',  self, 0, join(a:data, "\n")) :
   1269     \ s:job_cb('s:job_exit_cb', self, 0, a:data)
   1270 endfunction
   1271 
   1272 function! s:spawn(name, cmd, opts)
   1273   let job = { 'name': a:name, 'running': 1, 'error': 0, 'lines': [''],
   1274             \ 'new': get(a:opts, 'new', 0) }
   1275   let s:jobs[a:name] = job
   1276   let cmd = has_key(a:opts, 'dir') ? s:with_cd(a:cmd, a:opts.dir, 0) : a:cmd
   1277   let argv = s:is_win ? ['cmd', '/s', '/c', '"'.cmd.'"'] : ['sh', '-c', cmd]
   1278 
   1279   if s:nvim
   1280     call extend(job, {
   1281     \ 'on_stdout': function('s:nvim_cb'),
   1282     \ 'on_exit':   function('s:nvim_cb'),
   1283     \ })
   1284     let jid = s:plug_call('jobstart', argv, job)
   1285     if jid > 0
   1286       let job.jobid = jid
   1287     else
   1288       let job.running = 0
   1289       let job.error   = 1
   1290       let job.lines   = [jid < 0 ? argv[0].' is not executable' :
   1291             \ 'Invalid arguments (or job table is full)']
   1292     endif
   1293   elseif s:vim8
   1294     let jid = job_start(s:is_win ? join(argv, ' ') : argv, {
   1295     \ 'out_cb':   function('s:job_cb', ['s:job_out_cb',  job]),
   1296     \ 'exit_cb':  function('s:job_cb', ['s:job_exit_cb', job]),
   1297     \ 'out_mode': 'raw'
   1298     \})
   1299     if job_status(jid) == 'run'
   1300       let job.jobid = jid
   1301     else
   1302       let job.running = 0
   1303       let job.error   = 1
   1304       let job.lines   = ['Failed to start job']
   1305     endif
   1306   else
   1307     let job.lines = s:lines(call('s:system', [cmd]))
   1308     let job.error = v:shell_error != 0
   1309     let job.running = 0
   1310   endif
   1311 endfunction
   1312 
   1313 function! s:reap(name)
   1314   let job = s:jobs[a:name]
   1315   if job.error
   1316     call add(s:update.errors, a:name)
   1317   elseif get(job, 'new', 0)
   1318     let s:update.new[a:name] = 1
   1319   endif
   1320   let s:update.bar .= job.error ? 'x' : '='
   1321 
   1322   let bullet = job.error ? 'x' : '-'
   1323   let result = job.error ? join(job.lines, "\n") : s:last_non_empty_line(job.lines)
   1324   call s:log(bullet, a:name, empty(result) ? 'OK' : result)
   1325   call s:bar()
   1326 
   1327   call remove(s:jobs, a:name)
   1328 endfunction
   1329 
   1330 function! s:bar()
   1331   if s:switch_in()
   1332     let total = len(s:update.all)
   1333     call setline(1, (s:update.pull ? 'Updating' : 'Installing').
   1334           \ ' plugins ('.len(s:update.bar).'/'.total.')')
   1335     call s:progress_bar(2, s:update.bar, total)
   1336     call s:switch_out()
   1337   endif
   1338 endfunction
   1339 
   1340 function! s:logpos(name)
   1341   let max = line('$')
   1342   for i in range(4, max > 4 ? max : 4)
   1343     if getline(i) =~# '^[-+x*] '.a:name.':'
   1344       for j in range(i + 1, max > 5 ? max : 5)
   1345         if getline(j) !~ '^ '
   1346           return [i, j - 1]
   1347         endif
   1348       endfor
   1349       return [i, i]
   1350     endif
   1351   endfor
   1352   return [0, 0]
   1353 endfunction
   1354 
   1355 function! s:log(bullet, name, lines)
   1356   if s:switch_in()
   1357     let [b, e] = s:logpos(a:name)
   1358     if b > 0
   1359       silent execute printf('%d,%d d _', b, e)
   1360       if b > winheight('.')
   1361         let b = 4
   1362       endif
   1363     else
   1364       let b = 4
   1365     endif
   1366     " FIXME For some reason, nomodifiable is set after :d in vim8
   1367     setlocal modifiable
   1368     call append(b - 1, s:format_message(a:bullet, a:name, a:lines))
   1369     call s:switch_out()
   1370   endif
   1371 endfunction
   1372 
   1373 function! s:update_vim()
   1374   let s:jobs = {}
   1375 
   1376   call s:bar()
   1377   call s:tick()
   1378 endfunction
   1379 
   1380 function! s:tick()
   1381   let pull = s:update.pull
   1382   let prog = s:progress_opt(s:nvim || s:vim8)
   1383 while 1 " Without TCO, Vim stack is bound to explode
   1384   if empty(s:update.todo)
   1385     if empty(s:jobs) && !s:update.fin
   1386       call s:update_finish()
   1387       let s:update.fin = 1
   1388     endif
   1389     return
   1390   endif
   1391 
   1392   let name = keys(s:update.todo)[0]
   1393   let spec = remove(s:update.todo, name)
   1394   let new  = empty(globpath(spec.dir, '.git', 1))
   1395 
   1396   call s:log(new ? '+' : '*', name, pull ? 'Updating ...' : 'Installing ...')
   1397   redraw
   1398 
   1399   let has_tag = has_key(spec, 'tag')
   1400   if !new
   1401     let [error, _] = s:git_validate(spec, 0)
   1402     if empty(error)
   1403       if pull
   1404         let fetch_opt = (has_tag && !empty(globpath(spec.dir, '.git/shallow'))) ? '--depth 99999999' : ''
   1405         call s:spawn(name, printf('git fetch %s %s 2>&1', fetch_opt, prog), { 'dir': spec.dir })
   1406       else
   1407         let s:jobs[name] = { 'running': 0, 'lines': ['Already installed'], 'error': 0 }
   1408       endif
   1409     else
   1410       let s:jobs[name] = { 'running': 0, 'lines': s:lines(error), 'error': 1 }
   1411     endif
   1412   else
   1413     call s:spawn(name,
   1414           \ printf('git clone %s %s %s %s 2>&1',
   1415           \ has_tag ? '' : s:clone_opt,
   1416           \ prog,
   1417           \ plug#shellescape(spec.uri, {'script': 0}),
   1418           \ plug#shellescape(s:trim(spec.dir), {'script': 0})), { 'new': 1 })
   1419   endif
   1420 
   1421   if !s:jobs[name].running
   1422     call s:reap(name)
   1423   endif
   1424   if len(s:jobs) >= s:update.threads
   1425     break
   1426   endif
   1427 endwhile
   1428 endfunction
   1429 
   1430 function! s:update_python()
   1431 let py_exe = has('python') ? 'python' : 'python3'
   1432 execute py_exe "<< EOF"
   1433 import datetime
   1434 import functools
   1435 import os
   1436 try:
   1437   import queue
   1438 except ImportError:
   1439   import Queue as queue
   1440 import random
   1441 import re
   1442 import shutil
   1443 import signal
   1444 import subprocess
   1445 import tempfile
   1446 import threading as thr
   1447 import time
   1448 import traceback
   1449 import vim
   1450 
   1451 G_NVIM = vim.eval("has('nvim')") == '1'
   1452 G_PULL = vim.eval('s:update.pull') == '1'
   1453 G_RETRIES = int(vim.eval('get(g:, "plug_retries", 2)')) + 1
   1454 G_TIMEOUT = int(vim.eval('get(g:, "plug_timeout", 60)'))
   1455 G_CLONE_OPT = vim.eval('s:clone_opt')
   1456 G_PROGRESS = vim.eval('s:progress_opt(1)')
   1457 G_LOG_PROB = 1.0 / int(vim.eval('s:update.threads'))
   1458 G_STOP = thr.Event()
   1459 G_IS_WIN = vim.eval('s:is_win') == '1'
   1460 
   1461 class PlugError(Exception):
   1462   def __init__(self, msg):
   1463     self.msg = msg
   1464 class CmdTimedOut(PlugError):
   1465   pass
   1466 class CmdFailed(PlugError):
   1467   pass
   1468 class InvalidURI(PlugError):
   1469   pass
   1470 class Action(object):
   1471   INSTALL, UPDATE, ERROR, DONE = ['+', '*', 'x', '-']
   1472 
   1473 class Buffer(object):
   1474   def __init__(self, lock, num_plugs, is_pull):
   1475     self.bar = ''
   1476     self.event = 'Updating' if is_pull else 'Installing'
   1477     self.lock = lock
   1478     self.maxy = int(vim.eval('winheight(".")'))
   1479     self.num_plugs = num_plugs
   1480 
   1481   def __where(self, name):
   1482     """ Find first line with name in current buffer. Return line num. """
   1483     found, lnum = False, 0
   1484     matcher = re.compile('^[-+x*] {0}:'.format(name))
   1485     for line in vim.current.buffer:
   1486       if matcher.search(line) is not None:
   1487         found = True
   1488         break
   1489       lnum += 1
   1490 
   1491     if not found:
   1492       lnum = -1
   1493     return lnum
   1494 
   1495   def header(self):
   1496     curbuf = vim.current.buffer
   1497     curbuf[0] = self.event + ' plugins ({0}/{1})'.format(len(self.bar), self.num_plugs)
   1498 
   1499     num_spaces = self.num_plugs - len(self.bar)
   1500     curbuf[1] = '[{0}{1}]'.format(self.bar, num_spaces * ' ')
   1501 
   1502     with self.lock:
   1503       vim.command('normal! 2G')
   1504       vim.command('redraw')
   1505 
   1506   def write(self, action, name, lines):
   1507     first, rest = lines[0], lines[1:]
   1508     msg = ['{0} {1}{2}{3}'.format(action, name, ': ' if first else '', first)]
   1509     msg.extend(['    ' + line for line in rest])
   1510 
   1511     try:
   1512       if action == Action.ERROR:
   1513         self.bar += 'x'
   1514         vim.command("call add(s:update.errors, '{0}')".format(name))
   1515       elif action == Action.DONE:
   1516         self.bar += '='
   1517 
   1518       curbuf = vim.current.buffer
   1519       lnum = self.__where(name)
   1520       if lnum != -1: # Found matching line num
   1521         del curbuf[lnum]
   1522         if lnum > self.maxy and action in set([Action.INSTALL, Action.UPDATE]):
   1523           lnum = 3
   1524       else:
   1525         lnum = 3
   1526       curbuf.append(msg, lnum)
   1527 
   1528       self.header()
   1529     except vim.error:
   1530       pass
   1531 
   1532 class Command(object):
   1533   CD = 'cd /d' if G_IS_WIN else 'cd'
   1534 
   1535   def __init__(self, cmd, cmd_dir=None, timeout=60, cb=None, clean=None):
   1536     self.cmd = cmd
   1537     if cmd_dir:
   1538       self.cmd = '{0} {1} && {2}'.format(Command.CD, cmd_dir, self.cmd)
   1539     self.timeout = timeout
   1540     self.callback = cb if cb else (lambda msg: None)
   1541     self.clean = clean if clean else (lambda: None)
   1542     self.proc = None
   1543 
   1544   @property
   1545   def alive(self):
   1546     """ Returns true only if command still running. """
   1547     return self.proc and self.proc.poll() is None
   1548 
   1549   def execute(self, ntries=3):
   1550     """ Execute the command with ntries if CmdTimedOut.
   1551         Returns the output of the command if no Exception.
   1552     """
   1553     attempt, finished, limit = 0, False, self.timeout
   1554 
   1555     while not finished:
   1556       try:
   1557         attempt += 1
   1558         result = self.try_command()
   1559         finished = True
   1560         return result
   1561       except CmdTimedOut:
   1562         if attempt != ntries:
   1563           self.notify_retry()
   1564           self.timeout += limit
   1565         else:
   1566           raise
   1567 
   1568   def notify_retry(self):
   1569     """ Retry required for command, notify user. """
   1570     for count in range(3, 0, -1):
   1571       if G_STOP.is_set():
   1572         raise KeyboardInterrupt
   1573       msg = 'Timeout. Will retry in {0} second{1} ...'.format(
   1574             count, 's' if count != 1 else '')
   1575       self.callback([msg])
   1576       time.sleep(1)
   1577     self.callback(['Retrying ...'])
   1578 
   1579   def try_command(self):
   1580     """ Execute a cmd & poll for callback. Returns list of output.
   1581         Raises CmdFailed   -> return code for Popen isn't 0
   1582         Raises CmdTimedOut -> command exceeded timeout without new output
   1583     """
   1584     first_line = True
   1585 
   1586     try:
   1587       tfile = tempfile.NamedTemporaryFile(mode='w+b')
   1588       preexec_fn = not G_IS_WIN and os.setsid or None
   1589       self.proc = subprocess.Popen(self.cmd, stdout=tfile,
   1590                                    stderr=subprocess.STDOUT,
   1591                                    stdin=subprocess.PIPE, shell=True,
   1592                                    preexec_fn=preexec_fn)
   1593       thrd = thr.Thread(target=(lambda proc: proc.wait()), args=(self.proc,))
   1594       thrd.start()
   1595 
   1596       thread_not_started = True
   1597       while thread_not_started:
   1598         try:
   1599           thrd.join(0.1)
   1600           thread_not_started = False
   1601         except RuntimeError:
   1602           pass
   1603 
   1604       while self.alive:
   1605         if G_STOP.is_set():
   1606           raise KeyboardInterrupt
   1607 
   1608         if first_line or random.random() < G_LOG_PROB:
   1609           first_line = False
   1610           line = '' if G_IS_WIN else nonblock_read(tfile.name)
   1611           if line:
   1612             self.callback([line])
   1613 
   1614         time_diff = time.time() - os.path.getmtime(tfile.name)
   1615         if time_diff > self.timeout:
   1616           raise CmdTimedOut(['Timeout!'])
   1617 
   1618         thrd.join(0.5)
   1619 
   1620       tfile.seek(0)
   1621       result = [line.decode('utf-8', 'replace').rstrip() for line in tfile]
   1622 
   1623       if self.proc.returncode != 0:
   1624         raise CmdFailed([''] + result)
   1625 
   1626       return result
   1627     except:
   1628       self.terminate()
   1629       raise
   1630 
   1631   def terminate(self):
   1632     """ Terminate process and cleanup. """
   1633     if self.alive:
   1634       if G_IS_WIN:
   1635         os.kill(self.proc.pid, signal.SIGINT)
   1636       else:
   1637         os.killpg(self.proc.pid, signal.SIGTERM)
   1638     self.clean()
   1639 
   1640 class Plugin(object):
   1641   def __init__(self, name, args, buf_q, lock):
   1642     self.name = name
   1643     self.args = args
   1644     self.buf_q = buf_q
   1645     self.lock = lock
   1646     self.tag = args.get('tag', 0)
   1647 
   1648   def manage(self):
   1649     try:
   1650       if os.path.exists(self.args['dir']):
   1651         self.update()
   1652       else:
   1653         self.install()
   1654         with self.lock:
   1655           thread_vim_command("let s:update.new['{0}'] = 1".format(self.name))
   1656     except PlugError as exc:
   1657       self.write(Action.ERROR, self.name, exc.msg)
   1658     except KeyboardInterrupt:
   1659       G_STOP.set()
   1660       self.write(Action.ERROR, self.name, ['Interrupted!'])
   1661     except:
   1662       # Any exception except those above print stack trace
   1663       msg = 'Trace:\n{0}'.format(traceback.format_exc().rstrip())
   1664       self.write(Action.ERROR, self.name, msg.split('\n'))
   1665       raise
   1666 
   1667   def install(self):
   1668     target = self.args['dir']
   1669     if target[-1] == '\\':
   1670       target = target[0:-1]
   1671 
   1672     def clean(target):
   1673       def _clean():
   1674         try:
   1675           shutil.rmtree(target)
   1676         except OSError:
   1677           pass
   1678       return _clean
   1679 
   1680     self.write(Action.INSTALL, self.name, ['Installing ...'])
   1681     callback = functools.partial(self.write, Action.INSTALL, self.name)
   1682     cmd = 'git clone {0} {1} {2} {3} 2>&1'.format(
   1683           '' if self.tag else G_CLONE_OPT, G_PROGRESS, self.args['uri'],
   1684           esc(target))
   1685     com = Command(cmd, None, G_TIMEOUT, callback, clean(target))
   1686     result = com.execute(G_RETRIES)
   1687     self.write(Action.DONE, self.name, result[-1:])
   1688 
   1689   def repo_uri(self):
   1690     cmd = 'git rev-parse --abbrev-ref HEAD 2>&1 && git config -f .git/config remote.origin.url'
   1691     command = Command(cmd, self.args['dir'], G_TIMEOUT,)
   1692     result = command.execute(G_RETRIES)
   1693     return result[-1]
   1694 
   1695   def update(self):
   1696     actual_uri = self.repo_uri()
   1697     expect_uri = self.args['uri']
   1698     regex = re.compile(r'^(?:\w+://)?(?:[^@/]*@)?([^:/]*(?::[0-9]*)?)[:/](.*?)(?:\.git)?/?$')
   1699     ma = regex.match(actual_uri)
   1700     mb = regex.match(expect_uri)
   1701     if ma is None or mb is None or ma.groups() != mb.groups():
   1702       msg = ['',
   1703              'Invalid URI: {0}'.format(actual_uri),
   1704              'Expected     {0}'.format(expect_uri),
   1705              'PlugClean required.']
   1706       raise InvalidURI(msg)
   1707 
   1708     if G_PULL:
   1709       self.write(Action.UPDATE, self.name, ['Updating ...'])
   1710       callback = functools.partial(self.write, Action.UPDATE, self.name)
   1711       fetch_opt = '--depth 99999999' if self.tag and os.path.isfile(os.path.join(self.args['dir'], '.git/shallow')) else ''
   1712       cmd = 'git fetch {0} {1} 2>&1'.format(fetch_opt, G_PROGRESS)
   1713       com = Command(cmd, self.args['dir'], G_TIMEOUT, callback)
   1714       result = com.execute(G_RETRIES)
   1715       self.write(Action.DONE, self.name, result[-1:])
   1716     else:
   1717       self.write(Action.DONE, self.name, ['Already installed'])
   1718 
   1719   def write(self, action, name, msg):
   1720     self.buf_q.put((action, name, msg))
   1721 
   1722 class PlugThread(thr.Thread):
   1723   def __init__(self, tname, args):
   1724     super(PlugThread, self).__init__()
   1725     self.tname = tname
   1726     self.args = args
   1727 
   1728   def run(self):
   1729     thr.current_thread().name = self.tname
   1730     buf_q, work_q, lock = self.args
   1731 
   1732     try:
   1733       while not G_STOP.is_set():
   1734         name, args = work_q.get_nowait()
   1735         plug = Plugin(name, args, buf_q, lock)
   1736         plug.manage()
   1737         work_q.task_done()
   1738     except queue.Empty:
   1739       pass
   1740 
   1741 class RefreshThread(thr.Thread):
   1742   def __init__(self, lock):
   1743     super(RefreshThread, self).__init__()
   1744     self.lock = lock
   1745     self.running = True
   1746 
   1747   def run(self):
   1748     while self.running:
   1749       with self.lock:
   1750         thread_vim_command('noautocmd normal! a')
   1751       time.sleep(0.33)
   1752 
   1753   def stop(self):
   1754     self.running = False
   1755 
   1756 if G_NVIM:
   1757   def thread_vim_command(cmd):
   1758     vim.session.threadsafe_call(lambda: vim.command(cmd))
   1759 else:
   1760   def thread_vim_command(cmd):
   1761     vim.command(cmd)
   1762 
   1763 def esc(name):
   1764   return '"' + name.replace('"', '\"') + '"'
   1765 
   1766 def nonblock_read(fname):
   1767   """ Read a file with nonblock flag. Return the last line. """
   1768   fread = os.open(fname, os.O_RDONLY | os.O_NONBLOCK)
   1769   buf = os.read(fread, 100000).decode('utf-8', 'replace')
   1770   os.close(fread)
   1771 
   1772   line = buf.rstrip('\r\n')
   1773   left = max(line.rfind('\r'), line.rfind('\n'))
   1774   if left != -1:
   1775     left += 1
   1776     line = line[left:]
   1777 
   1778   return line
   1779 
   1780 def main():
   1781   thr.current_thread().name = 'main'
   1782   nthreads = int(vim.eval('s:update.threads'))
   1783   plugs = vim.eval('s:update.todo')
   1784   mac_gui = vim.eval('s:mac_gui') == '1'
   1785 
   1786   lock = thr.Lock()
   1787   buf = Buffer(lock, len(plugs), G_PULL)
   1788   buf_q, work_q = queue.Queue(), queue.Queue()
   1789   for work in plugs.items():
   1790     work_q.put(work)
   1791 
   1792   start_cnt = thr.active_count()
   1793   for num in range(nthreads):
   1794     tname = 'PlugT-{0:02}'.format(num)
   1795     thread = PlugThread(tname, (buf_q, work_q, lock))
   1796     thread.start()
   1797   if mac_gui:
   1798     rthread = RefreshThread(lock)
   1799     rthread.start()
   1800 
   1801   while not buf_q.empty() or thr.active_count() != start_cnt:
   1802     try:
   1803       action, name, msg = buf_q.get(True, 0.25)
   1804       buf.write(action, name, ['OK'] if not msg else msg)
   1805       buf_q.task_done()
   1806     except queue.Empty:
   1807       pass
   1808     except KeyboardInterrupt:
   1809       G_STOP.set()
   1810 
   1811   if mac_gui:
   1812     rthread.stop()
   1813     rthread.join()
   1814 
   1815 main()
   1816 EOF
   1817 endfunction
   1818 
   1819 function! s:update_ruby()
   1820   ruby << EOF
   1821   module PlugStream
   1822     SEP = ["\r", "\n", nil]
   1823     def get_line
   1824       buffer = ''
   1825       loop do
   1826         char = readchar rescue return
   1827         if SEP.include? char.chr
   1828           buffer << $/
   1829           break
   1830         else
   1831           buffer << char
   1832         end
   1833       end
   1834       buffer
   1835     end
   1836   end unless defined?(PlugStream)
   1837 
   1838   def esc arg
   1839     %["#{arg.gsub('"', '\"')}"]
   1840   end
   1841 
   1842   def killall pid
   1843     pids = [pid]
   1844     if /mswin|mingw|bccwin/ =~ RUBY_PLATFORM
   1845       pids.each { |pid| Process.kill 'INT', pid.to_i rescue nil }
   1846     else
   1847       unless `which pgrep 2> /dev/null`.empty?
   1848         children = pids
   1849         until children.empty?
   1850           children = children.map { |pid|
   1851             `pgrep -P #{pid}`.lines.map { |l| l.chomp }
   1852           }.flatten
   1853           pids += children
   1854         end
   1855       end
   1856       pids.each { |pid| Process.kill 'TERM', pid.to_i rescue nil }
   1857     end
   1858   end
   1859 
   1860   def compare_git_uri a, b
   1861     regex = %r{^(?:\w+://)?(?:[^@/]*@)?([^:/]*(?::[0-9]*)?)[:/](.*?)(?:\.git)?/?$}
   1862     regex.match(a).to_a.drop(1) == regex.match(b).to_a.drop(1)
   1863   end
   1864 
   1865   require 'thread'
   1866   require 'fileutils'
   1867   require 'timeout'
   1868   running = true
   1869   iswin = VIM::evaluate('s:is_win').to_i == 1
   1870   pull  = VIM::evaluate('s:update.pull').to_i == 1
   1871   base  = VIM::evaluate('g:plug_home')
   1872   all   = VIM::evaluate('s:update.todo')
   1873   limit = VIM::evaluate('get(g:, "plug_timeout", 60)')
   1874   tries = VIM::evaluate('get(g:, "plug_retries", 2)') + 1
   1875   nthr  = VIM::evaluate('s:update.threads').to_i
   1876   maxy  = VIM::evaluate('winheight(".")').to_i
   1877   vim7  = VIM::evaluate('v:version').to_i <= 703 && RUBY_PLATFORM =~ /darwin/
   1878   cd    = iswin ? 'cd /d' : 'cd'
   1879   tot   = VIM::evaluate('len(s:update.todo)') || 0
   1880   bar   = ''
   1881   skip  = 'Already installed'
   1882   mtx   = Mutex.new
   1883   take1 = proc { mtx.synchronize { running && all.shift } }
   1884   logh  = proc {
   1885     cnt = bar.length
   1886     $curbuf[1] = "#{pull ? 'Updating' : 'Installing'} plugins (#{cnt}/#{tot})"
   1887     $curbuf[2] = '[' + bar.ljust(tot) + ']'
   1888     VIM::command('normal! 2G')
   1889     VIM::command('redraw')
   1890   }
   1891   where = proc { |name| (1..($curbuf.length)).find { |l| $curbuf[l] =~ /^[-+x*] #{name}:/ } }
   1892   log   = proc { |name, result, type|
   1893     mtx.synchronize do
   1894       ing  = ![true, false].include?(type)
   1895       bar += type ? '=' : 'x' unless ing
   1896       b = case type
   1897           when :install  then '+' when :update then '*'
   1898           when true, nil then '-' else
   1899             VIM::command("call add(s:update.errors, '#{name}')")
   1900             'x'
   1901           end
   1902       result =
   1903         if type || type.nil?
   1904           ["#{b} #{name}: #{result.lines.to_a.last || 'OK'}"]
   1905         elsif result =~ /^Interrupted|^Timeout/
   1906           ["#{b} #{name}: #{result}"]
   1907         else
   1908           ["#{b} #{name}"] + result.lines.map { |l| "    " << l }
   1909         end
   1910       if lnum = where.call(name)
   1911         $curbuf.delete lnum
   1912         lnum = 4 if ing && lnum > maxy
   1913       end
   1914       result.each_with_index do |line, offset|
   1915         $curbuf.append((lnum || 4) - 1 + offset, line.gsub(/\e\[./, '').chomp)
   1916       end
   1917       logh.call
   1918     end
   1919   }
   1920   bt = proc { |cmd, name, type, cleanup|
   1921     tried = timeout = 0
   1922     begin
   1923       tried += 1
   1924       timeout += limit
   1925       fd = nil
   1926       data = ''
   1927       if iswin
   1928         Timeout::timeout(timeout) do
   1929           tmp = VIM::evaluate('tempname()')
   1930           system("(#{cmd}) > #{tmp}")
   1931           data = File.read(tmp).chomp
   1932           File.unlink tmp rescue nil
   1933         end
   1934       else
   1935         fd = IO.popen(cmd).extend(PlugStream)
   1936         first_line = true
   1937         log_prob = 1.0 / nthr
   1938         while line = Timeout::timeout(timeout) { fd.get_line }
   1939           data << line
   1940           log.call name, line.chomp, type if name && (first_line || rand < log_prob)
   1941           first_line = false
   1942         end
   1943         fd.close
   1944       end
   1945       [$? == 0, data.chomp]
   1946     rescue Timeout::Error, Interrupt => e
   1947       if fd && !fd.closed?
   1948         killall fd.pid
   1949         fd.close
   1950       end
   1951       cleanup.call if cleanup
   1952       if e.is_a?(Timeout::Error) && tried < tries
   1953         3.downto(1) do |countdown|
   1954           s = countdown > 1 ? 's' : ''
   1955           log.call name, "Timeout. Will retry in #{countdown} second#{s} ...", type
   1956           sleep 1
   1957         end
   1958         log.call name, 'Retrying ...', type
   1959         retry
   1960       end
   1961       [false, e.is_a?(Interrupt) ? "Interrupted!" : "Timeout!"]
   1962     end
   1963   }
   1964   main = Thread.current
   1965   threads = []
   1966   watcher = Thread.new {
   1967     if vim7
   1968       while VIM::evaluate('getchar(1)')
   1969         sleep 0.1
   1970       end
   1971     else
   1972       require 'io/console' # >= Ruby 1.9
   1973       nil until IO.console.getch == 3.chr
   1974     end
   1975     mtx.synchronize do
   1976       running = false
   1977       threads.each { |t| t.raise Interrupt } unless vim7
   1978     end
   1979     threads.each { |t| t.join rescue nil }
   1980     main.kill
   1981   }
   1982   refresh = Thread.new {
   1983     while true
   1984       mtx.synchronize do
   1985         break unless running
   1986         VIM::command('noautocmd normal! a')
   1987       end
   1988       sleep 0.2
   1989     end
   1990   } if VIM::evaluate('s:mac_gui') == 1
   1991 
   1992   clone_opt = VIM::evaluate('s:clone_opt')
   1993   progress = VIM::evaluate('s:progress_opt(1)')
   1994   nthr.times do
   1995     mtx.synchronize do
   1996       threads << Thread.new {
   1997         while pair = take1.call
   1998           name = pair.first
   1999           dir, uri, tag = pair.last.values_at *%w[dir uri tag]
   2000           exists = File.directory? dir
   2001           ok, result =
   2002             if exists
   2003               chdir = "#{cd} #{iswin ? dir : esc(dir)}"
   2004               ret, data = bt.call "#{chdir} && git rev-parse --abbrev-ref HEAD 2>&1 && git config -f .git/config remote.origin.url", nil, nil, nil
   2005               current_uri = data.lines.to_a.last
   2006               if !ret
   2007                 if data =~ /^Interrupted|^Timeout/
   2008                   [false, data]
   2009                 else
   2010                   [false, [data.chomp, "PlugClean required."].join($/)]
   2011                 end
   2012               elsif !compare_git_uri(current_uri, uri)
   2013                 [false, ["Invalid URI: #{current_uri}",
   2014                          "Expected:    #{uri}",
   2015                          "PlugClean required."].join($/)]
   2016               else
   2017                 if pull
   2018                   log.call name, 'Updating ...', :update
   2019                   fetch_opt = (tag && File.exist?(File.join(dir, '.git/shallow'))) ? '--depth 99999999' : ''
   2020                   bt.call "#{chdir} && git fetch #{fetch_opt} #{progress} 2>&1", name, :update, nil
   2021                 else
   2022                   [true, skip]
   2023                 end
   2024               end
   2025             else
   2026               d = esc dir.sub(%r{[\\/]+$}, '')
   2027               log.call name, 'Installing ...', :install
   2028               bt.call "git clone #{clone_opt unless tag} #{progress} #{uri} #{d} 2>&1", name, :install, proc {
   2029                 FileUtils.rm_rf dir
   2030               }
   2031             end
   2032           mtx.synchronize { VIM::command("let s:update.new['#{name}'] = 1") } if !exists && ok
   2033           log.call name, result, ok
   2034         end
   2035       } if running
   2036     end
   2037   end
   2038   threads.each { |t| t.join rescue nil }
   2039   logh.call
   2040   refresh.kill if refresh
   2041   watcher.kill
   2042 EOF
   2043 endfunction
   2044 
   2045 function! s:shellesc_cmd(arg, script)
   2046   let escaped = substitute('"'.a:arg.'"', '[&|<>()@^!"]', '^&', 'g')
   2047   return substitute(escaped, '%', (a:script ? '%' : '^') . '&', 'g')
   2048 endfunction
   2049 
   2050 function! s:shellesc_ps1(arg)
   2051   return "'".substitute(escape(a:arg, '\"'), "'", "''", 'g')."'"
   2052 endfunction
   2053 
   2054 function! s:shellesc_sh(arg)
   2055   return "'".substitute(a:arg, "'", "'\\\\''", 'g')."'"
   2056 endfunction
   2057 
   2058 function! plug#shellescape(arg, ...)
   2059   let opts = a:0 > 0 && type(a:1) == s:TYPE.dict ? a:1 : {}
   2060   let shell = get(opts, 'shell', s:is_win ? 'cmd.exe' : 'sh')
   2061   let script = get(opts, 'script', 1)
   2062   if shell =~# 'cmd\.exe'
   2063     return s:shellesc_cmd(a:arg, script)
   2064   elseif shell =~# 'powershell\.exe' || shell =~# 'pwsh$'
   2065     return s:shellesc_ps1(a:arg)
   2066   endif
   2067   return s:shellesc_sh(a:arg)
   2068 endfunction
   2069 
   2070 function! s:glob_dir(path)
   2071   return map(filter(s:glob(a:path, '**'), 'isdirectory(v:val)'), 's:dirpath(v:val)')
   2072 endfunction
   2073 
   2074 function! s:progress_bar(line, bar, total)
   2075   call setline(a:line, '[' . s:lpad(a:bar, a:total) . ']')
   2076 endfunction
   2077 
   2078 function! s:compare_git_uri(a, b)
   2079   " See `git help clone'
   2080   " https:// [user@] github.com[:port] / junegunn/vim-plug [.git]
   2081   "          [git@]  github.com[:port] : junegunn/vim-plug [.git]
   2082   " file://                            / junegunn/vim-plug        [/]
   2083   "                                    / junegunn/vim-plug        [/]
   2084   let pat = '^\%(\w\+://\)\='.'\%([^@/]*@\)\='.'\([^:/]*\%(:[0-9]*\)\=\)'.'[:/]'.'\(.\{-}\)'.'\%(\.git\)\=/\?$'
   2085   let ma = matchlist(a:a, pat)
   2086   let mb = matchlist(a:b, pat)
   2087   return ma[1:2] ==# mb[1:2]
   2088 endfunction
   2089 
   2090 function! s:format_message(bullet, name, message)
   2091   if a:bullet != 'x'
   2092     return [printf('%s %s: %s', a:bullet, a:name, s:lastline(a:message))]
   2093   else
   2094     let lines = map(s:lines(a:message), '"    ".v:val')
   2095     return extend([printf('x %s:', a:name)], lines)
   2096   endif
   2097 endfunction
   2098 
   2099 function! s:with_cd(cmd, dir, ...)
   2100   let script = a:0 > 0 ? a:1 : 1
   2101   return printf('cd%s %s && %s', s:is_win ? ' /d' : '', plug#shellescape(a:dir, {'script': script}), a:cmd)
   2102 endfunction
   2103 
   2104 function! s:system(cmd, ...)
   2105   let batchfile = ''
   2106   try
   2107     let [sh, shellcmdflag, shrd] = s:chsh(1)
   2108     let cmd = a:0 > 0 ? s:with_cd(a:cmd, a:1) : a:cmd
   2109     if s:is_win
   2110       let [batchfile, cmd] = s:batchfile(cmd)
   2111     endif
   2112     return system(cmd)
   2113   finally
   2114     let [&shell, &shellcmdflag, &shellredir] = [sh, shellcmdflag, shrd]
   2115     if s:is_win && filereadable(batchfile)
   2116       call delete(batchfile)
   2117     endif
   2118   endtry
   2119 endfunction
   2120 
   2121 function! s:system_chomp(...)
   2122   let ret = call('s:system', a:000)
   2123   return v:shell_error ? '' : substitute(ret, '\n$', '', '')
   2124 endfunction
   2125 
   2126 function! s:git_validate(spec, check_branch)
   2127   let err = ''
   2128   if isdirectory(a:spec.dir)
   2129     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))
   2130     let remote = result[-1]
   2131     if v:shell_error
   2132       let err = join([remote, 'PlugClean required.'], "\n")
   2133     elseif !s:compare_git_uri(remote, a:spec.uri)
   2134       let err = join(['Invalid URI: '.remote,
   2135                     \ 'Expected:    '.a:spec.uri,
   2136                     \ 'PlugClean required.'], "\n")
   2137     elseif a:check_branch && has_key(a:spec, 'commit')
   2138       let result = s:lines(s:system('git rev-parse HEAD 2>&1', a:spec.dir))
   2139       let sha = result[-1]
   2140       if v:shell_error
   2141         let err = join(add(result, 'PlugClean required.'), "\n")
   2142       elseif !s:hash_match(sha, a:spec.commit)
   2143         let err = join([printf('Invalid HEAD (expected: %s, actual: %s)',
   2144                               \ a:spec.commit[:6], sha[:6]),
   2145                       \ 'PlugUpdate required.'], "\n")
   2146       endif
   2147     elseif a:check_branch
   2148       let branch = result[0]
   2149       " Check tag
   2150       if has_key(a:spec, 'tag')
   2151         let tag = s:system_chomp('git describe --exact-match --tags HEAD 2>&1', a:spec.dir)
   2152         if a:spec.tag !=# tag && a:spec.tag !~ '\*'
   2153           let err = printf('Invalid tag: %s (expected: %s). Try PlugUpdate.',
   2154                 \ (empty(tag) ? 'N/A' : tag), a:spec.tag)
   2155         endif
   2156       " Check branch
   2157       elseif a:spec.branch !=# branch
   2158         let err = printf('Invalid branch: %s (expected: %s). Try PlugUpdate.',
   2159               \ branch, a:spec.branch)
   2160       endif
   2161       if empty(err)
   2162         let [ahead, behind] = split(s:lastline(s:system(printf(
   2163               \ 'git rev-list --count --left-right HEAD...origin/%s',
   2164               \ a:spec.branch), a:spec.dir)), '\t')
   2165         if !v:shell_error && ahead
   2166           if behind
   2167             " Only mention PlugClean if diverged, otherwise it's likely to be
   2168             " pushable (and probably not that messed up).
   2169             let err = printf(
   2170                   \ "Diverged from origin/%s (%d commit(s) ahead and %d commit(s) behind!\n"
   2171                   \ .'Backup local changes and run PlugClean and PlugUpdate to reinstall it.', a:spec.branch, ahead, behind)
   2172           else
   2173             let err = printf("Ahead of origin/%s by %d commit(s).\n"
   2174                   \ .'Cannot update until local changes are pushed.',
   2175                   \ a:spec.branch, ahead)
   2176           endif
   2177         endif
   2178       endif
   2179     endif
   2180   else
   2181     let err = 'Not found'
   2182   endif
   2183   return [err, err =~# 'PlugClean']
   2184 endfunction
   2185 
   2186 function! s:rm_rf(dir)
   2187   if isdirectory(a:dir)
   2188     call s:system((s:is_win ? 'rmdir /S /Q ' : 'rm -rf ') . plug#shellescape(a:dir))
   2189   endif
   2190 endfunction
   2191 
   2192 function! s:clean(force)
   2193   call s:prepare()
   2194   call append(0, 'Searching for invalid plugins in '.g:plug_home)
   2195   call append(1, '')
   2196 
   2197   " List of valid directories
   2198   let dirs = []
   2199   let errs = {}
   2200   let [cnt, total] = [0, len(g:plugs)]
   2201   for [name, spec] in items(g:plugs)
   2202     if !s:is_managed(name)
   2203       call add(dirs, spec.dir)
   2204     else
   2205       let [err, clean] = s:git_validate(spec, 1)
   2206       if clean
   2207         let errs[spec.dir] = s:lines(err)[0]
   2208       else
   2209         call add(dirs, spec.dir)
   2210       endif
   2211     endif
   2212     let cnt += 1
   2213     call s:progress_bar(2, repeat('=', cnt), total)
   2214     normal! 2G
   2215     redraw
   2216   endfor
   2217 
   2218   let allowed = {}
   2219   for dir in dirs
   2220     let allowed[s:dirpath(s:plug_fnamemodify(dir, ':h:h'))] = 1
   2221     let allowed[dir] = 1
   2222     for child in s:glob_dir(dir)
   2223       let allowed[child] = 1
   2224     endfor
   2225   endfor
   2226 
   2227   let todo = []
   2228   let found = sort(s:glob_dir(g:plug_home))
   2229   while !empty(found)
   2230     let f = remove(found, 0)
   2231     if !has_key(allowed, f) && isdirectory(f)
   2232       call add(todo, f)
   2233       call append(line('$'), '- ' . f)
   2234       if has_key(errs, f)
   2235         call append(line('$'), '    ' . errs[f])
   2236       endif
   2237       let found = filter(found, 'stridx(v:val, f) != 0')
   2238     end
   2239   endwhile
   2240 
   2241   4
   2242   redraw
   2243   if empty(todo)
   2244     call append(line('$'), 'Already clean.')
   2245   else
   2246     let s:clean_count = 0
   2247     call append(3, ['Directories to delete:', ''])
   2248     redraw!
   2249     if a:force || s:ask_no_interrupt('Delete all directories?')
   2250       call s:delete([6, line('$')], 1)
   2251     else
   2252       call setline(4, 'Cancelled.')
   2253       nnoremap <silent> <buffer> d :set opfunc=<sid>delete_op<cr>g@
   2254       nmap     <silent> <buffer> dd d_
   2255       xnoremap <silent> <buffer> d :<c-u>call <sid>delete_op(visualmode(), 1)<cr>
   2256       echo 'Delete the lines (d{motion}) to delete the corresponding directories'
   2257     endif
   2258   endif
   2259   4
   2260   setlocal nomodifiable
   2261 endfunction
   2262 
   2263 function! s:delete_op(type, ...)
   2264   call s:delete(a:0 ? [line("'<"), line("'>")] : [line("'["), line("']")], 0)
   2265 endfunction
   2266 
   2267 function! s:delete(range, force)
   2268   let [l1, l2] = a:range
   2269   let force = a:force
   2270   while l1 <= l2
   2271     let line = getline(l1)
   2272     if line =~ '^- ' && isdirectory(line[2:])
   2273       execute l1
   2274       redraw!
   2275       let answer = force ? 1 : s:ask('Delete '.line[2:].'?', 1)
   2276       let force = force || answer > 1
   2277       if answer
   2278         call s:rm_rf(line[2:])
   2279         setlocal modifiable
   2280         call setline(l1, '~'.line[1:])
   2281         let s:clean_count += 1
   2282         call setline(4, printf('Removed %d directories.', s:clean_count))
   2283         setlocal nomodifiable
   2284       endif
   2285     endif
   2286     let l1 += 1
   2287   endwhile
   2288 endfunction
   2289 
   2290 function! s:upgrade()
   2291   echo 'Downloading the latest version of vim-plug'
   2292   redraw
   2293   let tmp = s:plug_tempname()
   2294   let new = tmp . '/plug.vim'
   2295 
   2296   try
   2297     let out = s:system(printf('git clone --depth 1 %s %s', plug#shellescape(s:plug_src), plug#shellescape(tmp)))
   2298     if v:shell_error
   2299       return s:err('Error upgrading vim-plug: '. out)
   2300     endif
   2301 
   2302     if readfile(s:me) ==# readfile(new)
   2303       echo 'vim-plug is already up-to-date'
   2304       return 0
   2305     else
   2306       call rename(s:me, s:me . '.old')
   2307       call rename(new, s:me)
   2308       unlet g:loaded_plug
   2309       echo 'vim-plug has been upgraded'
   2310       return 1
   2311     endif
   2312   finally
   2313     silent! call s:rm_rf(tmp)
   2314   endtry
   2315 endfunction
   2316 
   2317 function! s:upgrade_specs()
   2318   for spec in values(g:plugs)
   2319     let spec.frozen = get(spec, 'frozen', 0)
   2320   endfor
   2321 endfunction
   2322 
   2323 function! s:status()
   2324   call s:prepare()
   2325   call append(0, 'Checking plugins')
   2326   call append(1, '')
   2327 
   2328   let ecnt = 0
   2329   let unloaded = 0
   2330   let [cnt, total] = [0, len(g:plugs)]
   2331   for [name, spec] in items(g:plugs)
   2332     let is_dir = isdirectory(spec.dir)
   2333     if has_key(spec, 'uri')
   2334       if is_dir
   2335         let [err, _] = s:git_validate(spec, 1)
   2336         let [valid, msg] = [empty(err), empty(err) ? 'OK' : err]
   2337       else
   2338         let [valid, msg] = [0, 'Not found. Try PlugInstall.']
   2339       endif
   2340     else
   2341       if is_dir
   2342         let [valid, msg] = [1, 'OK']
   2343       else
   2344         let [valid, msg] = [0, 'Not found.']
   2345       endif
   2346     endif
   2347     let cnt += 1
   2348     let ecnt += !valid
   2349     " `s:loaded` entry can be missing if PlugUpgraded
   2350     if is_dir && get(s:loaded, name, -1) == 0
   2351       let unloaded = 1
   2352       let msg .= ' (not loaded)'
   2353     endif
   2354     call s:progress_bar(2, repeat('=', cnt), total)
   2355     call append(3, s:format_message(valid ? '-' : 'x', name, msg))
   2356     normal! 2G
   2357     redraw
   2358   endfor
   2359   call setline(1, 'Finished. '.ecnt.' error(s).')
   2360   normal! gg
   2361   setlocal nomodifiable
   2362   if unloaded
   2363     echo "Press 'L' on each line to load plugin, or 'U' to update"
   2364     nnoremap <silent> <buffer> L :call <SID>status_load(line('.'))<cr>
   2365     xnoremap <silent> <buffer> L :call <SID>status_load(line('.'))<cr>
   2366   end
   2367 endfunction
   2368 
   2369 function! s:extract_name(str, prefix, suffix)
   2370   return matchstr(a:str, '^'.a:prefix.' \zs[^:]\+\ze:.*'.a:suffix.'$')
   2371 endfunction
   2372 
   2373 function! s:status_load(lnum)
   2374   let line = getline(a:lnum)
   2375   let name = s:extract_name(line, '-', '(not loaded)')
   2376   if !empty(name)
   2377     call plug#load(name)
   2378     setlocal modifiable
   2379     call setline(a:lnum, substitute(line, ' (not loaded)$', '', ''))
   2380     setlocal nomodifiable
   2381   endif
   2382 endfunction
   2383 
   2384 function! s:status_update() range
   2385   let lines = getline(a:firstline, a:lastline)
   2386   let names = filter(map(lines, 's:extract_name(v:val, "[x-]", "")'), '!empty(v:val)')
   2387   if !empty(names)
   2388     echo
   2389     execute 'PlugUpdate' join(names)
   2390   endif
   2391 endfunction
   2392 
   2393 function! s:is_preview_window_open()
   2394   silent! wincmd P
   2395   if &previewwindow
   2396     wincmd p
   2397     return 1
   2398   endif
   2399 endfunction
   2400 
   2401 function! s:find_name(lnum)
   2402   for lnum in reverse(range(1, a:lnum))
   2403     let line = getline(lnum)
   2404     if empty(line)
   2405       return ''
   2406     endif
   2407     let name = s:extract_name(line, '-', '')
   2408     if !empty(name)
   2409       return name
   2410     endif
   2411   endfor
   2412   return ''
   2413 endfunction
   2414 
   2415 function! s:preview_commit()
   2416   if b:plug_preview < 0
   2417     let b:plug_preview = !s:is_preview_window_open()
   2418   endif
   2419 
   2420   let sha = matchstr(getline('.'), '^  \X*\zs[0-9a-f]\{7,9}')
   2421   if empty(sha)
   2422     return
   2423   endif
   2424 
   2425   let name = s:find_name(line('.'))
   2426   if empty(name) || !has_key(g:plugs, name) || !isdirectory(g:plugs[name].dir)
   2427     return
   2428   endif
   2429 
   2430   if exists('g:plug_pwindow') && !s:is_preview_window_open()
   2431     execute g:plug_pwindow
   2432     execute 'e' sha
   2433   else
   2434     execute 'pedit' sha
   2435     wincmd P
   2436   endif
   2437   setlocal previewwindow filetype=git buftype=nofile nobuflisted modifiable
   2438   let batchfile = ''
   2439   try
   2440     let [sh, shellcmdflag, shrd] = s:chsh(1)
   2441     let cmd = 'cd '.plug#shellescape(g:plugs[name].dir).' && git show --no-color --pretty=medium '.sha
   2442     if s:is_win
   2443       let [batchfile, cmd] = s:batchfile(cmd)
   2444     endif
   2445     execute 'silent %!' cmd
   2446   finally
   2447     let [&shell, &shellcmdflag, &shellredir] = [sh, shellcmdflag, shrd]
   2448     if s:is_win && filereadable(batchfile)
   2449       call delete(batchfile)
   2450     endif
   2451   endtry
   2452   setlocal nomodifiable
   2453   nnoremap <silent> <buffer> q :q<cr>
   2454   wincmd p
   2455 endfunction
   2456 
   2457 function! s:section(flags)
   2458   call search('\(^[x-] \)\@<=[^:]\+:', a:flags)
   2459 endfunction
   2460 
   2461 function! s:format_git_log(line)
   2462   let indent = '  '
   2463   let tokens = split(a:line, nr2char(1))
   2464   if len(tokens) != 5
   2465     return indent.substitute(a:line, '\s*$', '', '')
   2466   endif
   2467   let [graph, sha, refs, subject, date] = tokens
   2468   let tag = matchstr(refs, 'tag: [^,)]\+')
   2469   let tag = empty(tag) ? ' ' : ' ('.tag.') '
   2470   return printf('%s%s%s%s%s (%s)', indent, graph, sha, tag, subject, date)
   2471 endfunction
   2472 
   2473 function! s:append_ul(lnum, text)
   2474   call append(a:lnum, ['', a:text, repeat('-', len(a:text))])
   2475 endfunction
   2476 
   2477 function! s:diff()
   2478   call s:prepare()
   2479   call append(0, ['Collecting changes ...', ''])
   2480   let cnts = [0, 0]
   2481   let bar = ''
   2482   let total = filter(copy(g:plugs), 's:is_managed(v:key) && isdirectory(v:val.dir)')
   2483   call s:progress_bar(2, bar, len(total))
   2484   for origin in [1, 0]
   2485     let plugs = reverse(sort(items(filter(copy(total), (origin ? '' : '!').'(has_key(v:val, "commit") || has_key(v:val, "tag"))'))))
   2486     if empty(plugs)
   2487       continue
   2488     endif
   2489     call s:append_ul(2, origin ? 'Pending updates:' : 'Last update:')
   2490     for [k, v] in plugs
   2491       let range = origin ? '..origin/'.v.branch : 'HEAD@{1}..'
   2492       let cmd = 'git log --graph --color=never '
   2493       \ . (s:git_version_requirement(2, 10, 0) ? '--no-show-signature ' : '')
   2494       \ . join(map(['--pretty=format:%x01%h%x01%d%x01%s%x01%cr', range], 'plug#shellescape(v:val)'))
   2495       if has_key(v, 'rtp')
   2496         let cmd .= ' -- '.plug#shellescape(v.rtp)
   2497       endif
   2498       let diff = s:system_chomp(cmd, v.dir)
   2499       if !empty(diff)
   2500         let ref = has_key(v, 'tag') ? (' (tag: '.v.tag.')') : has_key(v, 'commit') ? (' '.v.commit) : ''
   2501         call append(5, extend(['', '- '.k.':'.ref], map(s:lines(diff), 's:format_git_log(v:val)')))
   2502         let cnts[origin] += 1
   2503       endif
   2504       let bar .= '='
   2505       call s:progress_bar(2, bar, len(total))
   2506       normal! 2G
   2507       redraw
   2508     endfor
   2509     if !cnts[origin]
   2510       call append(5, ['', 'N/A'])
   2511     endif
   2512   endfor
   2513   call setline(1, printf('%d plugin(s) updated.', cnts[0])
   2514         \ . (cnts[1] ? printf(' %d plugin(s) have pending updates.', cnts[1]) : ''))
   2515 
   2516   if cnts[0] || cnts[1]
   2517     nnoremap <silent> <buffer> <plug>(plug-preview) :silent! call <SID>preview_commit()<cr>
   2518     if empty(maparg("\<cr>", 'n'))
   2519       nmap <buffer> <cr> <plug>(plug-preview)
   2520     endif
   2521     if empty(maparg('o', 'n'))
   2522       nmap <buffer> o <plug>(plug-preview)
   2523     endif
   2524   endif
   2525   if cnts[0]
   2526     nnoremap <silent> <buffer> X :call <SID>revert()<cr>
   2527     echo "Press 'X' on each block to revert the update"
   2528   endif
   2529   normal! gg
   2530   setlocal nomodifiable
   2531 endfunction
   2532 
   2533 function! s:revert()
   2534   if search('^Pending updates', 'bnW')
   2535     return
   2536   endif
   2537 
   2538   let name = s:find_name(line('.'))
   2539   if empty(name) || !has_key(g:plugs, name) ||
   2540     \ input(printf('Revert the update of %s? (y/N) ', name)) !~? '^y'
   2541     return
   2542   endif
   2543 
   2544   call s:system('git reset --hard HEAD@{1} && git checkout '.plug#shellescape(g:plugs[name].branch).' --', g:plugs[name].dir)
   2545   setlocal modifiable
   2546   normal! "_dap
   2547   setlocal nomodifiable
   2548   echo 'Reverted'
   2549 endfunction
   2550 
   2551 function! s:snapshot(force, ...) abort
   2552   call s:prepare()
   2553   setf vim
   2554   call append(0, ['" Generated by vim-plug',
   2555                 \ '" '.strftime("%c"),
   2556                 \ '" :source this file in vim to restore the snapshot',
   2557                 \ '" or execute: vim -S snapshot.vim',
   2558                 \ '', '', 'PlugUpdate!'])
   2559   1
   2560   let anchor = line('$') - 3
   2561   let names = sort(keys(filter(copy(g:plugs),
   2562         \'has_key(v:val, "uri") && !has_key(v:val, "commit") && isdirectory(v:val.dir)')))
   2563   for name in reverse(names)
   2564     let sha = s:system_chomp('git rev-parse --short HEAD', g:plugs[name].dir)
   2565     if !empty(sha)
   2566       call append(anchor, printf("silent! let g:plugs['%s'].commit = '%s'", name, sha))
   2567       redraw
   2568     endif
   2569   endfor
   2570 
   2571   if a:0 > 0
   2572     let fn = s:plug_expand(a:1)
   2573     if filereadable(fn) && !(a:force || s:ask(a:1.' already exists. Overwrite?'))
   2574       return
   2575     endif
   2576     call writefile(getline(1, '$'), fn)
   2577     echo 'Saved as '.a:1
   2578     silent execute 'e' s:esc(fn)
   2579     setf vim
   2580   endif
   2581 endfunction
   2582 
   2583 function! s:split_rtp()
   2584   return split(&rtp, '\\\@<!,')
   2585 endfunction
   2586 
   2587 let s:first_rtp = s:escrtp(get(s:split_rtp(), 0, ''))
   2588 let s:last_rtp  = s:escrtp(get(s:split_rtp(), -1, ''))
   2589 
   2590 if exists('g:plugs')
   2591   let g:plugs_order = get(g:, 'plugs_order', keys(g:plugs))
   2592   call s:upgrade_specs()
   2593   call s:define_commands()
   2594 endif
   2595 
   2596 let &cpo = s:cpo_save
   2597 unlet s:cpo_save