dotfiles

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