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