dotfiles

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

plug.vim (82481B)


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