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