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