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