1 " vim-plug: Vim plugin manager
 
   2 " ============================
 
   4 " Download plug.vim and put it in ~/.vim/autoload
 
   6 "   curl -fLo ~/.vim/autoload/plug.vim --create-dirs \
 
   7 "     https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim
 
  11 "   call plug#begin('~/.vim/plugged')
 
  13 "   " Make sure you use single quotes
 
  15 "   " Shorthand notation; fetches https://github.com/junegunn/vim-easy-align
 
  16 "   Plug 'junegunn/vim-easy-align'
 
  18 "   " Any valid git URL is allowed
 
  19 "   Plug 'https://github.com/junegunn/vim-github-dashboard.git'
 
  21 "   " Multiple Plug commands can be written in a single line using | separators
 
  22 "   Plug 'SirVer/ultisnips' | Plug 'honza/vim-snippets'
 
  25 "   Plug 'scrooloose/nerdtree', { 'on':  'NERDTreeToggle' }
 
  26 "   Plug 'tpope/vim-fireplace', { 'for': 'clojure' }
 
  28 "   " Using a non-master branch
 
  29 "   Plug 'rdnetto/YCM-Generator', { 'branch': 'stable' }
 
  31 "   " Using a tagged release; wildcard allowed (requires git 1.9.2 or above)
 
  32 "   Plug 'fatih/vim-go', { 'tag': '*' }
 
  35 "   Plug 'nsf/gocode', { 'tag': 'v.20150303', 'rtp': 'vim' }
 
  37 "   " Plugin outside ~/.vim/plugged with post-update hook
 
  38 "   Plug 'junegunn/fzf', { 'dir': '~/.fzf', 'do': './install --all' }
 
  40 "   " Unmanaged plugin (manually installed and updated)
 
  41 "   Plug '~/my-prototype-plugin'
 
  43 "   " Initialize plugin system
 
  46 " Then reload .vimrc and :PlugInstall to install plugins.
 
  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        |
 
  61 " More information: https://github.com/junegunn/vim-plug
 
  64 " Copyright (c) 2017 Junegunn Choi
 
  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:
 
  76 " The above copyright notice and this permission notice shall be
 
  77 " included in all copies or substantial portions of the Software.
 
  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.
 
  87 if exists('g:loaded_plug')
 
  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') || has('win64')
 
 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 let s:me = resolve(expand('<sfile>:p'))
 
 103 let s:base_spec = { 'branch': 'master', 'frozen': 0 }
 
 105 \   'string':  type(''),
 
 108 \   'funcref': type(function('call'))
 
 110 let s:loaded = get(s:, 'loaded', {})
 
 111 let s:triggers = get(s:, 'triggers', {})
 
 113 function! plug#begin(...)
 
 115     let s:plug_home_org = a:1
 
 116     let home = s:path(fnamemodify(expand(a:1), ':p'))
 
 117   elseif exists('g:plug_home')
 
 118     let home = s:path(g:plug_home)
 
 120     let home = s:path(split(&rtp, ',')[0]) . '/plugged'
 
 122     return s:err('Unable to determine plug home. Try calling plug#begin() with a path argument.')
 
 124   if fnamemodify(home, ':t') ==# 'plugin' && fnamemodify(home, ':h') ==# s:first_rtp
 
 125     return s:err('Invalid plug home. '.home.' is a standard Vim runtime path and is not allowed.')
 
 128   let g:plug_home = home
 
 130   let g:plugs_order = []
 
 133   call s:define_commands()
 
 137 function! s:define_commands()
 
 138   command! -nargs=+ -bar Plug call plug#(<args>)
 
 139   if !executable('git')
 
 140     return s:err('`git` executable not found. Most commands will not be available. To suppress this message, prepend `silent!` to `call plug#begin(...)`.')
 
 142   command! -nargs=* -bar -bang -complete=customlist,s:names PlugInstall call s:install(<bang>0, [<f-args>])
 
 143   command! -nargs=* -bar -bang -complete=customlist,s:names PlugUpdate  call s:update(<bang>0, [<f-args>])
 
 144   command! -nargs=0 -bar -bang PlugClean call s:clean(<bang>0)
 
 145   command! -nargs=0 -bar PlugUpgrade if s:upgrade() | execute 'source' s:esc(s:me) | endif
 
 146   command! -nargs=0 -bar PlugStatus  call s:status()
 
 147   command! -nargs=0 -bar PlugDiff    call s:diff()
 
 148   command! -nargs=? -bar -bang -complete=file PlugSnapshot call s:snapshot(<bang>0, <f-args>)
 
 152   return type(a:v) == s:TYPE.list ? a:v : [a:v]
 
 156   return type(a:v) == s:TYPE.string ? a:v : join(a:v, "\n") . "\n"
 
 159 function! s:glob(from, pattern)
 
 160   return s:lines(globpath(a:from, a:pattern))
 
 163 function! s:source(from, ...)
 
 166     for vim in s:glob(a:from, pattern)
 
 167       execute 'source' s:esc(vim)
 
 174 function! s:assoc(dict, key, val)
 
 175   let a:dict[a:key] = add(get(a:dict, a:key, []), a:val)
 
 178 function! s:ask(message, ...)
 
 181   let answer = input(a:message.(a:0 ? ' (y/N/a) ' : ' (y/N) '))
 
 185   return (a:0 && answer =~? '^a') ? 2 : (answer =~? '^y') ? 1 : 0
 
 188 function! s:ask_no_interrupt(...)
 
 190     return call('s:ask', a:000)
 
 197   if !exists('g:plugs')
 
 198     return s:err('Call plug#begin() first')
 
 201   if exists('#PlugLOD')
 
 207   let lod = { 'ft': {}, 'map': {}, 'cmd': {} }
 
 209   if exists('g:did_load_filetypes')
 
 212   for name in g:plugs_order
 
 213     if !has_key(g:plugs, name)
 
 216     let plug = g:plugs[name]
 
 217     if get(s:loaded, name, 0) || !has_key(plug, 'on') && !has_key(plug, 'for')
 
 218       let s:loaded[name] = 1
 
 222     if has_key(plug, 'on')
 
 223       let s:triggers[name] = { 'map': [], 'cmd': [] }
 
 224       for cmd in s:to_a(plug.on)
 
 225         if cmd =~? '^<Plug>.\+'
 
 226           if empty(mapcheck(cmd)) && empty(mapcheck(cmd, 'i'))
 
 227             call s:assoc(lod.map, cmd, name)
 
 229           call add(s:triggers[name].map, cmd)
 
 230         elseif cmd =~# '^[A-Z]'
 
 231           let cmd = substitute(cmd, '!*$', '', '')
 
 232           if exists(':'.cmd) != 2
 
 233             call s:assoc(lod.cmd, cmd, name)
 
 235           call add(s:triggers[name].cmd, cmd)
 
 237           call s:err('Invalid `on` option: '.cmd.
 
 238           \ '. Should start with an uppercase letter or `<Plug>`.')
 
 243     if has_key(plug, 'for')
 
 244       let types = s:to_a(plug.for)
 
 246         augroup filetypedetect
 
 247         call s:source(s:rtp(plug), 'ftdetect/**/*.vim', 'after/ftdetect/**/*.vim')
 
 251         call s:assoc(lod.ft, type, name)
 
 256   for [cmd, names] in items(lod.cmd)
 
 258     \ 'command! -nargs=* -range -bang -complete=file %s call s:lod_cmd(%s, "<bang>", <line1>, <line2>, <q-args>, %s)',
 
 259     \ cmd, string(cmd), string(names))
 
 262   for [map, names] in items(lod.map)
 
 263     for [mode, map_prefix, key_prefix] in
 
 264           \ [['i', '<C-O>', ''], ['n', '', ''], ['v', '', 'gv'], ['o', '', '']]
 
 266       \ '%snoremap <silent> %s %s:<C-U>call <SID>lod_map(%s, %s, %s, "%s")<CR>',
 
 267       \ mode, map, map_prefix, string(map), string(names), mode != 'i', key_prefix)
 
 271   for [ft, names] in items(lod.ft)
 
 273       execute printf('autocmd FileType %s call <SID>lod_ft(%s, %s)',
 
 274             \ ft, string(ft), string(names))
 
 279   filetype plugin indent on
 
 280   if has('vim_starting')
 
 281     if has('syntax') && !exists('g:syntax_on')
 
 285     call s:reload_plugins()
 
 289 function! s:loaded_names()
 
 290   return filter(copy(g:plugs_order), 'get(s:loaded, v:val, 0)')
 
 293 function! s:load_plugin(spec)
 
 294   call s:source(s:rtp(a:spec), 'plugin/**/*.vim', 'after/plugin/**/*.vim')
 
 297 function! s:reload_plugins()
 
 298   for name in s:loaded_names()
 
 299     call s:load_plugin(g:plugs[name])
 
 303 function! s:trim(str)
 
 304   return substitute(a:str, '[\/]\+$', '', '')
 
 307 function! s:version_requirement(val, min)
 
 308   for idx in range(0, len(a:min) - 1)
 
 309     let v = get(a:val, idx, 0)
 
 310     if     v < a:min[idx] | return 0
 
 311     elseif v > a:min[idx] | return 1
 
 317 function! s:git_version_requirement(...)
 
 318   if !exists('s:git_version')
 
 319     let s:git_version = map(split(split(s:system('git --version'))[2], '\.'), 'str2nr(v:val)')
 
 321   return s:version_requirement(s:git_version, a:000)
 
 324 function! s:progress_opt(base)
 
 325   return a:base && !s:is_win &&
 
 326         \ s:git_version_requirement(1, 7, 1) ? '--progress' : ''
 
 330   function! s:rtp(spec)
 
 331     return s:path(a:spec.dir . get(a:spec, 'rtp', ''))
 
 334   function! s:path(path)
 
 335     return s:trim(substitute(a:path, '/', '\', 'g'))
 
 338   function! s:dirpath(path)
 
 339     return s:path(a:path) . '\'
 
 342   function! s:is_local_plug(repo)
 
 343     return a:repo =~? '^[a-z]:\|^[%~]'
 
 346   function! s:rtp(spec)
 
 347     return s:dirpath(a:spec.dir . get(a:spec, 'rtp', ''))
 
 350   function! s:path(path)
 
 351     return s:trim(a:path)
 
 354   function! s:dirpath(path)
 
 355     return substitute(a:path, '[/\\]*$', '/', '')
 
 358   function! s:is_local_plug(repo)
 
 359     return a:repo[0] =~ '[/$~]'
 
 365   echom '[vim-plug] '.a:msg
 
 369 function! s:warn(cmd, msg)
 
 371   execute a:cmd 'a:msg'
 
 375 function! s:esc(path)
 
 376   return escape(a:path, ' ')
 
 379 function! s:escrtp(path)
 
 380   return escape(a:path, ' ,')
 
 383 function! s:remove_rtp()
 
 384   for name in s:loaded_names()
 
 385     let rtp = s:rtp(g:plugs[name])
 
 386     execute 'set rtp-='.s:escrtp(rtp)
 
 387     let after = globpath(rtp, 'after')
 
 388     if isdirectory(after)
 
 389       execute 'set rtp-='.s:escrtp(after)
 
 394 function! s:reorg_rtp()
 
 395   if !empty(s:first_rtp)
 
 396     execute 'set rtp-='.s:first_rtp
 
 397     execute 'set rtp-='.s:last_rtp
 
 400   " &rtp is modified from outside
 
 401   if exists('s:prtp') && s:prtp !=# &rtp
 
 406   let s:middle = get(s:, 'middle', &rtp)
 
 407   let rtps     = map(s:loaded_names(), 's:rtp(g:plugs[v:val])')
 
 408   let afters   = filter(map(copy(rtps), 'globpath(v:val, "after")'), '!empty(v:val)')
 
 409   let rtp      = join(map(rtps, 'escape(v:val, ",")'), ',')
 
 411                  \ . join(map(afters, 'escape(v:val, ",")'), ',')
 
 412   let &rtp     = substitute(substitute(rtp, ',,*', ',', 'g'), '^,\|,$', '', 'g')
 
 415   if !empty(s:first_rtp)
 
 416     execute 'set rtp^='.s:first_rtp
 
 417     execute 'set rtp+='.s:last_rtp
 
 421 function! s:doautocmd(...)
 
 422   if exists('#'.join(a:000, '#'))
 
 423     execute 'doautocmd' ((v:version > 703 || has('patch442')) ? '<nomodeline>' : '') join(a:000)
 
 427 function! s:dobufread(names)
 
 429     let path = s:rtp(g:plugs[name]).'/**'
 
 430     for dir in ['ftdetect', 'ftplugin']
 
 431       if len(finddir(dir, path))
 
 432         if exists('#BufRead')
 
 441 function! plug#load(...)
 
 443     return s:err('Argument missing: plugin name(s) required')
 
 445   if !exists('g:plugs')
 
 446     return s:err('plug#begin was not called')
 
 448   let names = a:0 == 1 && type(a:1) == s:TYPE.list ? a:1 : a:000
 
 449   let unknowns = filter(copy(names), '!has_key(g:plugs, v:val)')
 
 451     let s = len(unknowns) > 1 ? 's' : ''
 
 452     return s:err(printf('Unknown plugin%s: %s', s, join(unknowns, ', ')))
 
 454   let unloaded = filter(copy(names), '!get(s:loaded, v:val, 0)')
 
 457       call s:lod([name], ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin'])
 
 459     call s:dobufread(unloaded)
 
 465 function! s:remove_triggers(name)
 
 466   if !has_key(s:triggers, a:name)
 
 469   for cmd in s:triggers[a:name].cmd
 
 470     execute 'silent! delc' cmd
 
 472   for map in s:triggers[a:name].map
 
 473     execute 'silent! unmap' map
 
 474     execute 'silent! iunmap' map
 
 476   call remove(s:triggers, a:name)
 
 479 function! s:lod(names, types, ...)
 
 481     call s:remove_triggers(name)
 
 482     let s:loaded[name] = 1
 
 487     let rtp = s:rtp(g:plugs[name])
 
 489       call s:source(rtp, dir.'/**/*.vim')
 
 492       if !s:source(rtp, a:1) && !empty(s:glob(rtp, a:2))
 
 493         execute 'runtime' a:1
 
 495       call s:source(rtp, a:2)
 
 497     call s:doautocmd('User', name)
 
 501 function! s:lod_ft(pat, names)
 
 502   let syn = 'syntax/'.a:pat.'.vim'
 
 503   call s:lod(a:names, ['plugin', 'after/plugin'], syn, 'after/'.syn)
 
 504   execute 'autocmd! PlugLOD FileType' a:pat
 
 505   call s:doautocmd('filetypeplugin', 'FileType')
 
 506   call s:doautocmd('filetypeindent', 'FileType')
 
 509 function! s:lod_cmd(cmd, bang, l1, l2, args, names)
 
 510   call s:lod(a:names, ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin'])
 
 511   call s:dobufread(a:names)
 
 512   execute printf('%s%s%s %s', (a:l1 == a:l2 ? '' : (a:l1.','.a:l2)), a:cmd, a:bang, a:args)
 
 515 function! s:lod_map(map, names, with_prefix, prefix)
 
 516   call s:lod(a:names, ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin'])
 
 517   call s:dobufread(a:names)
 
 524     let extra .= nr2char(c)
 
 528     let prefix = v:count ? v:count : ''
 
 529     let prefix .= '"'.v:register.a:prefix
 
 532         let prefix = "\<esc>" . prefix
 
 534       let prefix .= v:operator
 
 536     call feedkeys(prefix, 'n')
 
 538   call feedkeys(substitute(a:map, '^<Plug>', "\<Plug>", '') . extra)
 
 541 function! plug#(repo, ...)
 
 543     return s:err('Invalid number of arguments (1..2)')
 
 547     let repo = s:trim(a:repo)
 
 548     let opts = a:0 == 1 ? s:parse_options(a:1) : s:base_spec
 
 549     let name = get(opts, 'as', fnamemodify(repo, ':t:s?\.git$??'))
 
 550     let spec = extend(s:infer_properties(name, repo), opts)
 
 551     if !has_key(g:plugs, name)
 
 552       call add(g:plugs_order, name)
 
 554     let g:plugs[name] = spec
 
 555     let s:loaded[name] = get(s:loaded, name, 0)
 
 557     return s:err(v:exception)
 
 561 function! s:parse_options(arg)
 
 562   let opts = copy(s:base_spec)
 
 563   let type = type(a:arg)
 
 564   if type == s:TYPE.string
 
 566   elseif type == s:TYPE.dict
 
 567     call extend(opts, a:arg)
 
 568     if has_key(opts, 'dir')
 
 569       let opts.dir = s:dirpath(expand(opts.dir))
 
 572     throw 'Invalid argument type (expected: string or dictionary)'
 
 577 function! s:infer_properties(name, repo)
 
 579   if s:is_local_plug(repo)
 
 580     return { 'dir': s:dirpath(expand(repo)) }
 
 586         throw printf('Invalid argument: %s (implicit `vim-scripts'' expansion is deprecated)', repo)
 
 588       let fmt = get(g:, 'plug_url_format', 'https://git::@github.com/%s.git')
 
 589       let uri = printf(fmt, repo)
 
 591     return { 'dir': s:dirpath(g:plug_home.'/'.a:name), 'uri': uri }
 
 595 function! s:install(force, names)
 
 596   call s:update_impl(0, a:force, a:names)
 
 599 function! s:update(force, names)
 
 600   call s:update_impl(1, a:force, a:names)
 
 603 function! plug#helptags()
 
 604   if !exists('g:plugs')
 
 605     return s:err('plug#begin was not called')
 
 607   for spec in values(g:plugs)
 
 608     let docd = join([s:rtp(spec), 'doc'], '/')
 
 610       silent! execute 'helptags' s:esc(docd)
 
 618   syntax region plug1 start=/\%1l/ end=/\%2l/ contains=plugNumber
 
 619   syntax region plug2 start=/\%2l/ end=/\%3l/ contains=plugBracket,plugX
 
 620   syn match plugNumber /[0-9]\+[0-9.]*/ contained
 
 621   syn match plugBracket /[[\]]/ contained
 
 622   syn match plugX /x/ contained
 
 623   syn match plugDash /^-/
 
 624   syn match plugPlus /^+/
 
 625   syn match plugStar /^*/
 
 626   syn match plugMessage /\(^- \)\@<=.*/
 
 627   syn match plugName /\(^- \)\@<=[^ ]*:/
 
 628   syn match plugSha /\%(: \)\@<=[0-9a-f]\{4,}$/
 
 629   syn match plugTag /(tag: [^)]\+)/
 
 630   syn match plugInstall /\(^+ \)\@<=[^:]*/
 
 631   syn match plugUpdate /\(^* \)\@<=[^:]*/
 
 632   syn match plugCommit /^  \X*[0-9a-f]\{7,9} .*/ contains=plugRelDate,plugEdge,plugTag
 
 633   syn match plugEdge /^  \X\+$/
 
 634   syn match plugEdge /^  \X*/ contained nextgroup=plugSha
 
 635   syn match plugSha /[0-9a-f]\{7,9}/ contained
 
 636   syn match plugRelDate /([^)]*)$/ contained
 
 637   syn match plugNotLoaded /(not loaded)$/
 
 638   syn match plugError /^x.*/
 
 639   syn region plugDeleted start=/^\~ .*/ end=/^\ze\S/
 
 640   syn match plugH2 /^.*:\n-\+$/
 
 641   syn keyword Function PlugInstall PlugStatus PlugUpdate PlugClean
 
 642   hi def link plug1       Title
 
 643   hi def link plug2       Repeat
 
 644   hi def link plugH2      Type
 
 645   hi def link plugX       Exception
 
 646   hi def link plugBracket Structure
 
 647   hi def link plugNumber  Number
 
 649   hi def link plugDash    Special
 
 650   hi def link plugPlus    Constant
 
 651   hi def link plugStar    Boolean
 
 653   hi def link plugMessage Function
 
 654   hi def link plugName    Label
 
 655   hi def link plugInstall Function
 
 656   hi def link plugUpdate  Type
 
 658   hi def link plugError   Error
 
 659   hi def link plugDeleted Ignore
 
 660   hi def link plugRelDate Comment
 
 661   hi def link plugEdge    PreProc
 
 662   hi def link plugSha     Identifier
 
 663   hi def link plugTag     Constant
 
 665   hi def link plugNotLoaded Comment
 
 668 function! s:lpad(str, len)
 
 669   return a:str . repeat(' ', a:len - len(a:str))
 
 672 function! s:lines(msg)
 
 673   return split(a:msg, "[\r\n]")
 
 676 function! s:lastline(msg)
 
 677   return get(s:lines(a:msg), -1, '')
 
 680 function! s:new_window()
 
 681   execute get(g:, 'plug_window', 'vertical topleft new')
 
 684 function! s:plug_window_exists()
 
 685   let buflist = tabpagebuflist(s:plug_tab)
 
 686   return !empty(buflist) && index(buflist, s:plug_buf) >= 0
 
 689 function! s:switch_in()
 
 690   if !s:plug_window_exists()
 
 694   if winbufnr(0) != s:plug_buf
 
 695     let s:pos = [tabpagenr(), winnr(), winsaveview()]
 
 696     execute 'normal!' s:plug_tab.'gt'
 
 697     let winnr = bufwinnr(s:plug_buf)
 
 698     execute winnr.'wincmd w'
 
 699     call add(s:pos, winsaveview())
 
 701     let s:pos = [winsaveview()]
 
 708 function! s:switch_out(...)
 
 709   call winrestview(s:pos[-1])
 
 710   setlocal nomodifiable
 
 716     execute 'normal!' s:pos[0].'gt'
 
 717     execute s:pos[1] 'wincmd w'
 
 718     call winrestview(s:pos[2])
 
 722 function! s:finish_bindings()
 
 723   nnoremap <silent> <buffer> R  :call <SID>retry()<cr>
 
 724   nnoremap <silent> <buffer> D  :PlugDiff<cr>
 
 725   nnoremap <silent> <buffer> S  :PlugStatus<cr>
 
 726   nnoremap <silent> <buffer> U  :call <SID>status_update()<cr>
 
 727   xnoremap <silent> <buffer> U  :call <SID>status_update()<cr>
 
 728   nnoremap <silent> <buffer> ]] :silent! call <SID>section('')<cr>
 
 729   nnoremap <silent> <buffer> [[ :silent! call <SID>section('b')<cr>
 
 732 function! s:prepare(...)
 
 734     throw 'Invalid current working directory. Cannot proceed.'
 
 737   for evar in ['$GIT_DIR', '$GIT_WORK_TREE']
 
 739       throw evar.' detected. Cannot proceed.'
 
 745     if b:plug_preview == 1
 
 753   nnoremap <silent> <buffer> q  :if b:plug_preview==1<bar>pc<bar>endif<bar>bd<cr>
 
 755     call s:finish_bindings()
 
 757   let b:plug_preview = -1
 
 758   let s:plug_tab = tabpagenr()
 
 759   let s:plug_buf = winbufnr(0)
 
 762   for k in ['<cr>', 'L', 'o', 'X', 'd', 'dd']
 
 763     execute 'silent! unmap <buffer>' k
 
 765   setlocal buftype=nofile bufhidden=wipe nobuflisted nolist noswapfile nowrap cursorline modifiable nospell
 
 767   if exists('g:syntax_on')
 
 772 function! s:assign_name()
 
 774   let prefix = '[Plugins]'
 
 777   while bufexists(name)
 
 778     let name = printf('%s (%s)', prefix, idx)
 
 781   silent! execute 'f' fnameescape(name)
 
 784 function! s:chsh(swap)
 
 785   let prev = [&shell, &shellcmdflag, &shellredir]
 
 787     set shell=cmd.exe shellcmdflag=/c shellredir=>%s\ 2>&1
 
 789     set shell=sh shellredir=>%s\ 2>&1
 
 794 function! s:bang(cmd, ...)
 
 796     let [sh, shellcmdflag, shrd] = s:chsh(a:0)
 
 797     " FIXME: Escaping is incomplete. We could use shellescape with eval,
 
 798     "        but it won't work on Windows.
 
 799     let cmd = a:0 ? s:with_cd(a:cmd, a:1) : a:cmd
 
 801       let batchfile = tempname().'.bat'
 
 802       call writefile(['@echo off', cmd], batchfile)
 
 805     let g:_plug_bang = (s:is_win && has('gui_running') ? 'silent ' : '').'!'.escape(cmd, '#!%')
 
 806     execute "normal! :execute g:_plug_bang\<cr>\<cr>"
 
 809     let [&shell, &shellcmdflag, &shellredir] = [sh, shellcmdflag, shrd]
 
 811       call delete(batchfile)
 
 814   return v:shell_error ? 'Exit status: ' . v:shell_error : ''
 
 817 function! s:regress_bar()
 
 818   let bar = substitute(getline(2)[1:-2], '.*\zs=', 'x', '')
 
 819   call s:progress_bar(2, bar, len(bar))
 
 822 function! s:is_updated(dir)
 
 823   return !empty(s:system_chomp('git log --pretty=format:"%h" "HEAD...HEAD@{1}"', a:dir))
 
 826 function! s:do(pull, force, todo)
 
 827   for [name, spec] in items(a:todo)
 
 828     if !isdirectory(spec.dir)
 
 831     let installed = has_key(s:update.new, name)
 
 832     let updated = installed ? 0 :
 
 833       \ (a:pull && index(s:update.errors, name) < 0 && s:is_updated(spec.dir))
 
 834     if a:force || installed || updated
 
 835       execute 'cd' s:esc(spec.dir)
 
 836       call append(3, '- Post-update hook for '. name .' ... ')
 
 838       let type = type(spec.do)
 
 839       if type == s:TYPE.string
 
 841           if !get(s:loaded, name, 0)
 
 842             let s:loaded[name] = 1
 
 845           call s:load_plugin(spec)
 
 849             let error = v:exception
 
 851           if !s:plug_window_exists()
 
 853             throw 'Warning: vim-plug was terminated by the post-update hook of '.name
 
 856           let error = s:bang(spec.do)
 
 858       elseif type == s:TYPE.funcref
 
 860           let status = installed ? 'installed' : (updated ? 'updated' : 'unchanged')
 
 861           call spec.do({ 'name': name, 'status': status, 'force': a:force })
 
 863           let error = v:exception
 
 866         let error = 'Invalid hook type'
 
 869       call setline(4, empty(error) ? (getline(4) . 'OK')
 
 870                                  \ : ('x' . getline(4)[1:] . error))
 
 872         call add(s:update.errors, name)
 
 880 function! s:hash_match(a, b)
 
 881   return stridx(a:a, a:b) == 0 || stridx(a:b, a:a) == 0
 
 884 function! s:checkout(spec)
 
 885   let sha = a:spec.commit
 
 886   let output = s:system('git rev-parse HEAD', a:spec.dir)
 
 887   if !v:shell_error && !s:hash_match(sha, s:lines(output)[0])
 
 888     let output = s:system(
 
 889           \ 'git fetch --depth 999999 && git checkout '.s:esc(sha).' --', a:spec.dir)
 
 894 function! s:finish(pull)
 
 895   let new_frozen = len(filter(keys(s:update.new), 'g:plugs[v:val].frozen'))
 
 897     let s = new_frozen > 1 ? 's' : ''
 
 898     call append(3, printf('- Installed %d frozen plugin%s', new_frozen, s))
 
 900   call append(3, '- Finishing ... ') | 4
 
 904   call setline(4, getline(4) . 'Done!')
 
 907   if !empty(s:update.errors)
 
 908     call add(msgs, "Press 'R' to retry.")
 
 910   if a:pull && len(s:update.new) < len(filter(getline(5, '$'),
 
 911                 \ "v:val =~ '^- ' && stridx(v:val, 'Already up-to-date') < 0"))
 
 912     call add(msgs, "Press 'D' to see the updated changes.")
 
 915   call s:finish_bindings()
 
 919   if empty(s:update.errors)
 
 923   call s:update_impl(s:update.pull, s:update.force,
 
 924         \ extend(copy(s:update.errors), [s:update.threads]))
 
 927 function! s:is_managed(name)
 
 928   return has_key(g:plugs[a:name], 'uri')
 
 931 function! s:names(...)
 
 932   return sort(filter(keys(g:plugs), 'stridx(v:val, a:1) == 0 && s:is_managed(v:val)'))
 
 935 function! s:check_ruby()
 
 936   silent! ruby require 'thread'; VIM::command("let g:plug_ruby = '#{RUBY_VERSION}'")
 
 937   if !exists('g:plug_ruby')
 
 939     return s:warn('echom', 'Warning: Ruby interface is broken')
 
 941   let ruby_version = split(g:plug_ruby, '\.')
 
 943   return s:version_requirement(ruby_version, [1, 8, 7])
 
 946 function! s:update_impl(pull, force, args) abort
 
 947   let sync = index(a:args, '--sync') >= 0 || has('vim_starting')
 
 948   let args = filter(copy(a:args), 'v:val != "--sync"')
 
 949   let threads = (len(args) > 0 && args[-1] =~ '^[1-9][0-9]*$') ?
 
 950                   \ remove(args, -1) : get(g:, 'plug_threads', 16)
 
 952   let managed = filter(copy(g:plugs), 's:is_managed(v:key)')
 
 953   let todo = empty(args) ? filter(managed, '!v:val.frozen || !isdirectory(v:val.dir)') :
 
 954                          \ filter(managed, 'index(args, v:key) >= 0')
 
 957     return s:warn('echo', 'No plugin to '. (a:pull ? 'update' : 'install'))
 
 960   if !s:is_win && s:git_version_requirement(2, 3)
 
 961     let s:git_terminal_prompt = exists('$GIT_TERMINAL_PROMPT') ? $GIT_TERMINAL_PROMPT : ''
 
 962     let $GIT_TERMINAL_PROMPT = 0
 
 963     for plug in values(todo)
 
 964       let plug.uri = substitute(plug.uri,
 
 965             \ '^https://git::@github\.com', 'https://github.com', '')
 
 969   if !isdirectory(g:plug_home)
 
 971       call mkdir(g:plug_home, 'p')
 
 973       return s:err(printf('Invalid plug directory: %s. '.
 
 974               \ 'Try to call plug#begin with a valid directory', g:plug_home))
 
 978   if has('nvim') && !exists('*jobwait') && threads > 1
 
 979     call s:warn('echom', '[vim-plug] Update Neovim for parallel installer')
 
 982   let use_job = s:nvim || s:vim8
 
 983   let python = (has('python') || has('python3')) && !use_job
 
 984   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()
 
 987     \ 'start':   reltime(),
 
 989     \ 'todo':    copy(todo),
 
 994     \ 'threads': (python || ruby || use_job) ? min([len(todo), threads]) : 1,
 
1000   call append(0, ['', ''])
 
1004   let s:clone_opt = get(g:, 'plug_shallow', 1) ?
 
1005         \ '--depth 1' . (s:git_version_requirement(1, 7, 10) ? ' --no-single-branch' : '') : ''
 
1008     let s:clone_opt .= ' -c core.eol=lf -c core.autocrlf=input'
 
1011   " Python version requirement (>= 2.7)
 
1012   if python && !has('python3') && !ruby && !use_job && s:update.threads > 1
 
1014     silent python import platform; print platform.python_version()
 
1016     let python = s:version_requirement(
 
1017           \ map(split(split(pyv)[0], '\.'), 'str2nr(v:val)'), [2, 6])
 
1020   if (python || ruby) && s:update.threads > 1
 
1027         call s:update_ruby()
 
1029         call s:update_python()
 
1032       let lines = getline(4, '$')
 
1036         let name = s:extract_name(line, '.', '')
 
1037         if empty(name) || !has_key(printed, name)
 
1038           call append('$', line)
 
1040             let printed[name] = 1
 
1041             if line[0] == 'x' && index(s:update.errors, name) < 0
 
1042               call add(s:update.errors, name)
 
1049       call s:update_finish()
 
1053     while use_job && sync
 
1062 function! s:log4(name, msg)
 
1063   call setline(4, printf('- %s (%s)', a:msg, a:name))
 
1067 function! s:update_finish()
 
1068   if exists('s:git_terminal_prompt')
 
1069     let $GIT_TERMINAL_PROMPT = s:git_terminal_prompt
 
1072     call append(3, '- Updating ...') | 4
 
1073     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))'))
 
1074       let [pos, _] = s:logpos(name)
 
1078       if has_key(spec, 'commit')
 
1079         call s:log4(name, 'Checking out '.spec.commit)
 
1080         let out = s:checkout(spec)
 
1081       elseif has_key(spec, 'tag')
 
1084           let tags = s:lines(s:system('git tag --list '.s:shellesc(tag).' --sort -version:refname 2>&1', spec.dir))
 
1085           if !v:shell_error && !empty(tags)
 
1087             call s:log4(name, printf('Latest tag for %s -> %s', spec.tag, tag))
 
1091         call s:log4(name, 'Checking out '.tag)
 
1092         let out = s:system('git checkout -q '.s:esc(tag).' -- 2>&1', spec.dir)
 
1094         let branch = s:esc(get(spec, 'branch', 'master'))
 
1095         call s:log4(name, 'Merging origin/'.branch)
 
1096         let out = s:system('git checkout -q '.branch.' -- 2>&1'
 
1097               \. (has_key(s:update.new, name) ? '' : ('&& git merge --ff-only origin/'.branch.' 2>&1')), spec.dir)
 
1099       if !v:shell_error && filereadable(spec.dir.'/.gitmodules') &&
 
1100             \ (s:update.force || has_key(s:update.new, name) || s:is_updated(spec.dir))
 
1101         call s:log4(name, 'Updating submodules. This may take a while.')
 
1102         let out .= s:bang('git submodule update --init --recursive 2>&1', spec.dir)
 
1104       let msg = s:format_message(v:shell_error ? 'x': '-', name, out)
 
1106         call add(s:update.errors, name)
 
1107         call s:regress_bar()
 
1108         silent execute pos 'd _'
 
1109         call append(4, msg) | 4
 
1111         call setline(pos, msg[0])
 
1117       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")'))
 
1119       call s:warn('echom', v:exception)
 
1120       call s:warn('echo', '')
 
1123     call s:finish(s:update.pull)
 
1124     call setline(1, 'Updated. Elapsed time: ' . split(reltimestr(reltime(s:update.start)))[0] . ' sec.')
 
1125     call s:switch_out('normal! gg')
 
1129 function! s:job_abort()
 
1130   if (!s:nvim && !s:vim8) || !exists('s:jobs')
 
1134   for [name, j] in items(s:jobs)
 
1136       silent! call jobstop(j.jobid)
 
1138       silent! call job_stop(j.jobid)
 
1141       call s:system('rm -rf ' . s:shellesc(g:plugs[name].dir))
 
1147 function! s:last_non_empty_line(lines)
 
1148   let len = len(a:lines)
 
1149   for idx in range(len)
 
1150     let line = a:lines[len-idx-1]
 
1158 function! s:job_out_cb(self, data) abort
 
1160   let data = remove(self.lines, -1) . a:data
 
1161   let lines = map(split(data, "\n", 1), 'split(v:val, "\r", 1)[-1]')
 
1162   call extend(self.lines, lines)
 
1163   " To reduce the number of buffer updates
 
1164   let self.tick = get(self, 'tick', -1) + 1
 
1165   if !self.running || self.tick % len(s:jobs) == 0
 
1166     let bullet = self.running ? (self.new ? '+' : '*') : (self.error ? 'x' : '-')
 
1167     let result = self.error ? join(self.lines, "\n") : s:last_non_empty_line(self.lines)
 
1168     call s:log(bullet, self.name, result)
 
1172 function! s:job_exit_cb(self, data) abort
 
1173   let a:self.running = 0
 
1174   let a:self.error = a:data != 0
 
1175   call s:reap(a:self.name)
 
1179 function! s:job_cb(fn, job, ch, data)
 
1180   if !s:plug_window_exists() " plug window closed
 
1181     return s:job_abort()
 
1183   call call(a:fn, [a:job, a:data])
 
1186 function! s:nvim_cb(job_id, data, event) dict abort
 
1187   return a:event == 'stdout' ?
 
1188     \ s:job_cb('s:job_out_cb',  self, 0, join(a:data, "\n")) :
 
1189     \ s:job_cb('s:job_exit_cb', self, 0, a:data)
 
1192 function! s:spawn(name, cmd, opts)
 
1193   let job = { 'name': a:name, 'running': 1, 'error': 0, 'lines': [''],
 
1194             \ 'batchfile': (s:is_win && (s:nvim || s:vim8)) ? tempname().'.bat' : '',
 
1195             \ 'new': get(a:opts, 'new', 0) }
 
1196   let s:jobs[a:name] = job
 
1197   let cmd = has_key(a:opts, 'dir') ? s:with_cd(a:cmd, a:opts.dir) : a:cmd
 
1198   if !empty(job.batchfile)
 
1199     call writefile(['@echo off', cmd], job.batchfile)
 
1200     let cmd = job.batchfile
 
1202   let argv = add(s:is_win ? ['cmd', '/c'] : ['sh', '-c'], cmd)
 
1206     \ 'on_stdout': function('s:nvim_cb'),
 
1207     \ 'on_exit':   function('s:nvim_cb'),
 
1209     let jid = jobstart(argv, job)
 
1215       let job.lines   = [jid < 0 ? argv[0].' is not executable' :
 
1216             \ 'Invalid arguments (or job table is full)']
 
1219     let jid = job_start(s:is_win ? join(argv, ' ') : argv, {
 
1220     \ 'out_cb':   function('s:job_cb', ['s:job_out_cb',  job]),
 
1221     \ 'exit_cb':  function('s:job_cb', ['s:job_exit_cb', job]),
 
1224     if job_status(jid) == 'run'
 
1229       let job.lines   = ['Failed to start job']
 
1232     let job.lines = s:lines(call('s:system', [cmd]))
 
1233     let job.error = v:shell_error != 0
 
1238 function! s:reap(name)
 
1239   let job = s:jobs[a:name]
 
1241     call add(s:update.errors, a:name)
 
1242   elseif get(job, 'new', 0)
 
1243     let s:update.new[a:name] = 1
 
1245   let s:update.bar .= job.error ? 'x' : '='
 
1247   let bullet = job.error ? 'x' : '-'
 
1248   let result = job.error ? join(job.lines, "\n") : s:last_non_empty_line(job.lines)
 
1249   call s:log(bullet, a:name, empty(result) ? 'OK' : result)
 
1252   if has_key(job, 'batchfile') && !empty(job.batchfile)
 
1253     call delete(job.batchfile)
 
1255   call remove(s:jobs, a:name)
 
1260     let total = len(s:update.all)
 
1261     call setline(1, (s:update.pull ? 'Updating' : 'Installing').
 
1262           \ ' plugins ('.len(s:update.bar).'/'.total.')')
 
1263     call s:progress_bar(2, s:update.bar, total)
 
1268 function! s:logpos(name)
 
1269   for i in range(4, line('$'))
 
1270     if getline(i) =~# '^[-+x*] '.a:name.':'
 
1271       for j in range(i + 1, line('$'))
 
1272         if getline(j) !~ '^ '
 
1282 function! s:log(bullet, name, lines)
 
1284     let [b, e] = s:logpos(a:name)
 
1286       silent execute printf('%d,%d d _', b, e)
 
1287       if b > winheight('.')
 
1293     " FIXME For some reason, nomodifiable is set after :d in vim8
 
1295     call append(b - 1, s:format_message(a:bullet, a:name, a:lines))
 
1300 function! s:update_vim()
 
1308   let pull = s:update.pull
 
1309   let prog = s:progress_opt(s:nvim || s:vim8)
 
1310 while 1 " Without TCO, Vim stack is bound to explode
 
1311   if empty(s:update.todo)
 
1312     if empty(s:jobs) && !s:update.fin
 
1313       call s:update_finish()
 
1314       let s:update.fin = 1
 
1319   let name = keys(s:update.todo)[0]
 
1320   let spec = remove(s:update.todo, name)
 
1321   let new  = !isdirectory(spec.dir)
 
1323   call s:log(new ? '+' : '*', name, pull ? 'Updating ...' : 'Installing ...')
 
1326   let has_tag = has_key(spec, 'tag')
 
1328     let [error, _] = s:git_validate(spec, 0)
 
1331         let fetch_opt = (has_tag && !empty(globpath(spec.dir, '.git/shallow'))) ? '--depth 99999999' : ''
 
1332         call s:spawn(name, printf('git fetch %s %s 2>&1', fetch_opt, prog), { 'dir': spec.dir })
 
1334         let s:jobs[name] = { 'running': 0, 'lines': ['Already installed'], 'error': 0 }
 
1337       let s:jobs[name] = { 'running': 0, 'lines': s:lines(error), 'error': 1 }
 
1341           \ printf('git clone %s %s %s %s 2>&1',
 
1342           \ has_tag ? '' : s:clone_opt,
 
1344           \ s:shellesc(spec.uri),
 
1345           \ s:shellesc(s:trim(spec.dir))), { 'new': 1 })
 
1348   if !s:jobs[name].running
 
1351   if len(s:jobs) >= s:update.threads
 
1357 function! s:update_python()
 
1358 let py_exe = has('python') ? 'python' : 'python3'
 
1359 execute py_exe "<< EOF"
 
1366   import Queue as queue
 
1373 import threading as thr
 
1378 G_NVIM = vim.eval("has('nvim')") == '1'
 
1379 G_PULL = vim.eval('s:update.pull') == '1'
 
1380 G_RETRIES = int(vim.eval('get(g:, "plug_retries", 2)')) + 1
 
1381 G_TIMEOUT = int(vim.eval('get(g:, "plug_timeout", 60)'))
 
1382 G_CLONE_OPT = vim.eval('s:clone_opt')
 
1383 G_PROGRESS = vim.eval('s:progress_opt(1)')
 
1384 G_LOG_PROB = 1.0 / int(vim.eval('s:update.threads'))
 
1385 G_STOP = thr.Event()
 
1386 G_IS_WIN = vim.eval('s:is_win') == '1'
 
1388 class PlugError(Exception):
 
1389   def __init__(self, msg):
 
1391 class CmdTimedOut(PlugError):
 
1393 class CmdFailed(PlugError):
 
1395 class InvalidURI(PlugError):
 
1397 class Action(object):
 
1398   INSTALL, UPDATE, ERROR, DONE = ['+', '*', 'x', '-']
 
1400 class Buffer(object):
 
1401   def __init__(self, lock, num_plugs, is_pull):
 
1403     self.event = 'Updating' if is_pull else 'Installing'
 
1405     self.maxy = int(vim.eval('winheight(".")'))
 
1406     self.num_plugs = num_plugs
 
1408   def __where(self, name):
 
1409     """ Find first line with name in current buffer. Return line num. """
 
1410     found, lnum = False, 0
 
1411     matcher = re.compile('^[-+x*] {0}:'.format(name))
 
1412     for line in vim.current.buffer:
 
1413       if matcher.search(line) is not None:
 
1423     curbuf = vim.current.buffer
 
1424     curbuf[0] = self.event + ' plugins ({0}/{1})'.format(len(self.bar), self.num_plugs)
 
1426     num_spaces = self.num_plugs - len(self.bar)
 
1427     curbuf[1] = '[{0}{1}]'.format(self.bar, num_spaces * ' ')
 
1430       vim.command('normal! 2G')
 
1431       vim.command('redraw')
 
1433   def write(self, action, name, lines):
 
1434     first, rest = lines[0], lines[1:]
 
1435     msg = ['{0} {1}{2}{3}'.format(action, name, ': ' if first else '', first)]
 
1436     msg.extend(['    ' + line for line in rest])
 
1439       if action == Action.ERROR:
 
1441         vim.command("call add(s:update.errors, '{0}')".format(name))
 
1442       elif action == Action.DONE:
 
1445       curbuf = vim.current.buffer
 
1446       lnum = self.__where(name)
 
1447       if lnum != -1: # Found matching line num
 
1449         if lnum > self.maxy and action in set([Action.INSTALL, Action.UPDATE]):
 
1453       curbuf.append(msg, lnum)
 
1459 class Command(object):
 
1460   CD = 'cd /d' if G_IS_WIN else 'cd'
 
1462   def __init__(self, cmd, cmd_dir=None, timeout=60, cb=None, clean=None):
 
1465       self.cmd = '{0} {1} && {2}'.format(Command.CD, cmd_dir, self.cmd)
 
1466     self.timeout = timeout
 
1467     self.callback = cb if cb else (lambda msg: None)
 
1468     self.clean = clean if clean else (lambda: None)
 
1473     """ Returns true only if command still running. """
 
1474     return self.proc and self.proc.poll() is None
 
1476   def execute(self, ntries=3):
 
1477     """ Execute the command with ntries if CmdTimedOut.
 
1478         Returns the output of the command if no Exception.
 
1480     attempt, finished, limit = 0, False, self.timeout
 
1485         result = self.try_command()
 
1489         if attempt != ntries:
 
1491           self.timeout += limit
 
1495   def notify_retry(self):
 
1496     """ Retry required for command, notify user. """
 
1497     for count in range(3, 0, -1):
 
1499         raise KeyboardInterrupt
 
1500       msg = 'Timeout. Will retry in {0} second{1} ...'.format(
 
1501             count, 's' if count != 1 else '')
 
1502       self.callback([msg])
 
1504     self.callback(['Retrying ...'])
 
1506   def try_command(self):
 
1507     """ Execute a cmd & poll for callback. Returns list of output.
 
1508         Raises CmdFailed   -> return code for Popen isn't 0
 
1509         Raises CmdTimedOut -> command exceeded timeout without new output
 
1514       tfile = tempfile.NamedTemporaryFile(mode='w+b')
 
1515       preexec_fn = not G_IS_WIN and os.setsid or None
 
1516       self.proc = subprocess.Popen(self.cmd, stdout=tfile,
 
1517                                    stderr=subprocess.STDOUT,
 
1518                                    stdin=subprocess.PIPE, shell=True,
 
1519                                    preexec_fn=preexec_fn)
 
1520       thrd = thr.Thread(target=(lambda proc: proc.wait()), args=(self.proc,))
 
1523       thread_not_started = True
 
1524       while thread_not_started:
 
1527           thread_not_started = False
 
1528         except RuntimeError:
 
1533           raise KeyboardInterrupt
 
1535         if first_line or random.random() < G_LOG_PROB:
 
1537           line = '' if G_IS_WIN else nonblock_read(tfile.name)
 
1539             self.callback([line])
 
1541         time_diff = time.time() - os.path.getmtime(tfile.name)
 
1542         if time_diff > self.timeout:
 
1543           raise CmdTimedOut(['Timeout!'])
 
1548       result = [line.decode('utf-8', 'replace').rstrip() for line in tfile]
 
1550       if self.proc.returncode != 0:
 
1551         raise CmdFailed([''] + result)
 
1558   def terminate(self):
 
1559     """ Terminate process and cleanup. """
 
1562         os.kill(self.proc.pid, signal.SIGINT)
 
1564         os.killpg(self.proc.pid, signal.SIGTERM)
 
1567 class Plugin(object):
 
1568   def __init__(self, name, args, buf_q, lock):
 
1573     self.tag = args.get('tag', 0)
 
1577       if os.path.exists(self.args['dir']):
 
1582           thread_vim_command("let s:update.new['{0}'] = 1".format(self.name))
 
1583     except PlugError as exc:
 
1584       self.write(Action.ERROR, self.name, exc.msg)
 
1585     except KeyboardInterrupt:
 
1587       self.write(Action.ERROR, self.name, ['Interrupted!'])
 
1589       # Any exception except those above print stack trace
 
1590       msg = 'Trace:\n{0}'.format(traceback.format_exc().rstrip())
 
1591       self.write(Action.ERROR, self.name, msg.split('\n'))
 
1595     target = self.args['dir']
 
1596     if target[-1] == '\\':
 
1597       target = target[0:-1]
 
1602           shutil.rmtree(target)
 
1607     self.write(Action.INSTALL, self.name, ['Installing ...'])
 
1608     callback = functools.partial(self.write, Action.INSTALL, self.name)
 
1609     cmd = 'git clone {0} {1} {2} {3} 2>&1'.format(
 
1610           '' if self.tag else G_CLONE_OPT, G_PROGRESS, self.args['uri'],
 
1612     com = Command(cmd, None, G_TIMEOUT, callback, clean(target))
 
1613     result = com.execute(G_RETRIES)
 
1614     self.write(Action.DONE, self.name, result[-1:])
 
1617     cmd = 'git rev-parse --abbrev-ref HEAD 2>&1 && git config -f .git/config remote.origin.url'
 
1618     command = Command(cmd, self.args['dir'], G_TIMEOUT,)
 
1619     result = command.execute(G_RETRIES)
 
1623     actual_uri = self.repo_uri()
 
1624     expect_uri = self.args['uri']
 
1625     regex = re.compile(r'^(?:\w+://)?(?:[^@/]*@)?([^:/]*(?::[0-9]*)?)[:/](.*?)(?:\.git)?/?$')
 
1626     ma = regex.match(actual_uri)
 
1627     mb = regex.match(expect_uri)
 
1628     if ma is None or mb is None or ma.groups() != mb.groups():
 
1630              'Invalid URI: {0}'.format(actual_uri),
 
1631              'Expected     {0}'.format(expect_uri),
 
1632              'PlugClean required.']
 
1633       raise InvalidURI(msg)
 
1636       self.write(Action.UPDATE, self.name, ['Updating ...'])
 
1637       callback = functools.partial(self.write, Action.UPDATE, self.name)
 
1638       fetch_opt = '--depth 99999999' if self.tag and os.path.isfile(os.path.join(self.args['dir'], '.git/shallow')) else ''
 
1639       cmd = 'git fetch {0} {1} 2>&1'.format(fetch_opt, G_PROGRESS)
 
1640       com = Command(cmd, self.args['dir'], G_TIMEOUT, callback)
 
1641       result = com.execute(G_RETRIES)
 
1642       self.write(Action.DONE, self.name, result[-1:])
 
1644       self.write(Action.DONE, self.name, ['Already installed'])
 
1646   def write(self, action, name, msg):
 
1647     self.buf_q.put((action, name, msg))
 
1649 class PlugThread(thr.Thread):
 
1650   def __init__(self, tname, args):
 
1651     super(PlugThread, self).__init__()
 
1656     thr.current_thread().name = self.tname
 
1657     buf_q, work_q, lock = self.args
 
1660       while not G_STOP.is_set():
 
1661         name, args = work_q.get_nowait()
 
1662         plug = Plugin(name, args, buf_q, lock)
 
1668 class RefreshThread(thr.Thread):
 
1669   def __init__(self, lock):
 
1670     super(RefreshThread, self).__init__()
 
1677         thread_vim_command('noautocmd normal! a')
 
1681     self.running = False
 
1684   def thread_vim_command(cmd):
 
1685     vim.session.threadsafe_call(lambda: vim.command(cmd))
 
1687   def thread_vim_command(cmd):
 
1691   return '"' + name.replace('"', '\"') + '"'
 
1693 def nonblock_read(fname):
 
1694   """ Read a file with nonblock flag. Return the last line. """
 
1695   fread = os.open(fname, os.O_RDONLY | os.O_NONBLOCK)
 
1696   buf = os.read(fread, 100000).decode('utf-8', 'replace')
 
1699   line = buf.rstrip('\r\n')
 
1700   left = max(line.rfind('\r'), line.rfind('\n'))
 
1708   thr.current_thread().name = 'main'
 
1709   nthreads = int(vim.eval('s:update.threads'))
 
1710   plugs = vim.eval('s:update.todo')
 
1711   mac_gui = vim.eval('s:mac_gui') == '1'
 
1714   buf = Buffer(lock, len(plugs), G_PULL)
 
1715   buf_q, work_q = queue.Queue(), queue.Queue()
 
1716   for work in plugs.items():
 
1719   start_cnt = thr.active_count()
 
1720   for num in range(nthreads):
 
1721     tname = 'PlugT-{0:02}'.format(num)
 
1722     thread = PlugThread(tname, (buf_q, work_q, lock))
 
1725     rthread = RefreshThread(lock)
 
1728   while not buf_q.empty() or thr.active_count() != start_cnt:
 
1730       action, name, msg = buf_q.get(True, 0.25)
 
1731       buf.write(action, name, ['OK'] if not msg else msg)
 
1735     except KeyboardInterrupt:
 
1746 function! s:update_ruby()
 
1749     SEP = ["\r", "\n", nil]
 
1753         char = readchar rescue return
 
1754         if SEP.include? char.chr
 
1763   end unless defined?(PlugStream)
 
1766     %["#{arg.gsub('"', '\"')}"]
 
1771     if /mswin|mingw|bccwin/ =~ RUBY_PLATFORM
 
1772       pids.each { |pid| Process.kill 'INT', pid.to_i rescue nil }
 
1774       unless `which pgrep 2> /dev/null`.empty?
 
1776         until children.empty?
 
1777           children = children.map { |pid|
 
1778             `pgrep -P #{pid}`.lines.map { |l| l.chomp }
 
1783       pids.each { |pid| Process.kill 'TERM', pid.to_i rescue nil }
 
1787   def compare_git_uri a, b
 
1788     regex = %r{^(?:\w+://)?(?:[^@/]*@)?([^:/]*(?::[0-9]*)?)[:/](.*?)(?:\.git)?/?$}
 
1789     regex.match(a).to_a.drop(1) == regex.match(b).to_a.drop(1)
 
1796   iswin = VIM::evaluate('s:is_win').to_i == 1
 
1797   pull  = VIM::evaluate('s:update.pull').to_i == 1
 
1798   base  = VIM::evaluate('g:plug_home')
 
1799   all   = VIM::evaluate('s:update.todo')
 
1800   limit = VIM::evaluate('get(g:, "plug_timeout", 60)')
 
1801   tries = VIM::evaluate('get(g:, "plug_retries", 2)') + 1
 
1802   nthr  = VIM::evaluate('s:update.threads').to_i
 
1803   maxy  = VIM::evaluate('winheight(".")').to_i
 
1804   vim7  = VIM::evaluate('v:version').to_i <= 703 && RUBY_PLATFORM =~ /darwin/
 
1805   cd    = iswin ? 'cd /d' : 'cd'
 
1806   tot   = VIM::evaluate('len(s:update.todo)') || 0
 
1808   skip  = 'Already installed'
 
1810   take1 = proc { mtx.synchronize { running && all.shift } }
 
1813     $curbuf[1] = "#{pull ? 'Updating' : 'Installing'} plugins (#{cnt}/#{tot})"
 
1814     $curbuf[2] = '[' + bar.ljust(tot) + ']'
 
1815     VIM::command('normal! 2G')
 
1816     VIM::command('redraw')
 
1818   where = proc { |name| (1..($curbuf.length)).find { |l| $curbuf[l] =~ /^[-+x*] #{name}:/ } }
 
1819   log   = proc { |name, result, type|
 
1821       ing  = ![true, false].include?(type)
 
1822       bar += type ? '=' : 'x' unless ing
 
1824           when :install  then '+' when :update then '*'
 
1825           when true, nil then '-' else
 
1826             VIM::command("call add(s:update.errors, '#{name}')")
 
1830         if type || type.nil?
 
1831           ["#{b} #{name}: #{result.lines.to_a.last || 'OK'}"]
 
1832         elsif result =~ /^Interrupted|^Timeout/
 
1833           ["#{b} #{name}: #{result}"]
 
1835           ["#{b} #{name}"] + result.lines.map { |l| "    " << l }
 
1837       if lnum = where.call(name)
 
1839         lnum = 4 if ing && lnum > maxy
 
1841       result.each_with_index do |line, offset|
 
1842         $curbuf.append((lnum || 4) - 1 + offset, line.gsub(/\e\[./, '').chomp)
 
1847   bt = proc { |cmd, name, type, cleanup|
 
1855         Timeout::timeout(timeout) do
 
1856           tmp = VIM::evaluate('tempname()')
 
1857           system("(#{cmd}) > #{tmp}")
 
1858           data = File.read(tmp).chomp
 
1859           File.unlink tmp rescue nil
 
1862         fd = IO.popen(cmd).extend(PlugStream)
 
1864         log_prob = 1.0 / nthr
 
1865         while line = Timeout::timeout(timeout) { fd.get_line }
 
1867           log.call name, line.chomp, type if name && (first_line || rand < log_prob)
 
1872       [$? == 0, data.chomp]
 
1873     rescue Timeout::Error, Interrupt => e
 
1874       if fd && !fd.closed?
 
1878       cleanup.call if cleanup
 
1879       if e.is_a?(Timeout::Error) && tried < tries
 
1880         3.downto(1) do |countdown|
 
1881           s = countdown > 1 ? 's' : ''
 
1882           log.call name, "Timeout. Will retry in #{countdown} second#{s} ...", type
 
1885         log.call name, 'Retrying ...', type
 
1888       [false, e.is_a?(Interrupt) ? "Interrupted!" : "Timeout!"]
 
1891   main = Thread.current
 
1893   watcher = Thread.new {
 
1895       while VIM::evaluate('getchar(1)')
 
1899       require 'io/console' # >= Ruby 1.9
 
1900       nil until IO.console.getch == 3.chr
 
1904       threads.each { |t| t.raise Interrupt } unless vim7
 
1906     threads.each { |t| t.join rescue nil }
 
1909   refresh = Thread.new {
 
1912         break unless running
 
1913         VIM::command('noautocmd normal! a')
 
1917   } if VIM::evaluate('s:mac_gui') == 1
 
1919   clone_opt = VIM::evaluate('s:clone_opt')
 
1920   progress = VIM::evaluate('s:progress_opt(1)')
 
1923       threads << Thread.new {
 
1924         while pair = take1.call
 
1926           dir, uri, tag = pair.last.values_at *%w[dir uri tag]
 
1927           exists = File.directory? dir
 
1930               chdir = "#{cd} #{iswin ? dir : esc(dir)}"
 
1931               ret, data = bt.call "#{chdir} && git rev-parse --abbrev-ref HEAD 2>&1 && git config -f .git/config remote.origin.url", nil, nil, nil
 
1932               current_uri = data.lines.to_a.last
 
1934                 if data =~ /^Interrupted|^Timeout/
 
1937                   [false, [data.chomp, "PlugClean required."].join($/)]
 
1939               elsif !compare_git_uri(current_uri, uri)
 
1940                 [false, ["Invalid URI: #{current_uri}",
 
1942                          "PlugClean required."].join($/)]
 
1945                   log.call name, 'Updating ...', :update
 
1946                   fetch_opt = (tag && File.exist?(File.join(dir, '.git/shallow'))) ? '--depth 99999999' : ''
 
1947                   bt.call "#{chdir} && git fetch #{fetch_opt} #{progress} 2>&1", name, :update, nil
 
1953               d = esc dir.sub(%r{[\\/]+$}, '')
 
1954               log.call name, 'Installing ...', :install
 
1955               bt.call "git clone #{clone_opt unless tag} #{progress} #{uri} #{d} 2>&1", name, :install, proc {
 
1959           mtx.synchronize { VIM::command("let s:update.new['#{name}'] = 1") } if !exists && ok
 
1960           log.call name, result, ok
 
1965   threads.each { |t| t.join rescue nil }
 
1967   refresh.kill if refresh
 
1972 function! s:shellesc_cmd(arg)
 
1973   let escaped = substitute(a:arg, '[&|<>()@^]', '^&', 'g')
 
1974   let escaped = substitute(escaped, '%', '%%', 'g')
 
1975   let escaped = substitute(escaped, '"', '\\^&', 'g')
 
1976   let escaped = substitute(escaped, '\(\\\+\)\(\\^\)', '\1\1\2', 'g')
 
1977   return '^"'.substitute(escaped, '\(\\\+\)$', '\1\1', '').'^"'
 
1980 function! s:shellesc(arg)
 
1981   if &shell =~# 'cmd.exe$'
 
1982     return s:shellesc_cmd(a:arg)
 
1984   return shellescape(a:arg)
 
1987 function! s:glob_dir(path)
 
1988   return map(filter(s:glob(a:path, '**'), 'isdirectory(v:val)'), 's:dirpath(v:val)')
 
1991 function! s:progress_bar(line, bar, total)
 
1992   call setline(a:line, '[' . s:lpad(a:bar, a:total) . ']')
 
1995 function! s:compare_git_uri(a, b)
 
1996   " See `git help clone'
 
1997   " https:// [user@] github.com[:port] / junegunn/vim-plug [.git]
 
1998   "          [git@]  github.com[:port] : junegunn/vim-plug [.git]
 
1999   " file://                            / junegunn/vim-plug        [/]
 
2000   "                                    / junegunn/vim-plug        [/]
 
2001   let pat = '^\%(\w\+://\)\='.'\%([^@/]*@\)\='.'\([^:/]*\%(:[0-9]*\)\=\)'.'[:/]'.'\(.\{-}\)'.'\%(\.git\)\=/\?$'
 
2002   let ma = matchlist(a:a, pat)
 
2003   let mb = matchlist(a:b, pat)
 
2004   return ma[1:2] ==# mb[1:2]
 
2007 function! s:format_message(bullet, name, message)
 
2009     return [printf('%s %s: %s', a:bullet, a:name, s:lastline(a:message))]
 
2011     let lines = map(s:lines(a:message), '"    ".v:val')
 
2012     return extend([printf('x %s:', a:name)], lines)
 
2016 function! s:with_cd(cmd, dir)
 
2017   return printf('cd%s %s && %s', s:is_win ? ' /d' : '', s:shellesc(a:dir), a:cmd)
 
2020 function! s:system(cmd, ...)
 
2022     let [sh, shellcmdflag, shrd] = s:chsh(1)
 
2023     let cmd = a:0 > 0 ? s:with_cd(a:cmd, a:1) : a:cmd
 
2025       let batchfile = tempname().'.bat'
 
2026       call writefile(['@echo off', cmd], batchfile)
 
2029     return system(s:is_win ? '('.cmd.')' : cmd)
 
2031     let [&shell, &shellcmdflag, &shellredir] = [sh, shellcmdflag, shrd]
 
2033       call delete(batchfile)
 
2038 function! s:system_chomp(...)
 
2039   let ret = call('s:system', a:000)
 
2040   return v:shell_error ? '' : substitute(ret, '\n$', '', '')
 
2043 function! s:git_validate(spec, check_branch)
 
2045   if isdirectory(a:spec.dir)
 
2046     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))
 
2047     let remote = result[-1]
 
2049       let err = join([remote, 'PlugClean required.'], "\n")
 
2050     elseif !s:compare_git_uri(remote, a:spec.uri)
 
2051       let err = join(['Invalid URI: '.remote,
 
2052                     \ 'Expected:    '.a:spec.uri,
 
2053                     \ 'PlugClean required.'], "\n")
 
2054     elseif a:check_branch && has_key(a:spec, 'commit')
 
2055       let result = s:lines(s:system('git rev-parse HEAD 2>&1', a:spec.dir))
 
2056       let sha = result[-1]
 
2058         let err = join(add(result, 'PlugClean required.'), "\n")
 
2059       elseif !s:hash_match(sha, a:spec.commit)
 
2060         let err = join([printf('Invalid HEAD (expected: %s, actual: %s)',
 
2061                               \ a:spec.commit[:6], sha[:6]),
 
2062                       \ 'PlugUpdate required.'], "\n")
 
2064     elseif a:check_branch
 
2065       let branch = result[0]
 
2067       if has_key(a:spec, 'tag')
 
2068         let tag = s:system_chomp('git describe --exact-match --tags HEAD 2>&1', a:spec.dir)
 
2069         if a:spec.tag !=# tag && a:spec.tag !~ '\*'
 
2070           let err = printf('Invalid tag: %s (expected: %s). Try PlugUpdate.',
 
2071                 \ (empty(tag) ? 'N/A' : tag), a:spec.tag)
 
2074       elseif a:spec.branch !=# branch
 
2075         let err = printf('Invalid branch: %s (expected: %s). Try PlugUpdate.',
 
2076               \ branch, a:spec.branch)
 
2079         let [ahead, behind] = split(s:lastline(s:system(printf(
 
2080               \ 'git rev-list --count --left-right HEAD...origin/%s',
 
2081               \ a:spec.branch), a:spec.dir)), '\t')
 
2082         if !v:shell_error && ahead
 
2084             " Only mention PlugClean if diverged, otherwise it's likely to be
 
2085             " pushable (and probably not that messed up).
 
2087                   \ "Diverged from origin/%s (%d commit(s) ahead and %d commit(s) behind!\n"
 
2088                   \ .'Backup local changes and run PlugClean and PlugUpdate to reinstall it.', a:spec.branch, ahead, behind)
 
2090             let err = printf("Ahead of origin/%s by %d commit(s).\n"
 
2091                   \ .'Cannot update until local changes are pushed.',
 
2092                   \ a:spec.branch, ahead)
 
2098     let err = 'Not found'
 
2100   return [err, err =~# 'PlugClean']
 
2103 function! s:rm_rf(dir)
 
2104   if isdirectory(a:dir)
 
2105     call s:system((s:is_win ? 'rmdir /S /Q ' : 'rm -rf ') . s:shellesc(a:dir))
 
2109 function! s:clean(force)
 
2111   call append(0, 'Searching for invalid plugins in '.g:plug_home)
 
2114   " List of valid directories
 
2117   let [cnt, total] = [0, len(g:plugs)]
 
2118   for [name, spec] in items(g:plugs)
 
2119     if !s:is_managed(name)
 
2120       call add(dirs, spec.dir)
 
2122       let [err, clean] = s:git_validate(spec, 1)
 
2124         let errs[spec.dir] = s:lines(err)[0]
 
2126         call add(dirs, spec.dir)
 
2130     call s:progress_bar(2, repeat('=', cnt), total)
 
2137     let allowed[s:dirpath(fnamemodify(dir, ':h:h'))] = 1
 
2138     let allowed[dir] = 1
 
2139     for child in s:glob_dir(dir)
 
2140       let allowed[child] = 1
 
2145   let found = sort(s:glob_dir(g:plug_home))
 
2147     let f = remove(found, 0)
 
2148     if !has_key(allowed, f) && isdirectory(f)
 
2150       call append(line('$'), '- ' . f)
 
2152         call append(line('$'), '    ' . errs[f])
 
2154       let found = filter(found, 'stridx(v:val, f) != 0')
 
2161     call append(line('$'), 'Already clean.')
 
2163     let s:clean_count = 0
 
2164     call append(3, ['Directories to delete:', ''])
 
2166     if a:force || s:ask_no_interrupt('Delete all directories?')
 
2167       call s:delete([6, line('$')], 1)
 
2169       call setline(4, 'Cancelled.')
 
2170       nnoremap <silent> <buffer> d :set opfunc=<sid>delete_op<cr>g@
 
2171       nmap     <silent> <buffer> dd d_
 
2172       xnoremap <silent> <buffer> d :<c-u>call <sid>delete_op(visualmode(), 1)<cr>
 
2173       echo 'Delete the lines (d{motion}) to delete the corresponding directories'
 
2177   setlocal nomodifiable
 
2180 function! s:delete_op(type, ...)
 
2181   call s:delete(a:0 ? [line("'<"), line("'>")] : [line("'["), line("']")], 0)
 
2184 function! s:delete(range, force)
 
2185   let [l1, l2] = a:range
 
2188     let line = getline(l1)
 
2189     if line =~ '^- ' && isdirectory(line[2:])
 
2192       let answer = force ? 1 : s:ask('Delete '.line[2:].'?', 1)
 
2193       let force = force || answer > 1
 
2195         call s:rm_rf(line[2:])
 
2197         call setline(l1, '~'.line[1:])
 
2198         let s:clean_count += 1
 
2199         call setline(4, printf('Removed %d directories.', s:clean_count))
 
2200         setlocal nomodifiable
 
2207 function! s:upgrade()
 
2208   echo 'Downloading the latest version of vim-plug'
 
2210   let tmp = tempname()
 
2211   let new = tmp . '/plug.vim'
 
2214     let out = s:system(printf('git clone --depth 1 %s %s', s:plug_src, tmp))
 
2216       return s:err('Error upgrading vim-plug: '. out)
 
2219     if readfile(s:me) ==# readfile(new)
 
2220       echo 'vim-plug is already up-to-date'
 
2223       call rename(s:me, s:me . '.old')
 
2224       call rename(new, s:me)
 
2226       echo 'vim-plug has been upgraded'
 
2230     silent! call s:rm_rf(tmp)
 
2234 function! s:upgrade_specs()
 
2235   for spec in values(g:plugs)
 
2236     let spec.frozen = get(spec, 'frozen', 0)
 
2240 function! s:status()
 
2242   call append(0, 'Checking plugins')
 
2247   let [cnt, total] = [0, len(g:plugs)]
 
2248   for [name, spec] in items(g:plugs)
 
2249     let is_dir = isdirectory(spec.dir)
 
2250     if has_key(spec, 'uri')
 
2252         let [err, _] = s:git_validate(spec, 1)
 
2253         let [valid, msg] = [empty(err), empty(err) ? 'OK' : err]
 
2255         let [valid, msg] = [0, 'Not found. Try PlugInstall.']
 
2259         let [valid, msg] = [1, 'OK']
 
2261         let [valid, msg] = [0, 'Not found.']
 
2266     " `s:loaded` entry can be missing if PlugUpgraded
 
2267     if is_dir && get(s:loaded, name, -1) == 0
 
2269       let msg .= ' (not loaded)'
 
2271     call s:progress_bar(2, repeat('=', cnt), total)
 
2272     call append(3, s:format_message(valid ? '-' : 'x', name, msg))
 
2276   call setline(1, 'Finished. '.ecnt.' error(s).')
 
2278   setlocal nomodifiable
 
2280     echo "Press 'L' on each line to load plugin, or 'U' to update"
 
2281     nnoremap <silent> <buffer> L :call <SID>status_load(line('.'))<cr>
 
2282     xnoremap <silent> <buffer> L :call <SID>status_load(line('.'))<cr>
 
2286 function! s:extract_name(str, prefix, suffix)
 
2287   return matchstr(a:str, '^'.a:prefix.' \zs[^:]\+\ze:.*'.a:suffix.'$')
 
2290 function! s:status_load(lnum)
 
2291   let line = getline(a:lnum)
 
2292   let name = s:extract_name(line, '-', '(not loaded)')
 
2294     call plug#load(name)
 
2296     call setline(a:lnum, substitute(line, ' (not loaded)$', '', ''))
 
2297     setlocal nomodifiable
 
2301 function! s:status_update() range
 
2302   let lines = getline(a:firstline, a:lastline)
 
2303   let names = filter(map(lines, 's:extract_name(v:val, "[x-]", "")'), '!empty(v:val)')
 
2306     execute 'PlugUpdate' join(names)
 
2310 function! s:is_preview_window_open()
 
2318 function! s:find_name(lnum)
 
2319   for lnum in reverse(range(1, a:lnum))
 
2320     let line = getline(lnum)
 
2324     let name = s:extract_name(line, '-', '')
 
2332 function! s:preview_commit()
 
2333   if b:plug_preview < 0
 
2334     let b:plug_preview = !s:is_preview_window_open()
 
2337   let sha = matchstr(getline('.'), '^  \X*\zs[0-9a-f]\{7,9}')
 
2342   let name = s:find_name(line('.'))
 
2343   if empty(name) || !has_key(g:plugs, name) || !isdirectory(g:plugs[name].dir)
 
2347   if exists('g:plug_pwindow') && !s:is_preview_window_open()
 
2348     execute g:plug_pwindow
 
2354   setlocal previewwindow filetype=git buftype=nofile nobuflisted modifiable
 
2356     let [sh, shellcmdflag, shrd] = s:chsh(1)
 
2357     let cmd = 'cd '.s:shellesc(g:plugs[name].dir).' && git show --no-color --pretty=medium '.sha
 
2359       let batchfile = tempname().'.bat'
 
2360       call writefile(['@echo off', cmd], batchfile)
 
2363     execute 'silent %!' cmd
 
2365     let [&shell, &shellcmdflag, &shellredir] = [sh, shellcmdflag, shrd]
 
2367       call delete(batchfile)
 
2370   setlocal nomodifiable
 
2371   nnoremap <silent> <buffer> q :q<cr>
 
2375 function! s:section(flags)
 
2376   call search('\(^[x-] \)\@<=[^:]\+:', a:flags)
 
2379 function! s:format_git_log(line)
 
2381   let tokens = split(a:line, nr2char(1))
 
2383     return indent.substitute(a:line, '\s*$', '', '')
 
2385   let [graph, sha, refs, subject, date] = tokens
 
2386   let tag = matchstr(refs, 'tag: [^,)]\+')
 
2387   let tag = empty(tag) ? ' ' : ' ('.tag.') '
 
2388   return printf('%s%s%s%s%s (%s)', indent, graph, sha, tag, subject, date)
 
2391 function! s:append_ul(lnum, text)
 
2392   call append(a:lnum, ['', a:text, repeat('-', len(a:text))])
 
2397   call append(0, ['Collecting changes ...', ''])
 
2400   let total = filter(copy(g:plugs), 's:is_managed(v:key) && isdirectory(v:val.dir)')
 
2401   call s:progress_bar(2, bar, len(total))
 
2402   for origin in [1, 0]
 
2403     let plugs = reverse(sort(items(filter(copy(total), (origin ? '' : '!').'(has_key(v:val, "commit") || has_key(v:val, "tag"))'))))
 
2407     call s:append_ul(2, origin ? 'Pending updates:' : 'Last update:')
 
2409       let range = origin ? '..origin/'.v.branch : 'HEAD@{1}..'
 
2410       let diff = s:system_chomp('git log --graph --color=never '.join(map(['--pretty=format:%x01%h%x01%d%x01%s%x01%cr', range], 's:shellesc(v:val)')), v.dir)
 
2412         let ref = has_key(v, 'tag') ? (' (tag: '.v.tag.')') : has_key(v, 'commit') ? (' '.v.commit) : ''
 
2413         call append(5, extend(['', '- '.k.':'.ref], map(s:lines(diff), 's:format_git_log(v:val)')))
 
2414         let cnts[origin] += 1
 
2417       call s:progress_bar(2, bar, len(total))
 
2422       call append(5, ['', 'N/A'])
 
2425   call setline(1, printf('%d plugin(s) updated.', cnts[0])
 
2426         \ . (cnts[1] ? printf(' %d plugin(s) have pending updates.', cnts[1]) : ''))
 
2428   if cnts[0] || cnts[1]
 
2429     nnoremap <silent> <buffer> <cr> :silent! call <SID>preview_commit()<cr>
 
2430     nnoremap <silent> <buffer> o    :silent! call <SID>preview_commit()<cr>
 
2433     nnoremap <silent> <buffer> X :call <SID>revert()<cr>
 
2434     echo "Press 'X' on each block to revert the update"
 
2437   setlocal nomodifiable
 
2440 function! s:revert()
 
2441   if search('^Pending updates', 'bnW')
 
2445   let name = s:find_name(line('.'))
 
2446   if empty(name) || !has_key(g:plugs, name) ||
 
2447     \ input(printf('Revert the update of %s? (y/N) ', name)) !~? '^y'
 
2451   call s:system('git reset --hard HEAD@{1} && git checkout '.s:esc(g:plugs[name].branch).' --', g:plugs[name].dir)
 
2454   setlocal nomodifiable
 
2458 function! s:snapshot(force, ...) abort
 
2461   call append(0, ['" Generated by vim-plug',
 
2462                 \ '" '.strftime("%c"),
 
2463                 \ '" :source this file in vim to restore the snapshot',
 
2464                 \ '" or execute: vim -S snapshot.vim',
 
2465                 \ '', '', 'PlugUpdate!'])
 
2467   let anchor = line('$') - 3
 
2468   let names = sort(keys(filter(copy(g:plugs),
 
2469         \'has_key(v:val, "uri") && !has_key(v:val, "commit") && isdirectory(v:val.dir)')))
 
2470   for name in reverse(names)
 
2471     let sha = s:system_chomp('git rev-parse --short HEAD', g:plugs[name].dir)
 
2473       call append(anchor, printf("silent! let g:plugs['%s'].commit = '%s'", name, sha))
 
2479     let fn = expand(a:1)
 
2480     if filereadable(fn) && !(a:force || s:ask(a:1.' already exists. Overwrite?'))
 
2483     call writefile(getline(1, '$'), fn)
 
2484     echo 'Saved as '.a:1
 
2485     silent execute 'e' s:esc(fn)
 
2490 function! s:split_rtp()
 
2491   return split(&rtp, '\\\@<!,')
 
2494 let s:first_rtp = s:escrtp(get(s:split_rtp(), 0, ''))
 
2495 let s:last_rtp  = s:escrtp(get(s:split_rtp(), -1, ''))
 
2497 if exists('g:plugs')
 
2498   let g:plugs_order = get(g:, 'plugs_order', keys(g:plugs))
 
2499   call s:upgrade_specs()
 
2500   call s:define_commands()
 
2503 let &cpo = s:cpo_save