This is the first push to this repo with my dotfiles
[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 && a:swap
868     set shell=sh shellredir=>%s\ 2>&1
869   endif
870   return prev
871 endfunction
872
873 function! s:bang(cmd, ...)
874   let batchfile = ''
875   try
876     let [sh, shellcmdflag, shrd] = s:chsh(a:0)
877     " FIXME: Escaping is incomplete. We could use shellescape with eval,
878     "        but it won't work on Windows.
879     let cmd = a:0 ? s:with_cd(a:cmd, a:1) : a:cmd
880     if s:is_win
881       let [batchfile, cmd] = s:batchfile(cmd)
882     endif
883     let g:_plug_bang = (s:is_win && has('gui_running') ? 'silent ' : '').'!'.escape(cmd, '#!%')
884     execute "normal! :execute g:_plug_bang\<cr>\<cr>"
885   finally
886     unlet g:_plug_bang
887     let [&shell, &shellcmdflag, &shellredir] = [sh, shellcmdflag, shrd]
888     if s:is_win && filereadable(batchfile)
889       call delete(batchfile)
890     endif
891   endtry
892   return v:shell_error ? 'Exit status: ' . v:shell_error : ''
893 endfunction
894
895 function! s:regress_bar()
896   let bar = substitute(getline(2)[1:-2], '.*\zs=', 'x', '')
897   call s:progress_bar(2, bar, len(bar))
898 endfunction
899
900 function! s:is_updated(dir)
901   return !empty(s:system_chomp('git log --pretty=format:"%h" "HEAD...HEAD@{1}"', a:dir))
902 endfunction
903
904 function! s:do(pull, force, todo)
905   for [name, spec] in items(a:todo)
906     if !isdirectory(spec.dir)
907       continue
908     endif
909     let installed = has_key(s:update.new, name)
910     let updated = installed ? 0 :
911       \ (a:pull && index(s:update.errors, name) < 0 && s:is_updated(spec.dir))
912     if a:force || installed || updated
913       execute 'cd' s:esc(spec.dir)
914       call append(3, '- Post-update hook for '. name .' ... ')
915       let error = ''
916       let type = type(spec.do)
917       if type == s:TYPE.string
918         if spec.do[0] == ':'
919           if !get(s:loaded, name, 0)
920             let s:loaded[name] = 1
921             call s:reorg_rtp()
922           endif
923           call s:load_plugin(spec)
924           try
925             execute spec.do[1:]
926           catch
927             let error = v:exception
928           endtry
929           if !s:plug_window_exists()
930             cd -
931             throw 'Warning: vim-plug was terminated by the post-update hook of '.name
932           endif
933         else
934           let error = s:bang(spec.do)
935         endif
936       elseif type == s:TYPE.funcref
937         try
938           let status = installed ? 'installed' : (updated ? 'updated' : 'unchanged')
939           call spec.do({ 'name': name, 'status': status, 'force': a:force })
940         catch
941           let error = v:exception
942         endtry
943       else
944         let error = 'Invalid hook type'
945       endif
946       call s:switch_in()
947       call setline(4, empty(error) ? (getline(4) . 'OK')
948                                  \ : ('x' . getline(4)[1:] . error))
949       if !empty(error)
950         call add(s:update.errors, name)
951         call s:regress_bar()
952       endif
953       cd -
954     endif
955   endfor
956 endfunction
957
958 function! s:hash_match(a, b)
959   return stridx(a:a, a:b) == 0 || stridx(a:b, a:a) == 0
960 endfunction
961
962 function! s:checkout(spec)
963   let sha = a:spec.commit
964   let output = s:system('git rev-parse HEAD', a:spec.dir)
965   if !v:shell_error && !s:hash_match(sha, s:lines(output)[0])
966     let output = s:system(
967           \ 'git fetch --depth 999999 && git checkout '.plug#shellescape(sha).' --', a:spec.dir)
968   endif
969   return output
970 endfunction
971
972 function! s:finish(pull)
973   let new_frozen = len(filter(keys(s:update.new), 'g:plugs[v:val].frozen'))
974   if new_frozen
975     let s = new_frozen > 1 ? 's' : ''
976     call append(3, printf('- Installed %d frozen plugin%s', new_frozen, s))
977   endif
978   call append(3, '- Finishing ... ') | 4
979   redraw
980   call plug#helptags()
981   call plug#end()
982   call setline(4, getline(4) . 'Done!')
983   redraw
984   let msgs = []
985   if !empty(s:update.errors)
986     call add(msgs, "Press 'R' to retry.")
987   endif
988   if a:pull && len(s:update.new) < len(filter(getline(5, '$'),
989                 \ "v:val =~ '^- ' && v:val !~# 'Already up.to.date'"))
990     call add(msgs, "Press 'D' to see the updated changes.")
991   endif
992   echo join(msgs, ' ')
993   call s:finish_bindings()
994 endfunction
995
996 function! s:retry()
997   if empty(s:update.errors)
998     return
999   endif
1000   echo
1001   call s:update_impl(s:update.pull, s:update.force,
1002         \ extend(copy(s:update.errors), [s:update.threads]))
1003 endfunction
1004
1005 function! s:is_managed(name)
1006   return has_key(g:plugs[a:name], 'uri')
1007 endfunction
1008
1009 function! s:names(...)
1010   return sort(filter(keys(g:plugs), 'stridx(v:val, a:1) == 0 && s:is_managed(v:val)'))
1011 endfunction
1012
1013 function! s:check_ruby()
1014   silent! ruby require 'thread'; VIM::command("let g:plug_ruby = '#{RUBY_VERSION}'")
1015   if !exists('g:plug_ruby')
1016     redraw!
1017     return s:warn('echom', 'Warning: Ruby interface is broken')
1018   endif
1019   let ruby_version = split(g:plug_ruby, '\.')
1020   unlet g:plug_ruby
1021   return s:version_requirement(ruby_version, [1, 8, 7])
1022 endfunction
1023
1024 function! s:update_impl(pull, force, args) abort
1025   let sync = index(a:args, '--sync') >= 0 || has('vim_starting')
1026   let args = filter(copy(a:args), 'v:val != "--sync"')
1027   let threads = (len(args) > 0 && args[-1] =~ '^[1-9][0-9]*$') ?
1028                   \ remove(args, -1) : get(g:, 'plug_threads', 16)
1029
1030   let managed = filter(copy(g:plugs), 's:is_managed(v:key)')
1031   let todo = empty(args) ? filter(managed, '!v:val.frozen || !isdirectory(v:val.dir)') :
1032                          \ filter(managed, 'index(args, v:key) >= 0')
1033
1034   if empty(todo)
1035     return s:warn('echo', 'No plugin to '. (a:pull ? 'update' : 'install'))
1036   endif
1037
1038   if !s:is_win && s:git_version_requirement(2, 3)
1039     let s:git_terminal_prompt = exists('$GIT_TERMINAL_PROMPT') ? $GIT_TERMINAL_PROMPT : ''
1040     let $GIT_TERMINAL_PROMPT = 0
1041     for plug in values(todo)
1042       let plug.uri = substitute(plug.uri,
1043             \ '^https://git::@github\.com', 'https://github.com', '')
1044     endfor
1045   endif
1046
1047   if !isdirectory(g:plug_home)
1048     try
1049       call mkdir(g:plug_home, 'p')
1050     catch
1051       return s:err(printf('Invalid plug directory: %s. '.
1052               \ 'Try to call plug#begin with a valid directory', g:plug_home))
1053     endtry
1054   endif
1055
1056   if has('nvim') && !exists('*jobwait') && threads > 1
1057     call s:warn('echom', '[vim-plug] Update Neovim for parallel installer')
1058   endif
1059
1060   let use_job = s:nvim || s:vim8
1061   let python = (has('python') || has('python3')) && !use_job
1062   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()
1063
1064   let s:update = {
1065     \ 'start':   reltime(),
1066     \ 'all':     todo,
1067     \ 'todo':    copy(todo),
1068     \ 'errors':  [],
1069     \ 'pull':    a:pull,
1070     \ 'force':   a:force,
1071     \ 'new':     {},
1072     \ 'threads': (python || ruby || use_job) ? min([len(todo), threads]) : 1,
1073     \ 'bar':     '',
1074     \ 'fin':     0
1075   \ }
1076
1077   call s:prepare(1)
1078   call append(0, ['', ''])
1079   normal! 2G
1080   silent! redraw
1081
1082   let s:clone_opt = get(g:, 'plug_shallow', 1) ?
1083         \ '--depth 1' . (s:git_version_requirement(1, 7, 10) ? ' --no-single-branch' : '') : ''
1084
1085   if has('win32unix') || has('wsl')
1086     let s:clone_opt .= ' -c core.eol=lf -c core.autocrlf=input'
1087   endif
1088
1089   let s:submodule_opt = s:git_version_requirement(2, 8) ? ' --jobs='.threads : ''
1090
1091   " Python version requirement (>= 2.7)
1092   if python && !has('python3') && !ruby && !use_job && s:update.threads > 1
1093     redir => pyv
1094     silent python import platform; print platform.python_version()
1095     redir END
1096     let python = s:version_requirement(
1097           \ map(split(split(pyv)[0], '\.'), 'str2nr(v:val)'), [2, 6])
1098   endif
1099
1100   if (python || ruby) && s:update.threads > 1
1101     try
1102       let imd = &imd
1103       if s:mac_gui
1104         set noimd
1105       endif
1106       if ruby
1107         call s:update_ruby()
1108       else
1109         call s:update_python()
1110       endif
1111     catch
1112       let lines = getline(4, '$')
1113       let printed = {}
1114       silent! 4,$d _
1115       for line in lines
1116         let name = s:extract_name(line, '.', '')
1117         if empty(name) || !has_key(printed, name)
1118           call append('$', line)
1119           if !empty(name)
1120             let printed[name] = 1
1121             if line[0] == 'x' && index(s:update.errors, name) < 0
1122               call add(s:update.errors, name)
1123             end
1124           endif
1125         endif
1126       endfor
1127     finally
1128       let &imd = imd
1129       call s:update_finish()
1130     endtry
1131   else
1132     call s:update_vim()
1133     while use_job && sync
1134       sleep 100m
1135       if s:update.fin
1136         break
1137       endif
1138     endwhile
1139   endif
1140 endfunction
1141
1142 function! s:log4(name, msg)
1143   call setline(4, printf('- %s (%s)', a:msg, a:name))
1144   redraw
1145 endfunction
1146
1147 function! s:update_finish()
1148   if exists('s:git_terminal_prompt')
1149     let $GIT_TERMINAL_PROMPT = s:git_terminal_prompt
1150   endif
1151   if s:switch_in()
1152     call append(3, '- Updating ...') | 4
1153     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))'))
1154       let [pos, _] = s:logpos(name)
1155       if !pos
1156         continue
1157       endif
1158       if has_key(spec, 'commit')
1159         call s:log4(name, 'Checking out '.spec.commit)
1160         let out = s:checkout(spec)
1161       elseif has_key(spec, 'tag')
1162         let tag = spec.tag
1163         if tag =~ '\*'
1164           let tags = s:lines(s:system('git tag --list '.plug#shellescape(tag).' --sort -version:refname 2>&1', spec.dir))
1165           if !v:shell_error && !empty(tags)
1166             let tag = tags[0]
1167             call s:log4(name, printf('Latest tag for %s -> %s', spec.tag, tag))
1168             call append(3, '')
1169           endif
1170         endif
1171         call s:log4(name, 'Checking out '.tag)
1172         let out = s:system('git checkout -q '.plug#shellescape(tag).' -- 2>&1', spec.dir)
1173       else
1174         let branch = get(spec, 'branch', 'master')
1175         call s:log4(name, 'Merging origin/'.s:esc(branch))
1176         let out = s:system('git checkout -q '.plug#shellescape(branch).' -- 2>&1'
1177               \. (has_key(s:update.new, name) ? '' : ('&& git merge --ff-only '.plug#shellescape('origin/'.branch).' 2>&1')), spec.dir)
1178       endif
1179       if !v:shell_error && filereadable(spec.dir.'/.gitmodules') &&
1180             \ (s:update.force || has_key(s:update.new, name) || s:is_updated(spec.dir))
1181         call s:log4(name, 'Updating submodules. This may take a while.')
1182         let out .= s:bang('git submodule update --init --recursive'.s:submodule_opt.' 2>&1', spec.dir)
1183       endif
1184       let msg = s:format_message(v:shell_error ? 'x': '-', name, out)
1185       if v:shell_error
1186         call add(s:update.errors, name)
1187         call s:regress_bar()
1188         silent execute pos 'd _'
1189         call append(4, msg) | 4
1190       elseif !empty(out)
1191         call setline(pos, msg[0])
1192       endif
1193       redraw
1194     endfor
1195     silent 4 d _
1196     try
1197       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")'))
1198     catch
1199       call s:warn('echom', v:exception)
1200       call s:warn('echo', '')
1201       return
1202     endtry
1203     call s:finish(s:update.pull)
1204     call setline(1, 'Updated. Elapsed time: ' . split(reltimestr(reltime(s:update.start)))[0] . ' sec.')
1205     call s:switch_out('normal! gg')
1206   endif
1207 endfunction
1208
1209 function! s:job_abort()
1210   if (!s:nvim && !s:vim8) || !exists('s:jobs')
1211     return
1212   endif
1213
1214   for [name, j] in items(s:jobs)
1215     if s:nvim
1216       silent! call jobstop(j.jobid)
1217     elseif s:vim8
1218       silent! call job_stop(j.jobid)
1219     endif
1220     if j.new
1221       call s:rm_rf(g:plugs[name].dir)
1222     endif
1223   endfor
1224   let s:jobs = {}
1225 endfunction
1226
1227 function! s:last_non_empty_line(lines)
1228   let len = len(a:lines)
1229   for idx in range(len)
1230     let line = a:lines[len-idx-1]
1231     if !empty(line)
1232       return line
1233     endif
1234   endfor
1235   return ''
1236 endfunction
1237
1238 function! s:job_out_cb(self, data) abort
1239   let self = a:self
1240   let data = remove(self.lines, -1) . a:data
1241   let lines = map(split(data, "\n", 1), 'split(v:val, "\r", 1)[-1]')
1242   call extend(self.lines, lines)
1243   " To reduce the number of buffer updates
1244   let self.tick = get(self, 'tick', -1) + 1
1245   if !self.running || self.tick % len(s:jobs) == 0
1246     let bullet = self.running ? (self.new ? '+' : '*') : (self.error ? 'x' : '-')
1247     let result = self.error ? join(self.lines, "\n") : s:last_non_empty_line(self.lines)
1248     call s:log(bullet, self.name, result)
1249   endif
1250 endfunction
1251
1252 function! s:job_exit_cb(self, data) abort
1253   let a:self.running = 0
1254   let a:self.error = a:data != 0
1255   call s:reap(a:self.name)
1256   call s:tick()
1257 endfunction
1258
1259 function! s:job_cb(fn, job, ch, data)
1260   if !s:plug_window_exists() " plug window closed
1261     return s:job_abort()
1262   endif
1263   call call(a:fn, [a:job, a:data])
1264 endfunction
1265
1266 function! s:nvim_cb(job_id, data, event) dict abort
1267   return a:event == 'stdout' ?
1268     \ s:job_cb('s:job_out_cb',  self, 0, join(a:data, "\n")) :
1269     \ s:job_cb('s:job_exit_cb', self, 0, a:data)
1270 endfunction
1271
1272 function! s:spawn(name, cmd, opts)
1273   let job = { 'name': a:name, 'running': 1, 'error': 0, 'lines': [''],
1274             \ 'new': get(a:opts, 'new', 0) }
1275   let s:jobs[a:name] = job
1276   let cmd = has_key(a:opts, 'dir') ? s:with_cd(a:cmd, a:opts.dir, 0) : a:cmd
1277   let argv = s:is_win ? ['cmd', '/s', '/c', '"'.cmd.'"'] : ['sh', '-c', cmd]
1278
1279   if s:nvim
1280     call extend(job, {
1281     \ 'on_stdout': function('s:nvim_cb'),
1282     \ 'on_exit':   function('s:nvim_cb'),
1283     \ })
1284     let jid = s:plug_call('jobstart', argv, job)
1285     if jid > 0
1286       let job.jobid = jid
1287     else
1288       let job.running = 0
1289       let job.error   = 1
1290       let job.lines   = [jid < 0 ? argv[0].' is not executable' :
1291             \ 'Invalid arguments (or job table is full)']
1292     endif
1293   elseif s:vim8
1294     let jid = job_start(s:is_win ? join(argv, ' ') : argv, {
1295     \ 'out_cb':   function('s:job_cb', ['s:job_out_cb',  job]),
1296     \ 'exit_cb':  function('s:job_cb', ['s:job_exit_cb', job]),
1297     \ 'out_mode': 'raw'
1298     \})
1299     if job_status(jid) == 'run'
1300       let job.jobid = jid
1301     else
1302       let job.running = 0
1303       let job.error   = 1
1304       let job.lines   = ['Failed to start job']
1305     endif
1306   else
1307     let job.lines = s:lines(call('s:system', [cmd]))
1308     let job.error = v:shell_error != 0
1309     let job.running = 0
1310   endif
1311 endfunction
1312
1313 function! s:reap(name)
1314   let job = s:jobs[a:name]
1315   if job.error
1316     call add(s:update.errors, a:name)
1317   elseif get(job, 'new', 0)
1318     let s:update.new[a:name] = 1
1319   endif
1320   let s:update.bar .= job.error ? 'x' : '='
1321
1322   let bullet = job.error ? 'x' : '-'
1323   let result = job.error ? join(job.lines, "\n") : s:last_non_empty_line(job.lines)
1324   call s:log(bullet, a:name, empty(result) ? 'OK' : result)
1325   call s:bar()
1326
1327   call remove(s:jobs, a:name)
1328 endfunction
1329
1330 function! s:bar()
1331   if s:switch_in()
1332     let total = len(s:update.all)
1333     call setline(1, (s:update.pull ? 'Updating' : 'Installing').
1334           \ ' plugins ('.len(s:update.bar).'/'.total.')')
1335     call s:progress_bar(2, s:update.bar, total)
1336     call s:switch_out()
1337   endif
1338 endfunction
1339
1340 function! s:logpos(name)
1341   let max = line('$')
1342   for i in range(4, max > 4 ? max : 4)
1343     if getline(i) =~# '^[-+x*] '.a:name.':'
1344       for j in range(i + 1, max > 5 ? max : 5)
1345         if getline(j) !~ '^ '
1346           return [i, j - 1]
1347         endif
1348       endfor
1349       return [i, i]
1350     endif
1351   endfor
1352   return [0, 0]
1353 endfunction
1354
1355 function! s:log(bullet, name, lines)
1356   if s:switch_in()
1357     let [b, e] = s:logpos(a:name)
1358     if b > 0
1359       silent execute printf('%d,%d d _', b, e)
1360       if b > winheight('.')
1361         let b = 4
1362       endif
1363     else
1364       let b = 4
1365     endif
1366     " FIXME For some reason, nomodifiable is set after :d in vim8
1367     setlocal modifiable
1368     call append(b - 1, s:format_message(a:bullet, a:name, a:lines))
1369     call s:switch_out()
1370   endif
1371 endfunction
1372
1373 function! s:update_vim()
1374   let s:jobs = {}
1375
1376   call s:bar()
1377   call s:tick()
1378 endfunction
1379
1380 function! s:tick()
1381   let pull = s:update.pull
1382   let prog = s:progress_opt(s:nvim || s:vim8)
1383 while 1 " Without TCO, Vim stack is bound to explode
1384   if empty(s:update.todo)
1385     if empty(s:jobs) && !s:update.fin
1386       call s:update_finish()
1387       let s:update.fin = 1
1388     endif
1389     return
1390   endif
1391
1392   let name = keys(s:update.todo)[0]
1393   let spec = remove(s:update.todo, name)
1394   let new  = empty(globpath(spec.dir, '.git', 1))
1395
1396   call s:log(new ? '+' : '*', name, pull ? 'Updating ...' : 'Installing ...')
1397   redraw
1398
1399   let has_tag = has_key(spec, 'tag')
1400   if !new
1401     let [error, _] = s:git_validate(spec, 0)
1402     if empty(error)
1403       if pull
1404         let fetch_opt = (has_tag && !empty(globpath(spec.dir, '.git/shallow'))) ? '--depth 99999999' : ''
1405         call s:spawn(name, printf('git fetch %s %s 2>&1', fetch_opt, prog), { 'dir': spec.dir })
1406       else
1407         let s:jobs[name] = { 'running': 0, 'lines': ['Already installed'], 'error': 0 }
1408       endif
1409     else
1410       let s:jobs[name] = { 'running': 0, 'lines': s:lines(error), 'error': 1 }
1411     endif
1412   else
1413     call s:spawn(name,
1414           \ printf('git clone %s %s %s %s 2>&1',
1415           \ has_tag ? '' : s:clone_opt,
1416           \ prog,
1417           \ plug#shellescape(spec.uri, {'script': 0}),
1418           \ plug#shellescape(s:trim(spec.dir), {'script': 0})), { 'new': 1 })
1419   endif
1420
1421   if !s:jobs[name].running
1422     call s:reap(name)
1423   endif
1424   if len(s:jobs) >= s:update.threads
1425     break
1426   endif
1427 endwhile
1428 endfunction
1429
1430 function! s:update_python()
1431 let py_exe = has('python') ? 'python' : 'python3'
1432 execute py_exe "<< EOF"
1433 import datetime
1434 import functools
1435 import os
1436 try:
1437   import queue
1438 except ImportError:
1439   import Queue as queue
1440 import random
1441 import re
1442 import shutil
1443 import signal
1444 import subprocess
1445 import tempfile
1446 import threading as thr
1447 import time
1448 import traceback
1449 import vim
1450
1451 G_NVIM = vim.eval("has('nvim')") == '1'
1452 G_PULL = vim.eval('s:update.pull') == '1'
1453 G_RETRIES = int(vim.eval('get(g:, "plug_retries", 2)')) + 1
1454 G_TIMEOUT = int(vim.eval('get(g:, "plug_timeout", 60)'))
1455 G_CLONE_OPT = vim.eval('s:clone_opt')
1456 G_PROGRESS = vim.eval('s:progress_opt(1)')
1457 G_LOG_PROB = 1.0 / int(vim.eval('s:update.threads'))
1458 G_STOP = thr.Event()
1459 G_IS_WIN = vim.eval('s:is_win') == '1'
1460
1461 class PlugError(Exception):
1462   def __init__(self, msg):
1463     self.msg = msg
1464 class CmdTimedOut(PlugError):
1465   pass
1466 class CmdFailed(PlugError):
1467   pass
1468 class InvalidURI(PlugError):
1469   pass
1470 class Action(object):
1471   INSTALL, UPDATE, ERROR, DONE = ['+', '*', 'x', '-']
1472
1473 class Buffer(object):
1474   def __init__(self, lock, num_plugs, is_pull):
1475     self.bar = ''
1476     self.event = 'Updating' if is_pull else 'Installing'
1477     self.lock = lock
1478     self.maxy = int(vim.eval('winheight(".")'))
1479     self.num_plugs = num_plugs
1480
1481   def __where(self, name):
1482     """ Find first line with name in current buffer. Return line num. """
1483     found, lnum = False, 0
1484     matcher = re.compile('^[-+x*] {0}:'.format(name))
1485     for line in vim.current.buffer:
1486       if matcher.search(line) is not None:
1487         found = True
1488         break
1489       lnum += 1
1490
1491     if not found:
1492       lnum = -1
1493     return lnum
1494
1495   def header(self):
1496     curbuf = vim.current.buffer
1497     curbuf[0] = self.event + ' plugins ({0}/{1})'.format(len(self.bar), self.num_plugs)
1498
1499     num_spaces = self.num_plugs - len(self.bar)
1500     curbuf[1] = '[{0}{1}]'.format(self.bar, num_spaces * ' ')
1501
1502     with self.lock:
1503       vim.command('normal! 2G')
1504       vim.command('redraw')
1505
1506   def write(self, action, name, lines):
1507     first, rest = lines[0], lines[1:]
1508     msg = ['{0} {1}{2}{3}'.format(action, name, ': ' if first else '', first)]
1509     msg.extend(['    ' + line for line in rest])
1510
1511     try:
1512       if action == Action.ERROR:
1513         self.bar += 'x'
1514         vim.command("call add(s:update.errors, '{0}')".format(name))
1515       elif action == Action.DONE:
1516         self.bar += '='
1517
1518       curbuf = vim.current.buffer
1519       lnum = self.__where(name)
1520       if lnum != -1: # Found matching line num
1521         del curbuf[lnum]
1522         if lnum > self.maxy and action in set([Action.INSTALL, Action.UPDATE]):
1523           lnum = 3
1524       else:
1525         lnum = 3
1526       curbuf.append(msg, lnum)
1527
1528       self.header()
1529     except vim.error:
1530       pass
1531
1532 class Command(object):
1533   CD = 'cd /d' if G_IS_WIN else 'cd'
1534
1535   def __init__(self, cmd, cmd_dir=None, timeout=60, cb=None, clean=None):
1536     self.cmd = cmd
1537     if cmd_dir:
1538       self.cmd = '{0} {1} && {2}'.format(Command.CD, cmd_dir, self.cmd)
1539     self.timeout = timeout
1540     self.callback = cb if cb else (lambda msg: None)
1541     self.clean = clean if clean else (lambda: None)
1542     self.proc = None
1543
1544   @property
1545   def alive(self):
1546     """ Returns true only if command still running. """
1547     return self.proc and self.proc.poll() is None
1548
1549   def execute(self, ntries=3):
1550     """ Execute the command with ntries if CmdTimedOut.
1551         Returns the output of the command if no Exception.
1552     """
1553     attempt, finished, limit = 0, False, self.timeout
1554
1555     while not finished:
1556       try:
1557         attempt += 1
1558         result = self.try_command()
1559         finished = True
1560         return result
1561       except CmdTimedOut:
1562         if attempt != ntries:
1563           self.notify_retry()
1564           self.timeout += limit
1565         else:
1566           raise
1567
1568   def notify_retry(self):
1569     """ Retry required for command, notify user. """
1570     for count in range(3, 0, -1):
1571       if G_STOP.is_set():
1572         raise KeyboardInterrupt
1573       msg = 'Timeout. Will retry in {0} second{1} ...'.format(
1574             count, 's' if count != 1 else '')
1575       self.callback([msg])
1576       time.sleep(1)
1577     self.callback(['Retrying ...'])
1578
1579   def try_command(self):
1580     """ Execute a cmd & poll for callback. Returns list of output.
1581         Raises CmdFailed   -> return code for Popen isn't 0
1582         Raises CmdTimedOut -> command exceeded timeout without new output
1583     """
1584     first_line = True
1585
1586     try:
1587       tfile = tempfile.NamedTemporaryFile(mode='w+b')
1588       preexec_fn = not G_IS_WIN and os.setsid or None
1589       self.proc = subprocess.Popen(self.cmd, stdout=tfile,
1590                                    stderr=subprocess.STDOUT,
1591                                    stdin=subprocess.PIPE, shell=True,
1592                                    preexec_fn=preexec_fn)
1593       thrd = thr.Thread(target=(lambda proc: proc.wait()), args=(self.proc,))
1594       thrd.start()
1595
1596       thread_not_started = True
1597       while thread_not_started:
1598         try:
1599           thrd.join(0.1)
1600           thread_not_started = False
1601         except RuntimeError:
1602           pass
1603
1604       while self.alive:
1605         if G_STOP.is_set():
1606           raise KeyboardInterrupt
1607
1608         if first_line or random.random() < G_LOG_PROB:
1609           first_line = False
1610           line = '' if G_IS_WIN else nonblock_read(tfile.name)
1611           if line:
1612             self.callback([line])
1613
1614         time_diff = time.time() - os.path.getmtime(tfile.name)
1615         if time_diff > self.timeout:
1616           raise CmdTimedOut(['Timeout!'])
1617
1618         thrd.join(0.5)
1619
1620       tfile.seek(0)
1621       result = [line.decode('utf-8', 'replace').rstrip() for line in tfile]
1622
1623       if self.proc.returncode != 0:
1624         raise CmdFailed([''] + result)
1625
1626       return result
1627     except:
1628       self.terminate()
1629       raise
1630
1631   def terminate(self):
1632     """ Terminate process and cleanup. """
1633     if self.alive:
1634       if G_IS_WIN:
1635         os.kill(self.proc.pid, signal.SIGINT)
1636       else:
1637         os.killpg(self.proc.pid, signal.SIGTERM)
1638     self.clean()
1639
1640 class Plugin(object):
1641   def __init__(self, name, args, buf_q, lock):
1642     self.name = name
1643     self.args = args
1644     self.buf_q = buf_q
1645     self.lock = lock
1646     self.tag = args.get('tag', 0)
1647
1648   def manage(self):
1649     try:
1650       if os.path.exists(self.args['dir']):
1651         self.update()
1652       else:
1653         self.install()
1654         with self.lock:
1655           thread_vim_command("let s:update.new['{0}'] = 1".format(self.name))
1656     except PlugError as exc:
1657       self.write(Action.ERROR, self.name, exc.msg)
1658     except KeyboardInterrupt:
1659       G_STOP.set()
1660       self.write(Action.ERROR, self.name, ['Interrupted!'])
1661     except:
1662       # Any exception except those above print stack trace
1663       msg = 'Trace:\n{0}'.format(traceback.format_exc().rstrip())
1664       self.write(Action.ERROR, self.name, msg.split('\n'))
1665       raise
1666
1667   def install(self):
1668     target = self.args['dir']
1669     if target[-1] == '\\':
1670       target = target[0:-1]
1671
1672     def clean(target):
1673       def _clean():
1674         try:
1675           shutil.rmtree(target)
1676         except OSError:
1677           pass
1678       return _clean
1679
1680     self.write(Action.INSTALL, self.name, ['Installing ...'])
1681     callback = functools.partial(self.write, Action.INSTALL, self.name)
1682     cmd = 'git clone {0} {1} {2} {3} 2>&1'.format(
1683           '' if self.tag else G_CLONE_OPT, G_PROGRESS, self.args['uri'],
1684           esc(target))
1685     com = Command(cmd, None, G_TIMEOUT, callback, clean(target))
1686     result = com.execute(G_RETRIES)
1687     self.write(Action.DONE, self.name, result[-1:])
1688
1689   def repo_uri(self):
1690     cmd = 'git rev-parse --abbrev-ref HEAD 2>&1 && git config -f .git/config remote.origin.url'
1691     command = Command(cmd, self.args['dir'], G_TIMEOUT,)
1692     result = command.execute(G_RETRIES)
1693     return result[-1]
1694
1695   def update(self):
1696     actual_uri = self.repo_uri()
1697     expect_uri = self.args['uri']
1698     regex = re.compile(r'^(?:\w+://)?(?:[^@/]*@)?([^:/]*(?::[0-9]*)?)[:/](.*?)(?:\.git)?/?$')
1699     ma = regex.match(actual_uri)
1700     mb = regex.match(expect_uri)
1701     if ma is None or mb is None or ma.groups() != mb.groups():
1702       msg = ['',
1703              'Invalid URI: {0}'.format(actual_uri),
1704              'Expected     {0}'.format(expect_uri),
1705              'PlugClean required.']
1706       raise InvalidURI(msg)
1707
1708     if G_PULL:
1709       self.write(Action.UPDATE, self.name, ['Updating ...'])
1710       callback = functools.partial(self.write, Action.UPDATE, self.name)
1711       fetch_opt = '--depth 99999999' if self.tag and os.path.isfile(os.path.join(self.args['dir'], '.git/shallow')) else ''
1712       cmd = 'git fetch {0} {1} 2>&1'.format(fetch_opt, G_PROGRESS)
1713       com = Command(cmd, self.args['dir'], G_TIMEOUT, callback)
1714       result = com.execute(G_RETRIES)
1715       self.write(Action.DONE, self.name, result[-1:])
1716     else:
1717       self.write(Action.DONE, self.name, ['Already installed'])
1718
1719   def write(self, action, name, msg):
1720     self.buf_q.put((action, name, msg))
1721
1722 class PlugThread(thr.Thread):
1723   def __init__(self, tname, args):
1724     super(PlugThread, self).__init__()
1725     self.tname = tname
1726     self.args = args
1727
1728   def run(self):
1729     thr.current_thread().name = self.tname
1730     buf_q, work_q, lock = self.args
1731
1732     try:
1733       while not G_STOP.is_set():
1734         name, args = work_q.get_nowait()
1735         plug = Plugin(name, args, buf_q, lock)
1736         plug.manage()
1737         work_q.task_done()
1738     except queue.Empty:
1739       pass
1740
1741 class RefreshThread(thr.Thread):
1742   def __init__(self, lock):
1743     super(RefreshThread, self).__init__()
1744     self.lock = lock
1745     self.running = True
1746
1747   def run(self):
1748     while self.running:
1749       with self.lock:
1750         thread_vim_command('noautocmd normal! a')
1751       time.sleep(0.33)
1752
1753   def stop(self):
1754     self.running = False
1755
1756 if G_NVIM:
1757   def thread_vim_command(cmd):
1758     vim.session.threadsafe_call(lambda: vim.command(cmd))
1759 else:
1760   def thread_vim_command(cmd):
1761     vim.command(cmd)
1762
1763 def esc(name):
1764   return '"' + name.replace('"', '\"') + '"'
1765
1766 def nonblock_read(fname):
1767   """ Read a file with nonblock flag. Return the last line. """
1768   fread = os.open(fname, os.O_RDONLY | os.O_NONBLOCK)
1769   buf = os.read(fread, 100000).decode('utf-8', 'replace')
1770   os.close(fread)
1771
1772   line = buf.rstrip('\r\n')
1773   left = max(line.rfind('\r'), line.rfind('\n'))
1774   if left != -1:
1775     left += 1
1776     line = line[left:]
1777
1778   return line
1779
1780 def main():
1781   thr.current_thread().name = 'main'
1782   nthreads = int(vim.eval('s:update.threads'))
1783   plugs = vim.eval('s:update.todo')
1784   mac_gui = vim.eval('s:mac_gui') == '1'
1785
1786   lock = thr.Lock()
1787   buf = Buffer(lock, len(plugs), G_PULL)
1788   buf_q, work_q = queue.Queue(), queue.Queue()
1789   for work in plugs.items():
1790     work_q.put(work)
1791
1792   start_cnt = thr.active_count()
1793   for num in range(nthreads):
1794     tname = 'PlugT-{0:02}'.format(num)
1795     thread = PlugThread(tname, (buf_q, work_q, lock))
1796     thread.start()
1797   if mac_gui:
1798     rthread = RefreshThread(lock)
1799     rthread.start()
1800
1801   while not buf_q.empty() or thr.active_count() != start_cnt:
1802     try:
1803       action, name, msg = buf_q.get(True, 0.25)
1804       buf.write(action, name, ['OK'] if not msg else msg)
1805       buf_q.task_done()
1806     except queue.Empty:
1807       pass
1808     except KeyboardInterrupt:
1809       G_STOP.set()
1810
1811   if mac_gui:
1812     rthread.stop()
1813     rthread.join()
1814
1815 main()
1816 EOF
1817 endfunction
1818
1819 function! s:update_ruby()
1820   ruby << EOF
1821   module PlugStream
1822     SEP = ["\r", "\n", nil]
1823     def get_line
1824       buffer = ''
1825       loop do
1826         char = readchar rescue return
1827         if SEP.include? char.chr
1828           buffer << $/
1829           break
1830         else
1831           buffer << char
1832         end
1833       end
1834       buffer
1835     end
1836   end unless defined?(PlugStream)
1837
1838   def esc arg
1839     %["#{arg.gsub('"', '\"')}"]
1840   end
1841
1842   def killall pid
1843     pids = [pid]
1844     if /mswin|mingw|bccwin/ =~ RUBY_PLATFORM
1845       pids.each { |pid| Process.kill 'INT', pid.to_i rescue nil }
1846     else
1847       unless `which pgrep 2> /dev/null`.empty?
1848         children = pids
1849         until children.empty?
1850           children = children.map { |pid|
1851             `pgrep -P #{pid}`.lines.map { |l| l.chomp }
1852           }.flatten
1853           pids += children
1854         end
1855       end
1856       pids.each { |pid| Process.kill 'TERM', pid.to_i rescue nil }
1857     end
1858   end
1859
1860   def compare_git_uri a, b
1861     regex = %r{^(?:\w+://)?(?:[^@/]*@)?([^:/]*(?::[0-9]*)?)[:/](.*?)(?:\.git)?/?$}
1862     regex.match(a).to_a.drop(1) == regex.match(b).to_a.drop(1)
1863   end
1864
1865   require 'thread'
1866   require 'fileutils'
1867   require 'timeout'
1868   running = true
1869   iswin = VIM::evaluate('s:is_win').to_i == 1
1870   pull  = VIM::evaluate('s:update.pull').to_i == 1
1871   base  = VIM::evaluate('g:plug_home')
1872   all   = VIM::evaluate('s:update.todo')
1873   limit = VIM::evaluate('get(g:, "plug_timeout", 60)')
1874   tries = VIM::evaluate('get(g:, "plug_retries", 2)') + 1
1875   nthr  = VIM::evaluate('s:update.threads').to_i
1876   maxy  = VIM::evaluate('winheight(".")').to_i
1877   vim7  = VIM::evaluate('v:version').to_i <= 703 && RUBY_PLATFORM =~ /darwin/
1878   cd    = iswin ? 'cd /d' : 'cd'
1879   tot   = VIM::evaluate('len(s:update.todo)') || 0
1880   bar   = ''
1881   skip  = 'Already installed'
1882   mtx   = Mutex.new
1883   take1 = proc { mtx.synchronize { running && all.shift } }
1884   logh  = proc {
1885     cnt = bar.length
1886     $curbuf[1] = "#{pull ? 'Updating' : 'Installing'} plugins (#{cnt}/#{tot})"
1887     $curbuf[2] = '[' + bar.ljust(tot) + ']'
1888     VIM::command('normal! 2G')
1889     VIM::command('redraw')
1890   }
1891   where = proc { |name| (1..($curbuf.length)).find { |l| $curbuf[l] =~ /^[-+x*] #{name}:/ } }
1892   log   = proc { |name, result, type|
1893     mtx.synchronize do
1894       ing  = ![true, false].include?(type)
1895       bar += type ? '=' : 'x' unless ing
1896       b = case type
1897           when :install  then '+' when :update then '*'
1898           when true, nil then '-' else
1899             VIM::command("call add(s:update.errors, '#{name}')")
1900             'x'
1901           end
1902       result =
1903         if type || type.nil?
1904           ["#{b} #{name}: #{result.lines.to_a.last || 'OK'}"]
1905         elsif result =~ /^Interrupted|^Timeout/
1906           ["#{b} #{name}: #{result}"]
1907         else
1908           ["#{b} #{name}"] + result.lines.map { |l| "    " << l }
1909         end
1910       if lnum = where.call(name)
1911         $curbuf.delete lnum
1912         lnum = 4 if ing && lnum > maxy
1913       end
1914       result.each_with_index do |line, offset|
1915         $curbuf.append((lnum || 4) - 1 + offset, line.gsub(/\e\[./, '').chomp)
1916       end
1917       logh.call
1918     end
1919   }
1920   bt = proc { |cmd, name, type, cleanup|
1921     tried = timeout = 0
1922     begin
1923       tried += 1
1924       timeout += limit
1925       fd = nil
1926       data = ''
1927       if iswin
1928         Timeout::timeout(timeout) do
1929           tmp = VIM::evaluate('tempname()')
1930           system("(#{cmd}) > #{tmp}")
1931           data = File.read(tmp).chomp
1932           File.unlink tmp rescue nil
1933         end
1934       else
1935         fd = IO.popen(cmd).extend(PlugStream)
1936         first_line = true
1937         log_prob = 1.0 / nthr
1938         while line = Timeout::timeout(timeout) { fd.get_line }
1939           data << line
1940           log.call name, line.chomp, type if name && (first_line || rand < log_prob)
1941           first_line = false
1942         end
1943         fd.close
1944       end
1945       [$? == 0, data.chomp]
1946     rescue Timeout::Error, Interrupt => e
1947       if fd && !fd.closed?
1948         killall fd.pid
1949         fd.close
1950       end
1951       cleanup.call if cleanup
1952       if e.is_a?(Timeout::Error) && tried < tries
1953         3.downto(1) do |countdown|
1954           s = countdown > 1 ? 's' : ''
1955           log.call name, "Timeout. Will retry in #{countdown} second#{s} ...", type
1956           sleep 1
1957         end
1958         log.call name, 'Retrying ...', type
1959         retry
1960       end
1961       [false, e.is_a?(Interrupt) ? "Interrupted!" : "Timeout!"]
1962     end
1963   }
1964   main = Thread.current
1965   threads = []
1966   watcher = Thread.new {
1967     if vim7
1968       while VIM::evaluate('getchar(1)')
1969         sleep 0.1
1970       end
1971     else
1972       require 'io/console' # >= Ruby 1.9
1973       nil until IO.console.getch == 3.chr
1974     end
1975     mtx.synchronize do
1976       running = false
1977       threads.each { |t| t.raise Interrupt } unless vim7
1978     end
1979     threads.each { |t| t.join rescue nil }
1980     main.kill
1981   }
1982   refresh = Thread.new {
1983     while true
1984       mtx.synchronize do
1985         break unless running
1986         VIM::command('noautocmd normal! a')
1987       end
1988       sleep 0.2
1989     end
1990   } if VIM::evaluate('s:mac_gui') == 1
1991
1992   clone_opt = VIM::evaluate('s:clone_opt')
1993   progress = VIM::evaluate('s:progress_opt(1)')
1994   nthr.times do
1995     mtx.synchronize do
1996       threads << Thread.new {
1997         while pair = take1.call
1998           name = pair.first
1999           dir, uri, tag = pair.last.values_at *%w[dir uri tag]
2000           exists = File.directory? dir
2001           ok, result =
2002             if exists
2003               chdir = "#{cd} #{iswin ? dir : esc(dir)}"
2004               ret, data = bt.call "#{chdir} && git rev-parse --abbrev-ref HEAD 2>&1 && git config -f .git/config remote.origin.url", nil, nil, nil
2005               current_uri = data.lines.to_a.last
2006               if !ret
2007                 if data =~ /^Interrupted|^Timeout/
2008                   [false, data]
2009                 else
2010                   [false, [data.chomp, "PlugClean required."].join($/)]
2011                 end
2012               elsif !compare_git_uri(current_uri, uri)
2013                 [false, ["Invalid URI: #{current_uri}",
2014                          "Expected:    #{uri}",
2015                          "PlugClean required."].join($/)]
2016               else
2017                 if pull
2018                   log.call name, 'Updating ...', :update
2019                   fetch_opt = (tag && File.exist?(File.join(dir, '.git/shallow'))) ? '--depth 99999999' : ''
2020                   bt.call "#{chdir} && git fetch #{fetch_opt} #{progress} 2>&1", name, :update, nil
2021                 else
2022                   [true, skip]
2023                 end
2024               end
2025             else
2026               d = esc dir.sub(%r{[\\/]+$}, '')
2027               log.call name, 'Installing ...', :install
2028               bt.call "git clone #{clone_opt unless tag} #{progress} #{uri} #{d} 2>&1", name, :install, proc {
2029                 FileUtils.rm_rf dir
2030               }
2031             end
2032           mtx.synchronize { VIM::command("let s:update.new['#{name}'] = 1") } if !exists && ok
2033           log.call name, result, ok
2034         end
2035       } if running
2036     end
2037   end
2038   threads.each { |t| t.join rescue nil }
2039   logh.call
2040   refresh.kill if refresh
2041   watcher.kill
2042 EOF
2043 endfunction
2044
2045 function! s:shellesc_cmd(arg, script)
2046   let escaped = substitute('"'.a:arg.'"', '[&|<>()@^!"]', '^&', 'g')
2047   return substitute(escaped, '%', (a:script ? '%' : '^') . '&', 'g')
2048 endfunction
2049
2050 function! s:shellesc_ps1(arg)
2051   return "'".substitute(escape(a:arg, '\"'), "'", "''", 'g')."'"
2052 endfunction
2053
2054 function! s:shellesc_sh(arg)
2055   return "'".substitute(a:arg, "'", "'\\\\''", 'g')."'"
2056 endfunction
2057
2058 function! plug#shellescape(arg, ...)
2059   let opts = a:0 > 0 && type(a:1) == s:TYPE.dict ? a:1 : {}
2060   let shell = get(opts, 'shell', s:is_win ? 'cmd.exe' : 'sh')
2061   let script = get(opts, 'script', 1)
2062   if shell =~# 'cmd\.exe'
2063     return s:shellesc_cmd(a:arg, script)
2064   elseif shell =~# 'powershell\.exe' || shell =~# 'pwsh$'
2065     return s:shellesc_ps1(a:arg)
2066   endif
2067   return s:shellesc_sh(a:arg)
2068 endfunction
2069
2070 function! s:glob_dir(path)
2071   return map(filter(s:glob(a:path, '**'), 'isdirectory(v:val)'), 's:dirpath(v:val)')
2072 endfunction
2073
2074 function! s:progress_bar(line, bar, total)
2075   call setline(a:line, '[' . s:lpad(a:bar, a:total) . ']')
2076 endfunction
2077
2078 function! s:compare_git_uri(a, b)
2079   " See `git help clone'
2080   " https:// [user@] github.com[:port] / junegunn/vim-plug [.git]
2081   "          [git@]  github.com[:port] : junegunn/vim-plug [.git]
2082   " file://                            / junegunn/vim-plug        [/]
2083   "                                    / junegunn/vim-plug        [/]
2084   let pat = '^\%(\w\+://\)\='.'\%([^@/]*@\)\='.'\([^:/]*\%(:[0-9]*\)\=\)'.'[:/]'.'\(.\{-}\)'.'\%(\.git\)\=/\?$'
2085   let ma = matchlist(a:a, pat)
2086   let mb = matchlist(a:b, pat)
2087   return ma[1:2] ==# mb[1:2]
2088 endfunction
2089
2090 function! s:format_message(bullet, name, message)
2091   if a:bullet != 'x'
2092     return [printf('%s %s: %s', a:bullet, a:name, s:lastline(a:message))]
2093   else
2094     let lines = map(s:lines(a:message), '"    ".v:val')
2095     return extend([printf('x %s:', a:name)], lines)
2096   endif
2097 endfunction
2098
2099 function! s:with_cd(cmd, dir, ...)
2100   let script = a:0 > 0 ? a:1 : 1
2101   return printf('cd%s %s && %s', s:is_win ? ' /d' : '', plug#shellescape(a:dir, {'script': script}), a:cmd)
2102 endfunction
2103
2104 function! s:system(cmd, ...)
2105   let batchfile = ''
2106   try
2107     let [sh, shellcmdflag, shrd] = s:chsh(1)
2108     let cmd = a:0 > 0 ? s:with_cd(a:cmd, a:1) : a:cmd
2109     if s:is_win
2110       let [batchfile, cmd] = s:batchfile(cmd)
2111     endif
2112     return system(cmd)
2113   finally
2114     let [&shell, &shellcmdflag, &shellredir] = [sh, shellcmdflag, shrd]
2115     if s:is_win && filereadable(batchfile)
2116       call delete(batchfile)
2117     endif
2118   endtry
2119 endfunction
2120
2121 function! s:system_chomp(...)
2122   let ret = call('s:system', a:000)
2123   return v:shell_error ? '' : substitute(ret, '\n$', '', '')
2124 endfunction
2125
2126 function! s:git_validate(spec, check_branch)
2127   let err = ''
2128   if isdirectory(a:spec.dir)
2129     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))
2130     let remote = result[-1]
2131     if v:shell_error
2132       let err = join([remote, 'PlugClean required.'], "\n")
2133     elseif !s:compare_git_uri(remote, a:spec.uri)
2134       let err = join(['Invalid URI: '.remote,
2135                     \ 'Expected:    '.a:spec.uri,
2136                     \ 'PlugClean required.'], "\n")
2137     elseif a:check_branch && has_key(a:spec, 'commit')
2138       let result = s:lines(s:system('git rev-parse HEAD 2>&1', a:spec.dir))
2139       let sha = result[-1]
2140       if v:shell_error
2141         let err = join(add(result, 'PlugClean required.'), "\n")
2142       elseif !s:hash_match(sha, a:spec.commit)
2143         let err = join([printf('Invalid HEAD (expected: %s, actual: %s)',
2144                               \ a:spec.commit[:6], sha[:6]),
2145                       \ 'PlugUpdate required.'], "\n")
2146       endif
2147     elseif a:check_branch
2148       let branch = result[0]
2149       " Check tag
2150       if has_key(a:spec, 'tag')
2151         let tag = s:system_chomp('git describe --exact-match --tags HEAD 2>&1', a:spec.dir)
2152         if a:spec.tag !=# tag && a:spec.tag !~ '\*'
2153           let err = printf('Invalid tag: %s (expected: %s). Try PlugUpdate.',
2154                 \ (empty(tag) ? 'N/A' : tag), a:spec.tag)
2155         endif
2156       " Check branch
2157       elseif a:spec.branch !=# branch
2158         let err = printf('Invalid branch: %s (expected: %s). Try PlugUpdate.',
2159               \ branch, a:spec.branch)
2160       endif
2161       if empty(err)
2162         let [ahead, behind] = split(s:lastline(s:system(printf(
2163               \ 'git rev-list --count --left-right HEAD...origin/%s',
2164               \ a:spec.branch), a:spec.dir)), '\t')
2165         if !v:shell_error && ahead
2166           if behind
2167             " Only mention PlugClean if diverged, otherwise it's likely to be
2168             " pushable (and probably not that messed up).
2169             let err = printf(
2170                   \ "Diverged from origin/%s (%d commit(s) ahead and %d commit(s) behind!\n"
2171                   \ .'Backup local changes and run PlugClean and PlugUpdate to reinstall it.', a:spec.branch, ahead, behind)
2172           else
2173             let err = printf("Ahead of origin/%s by %d commit(s).\n"
2174                   \ .'Cannot update until local changes are pushed.',
2175                   \ a:spec.branch, ahead)
2176           endif
2177         endif
2178       endif
2179     endif
2180   else
2181     let err = 'Not found'
2182   endif
2183   return [err, err =~# 'PlugClean']
2184 endfunction
2185
2186 function! s:rm_rf(dir)
2187   if isdirectory(a:dir)
2188     call s:system((s:is_win ? 'rmdir /S /Q ' : 'rm -rf ') . plug#shellescape(a:dir))
2189   endif
2190 endfunction
2191
2192 function! s:clean(force)
2193   call s:prepare()
2194   call append(0, 'Searching for invalid plugins in '.g:plug_home)
2195   call append(1, '')
2196
2197   " List of valid directories
2198   let dirs = []
2199   let errs = {}
2200   let [cnt, total] = [0, len(g:plugs)]
2201   for [name, spec] in items(g:plugs)
2202     if !s:is_managed(name)
2203       call add(dirs, spec.dir)
2204     else
2205       let [err, clean] = s:git_validate(spec, 1)
2206       if clean
2207         let errs[spec.dir] = s:lines(err)[0]
2208       else
2209         call add(dirs, spec.dir)
2210       endif
2211     endif
2212     let cnt += 1
2213     call s:progress_bar(2, repeat('=', cnt), total)
2214     normal! 2G
2215     redraw
2216   endfor
2217
2218   let allowed = {}
2219   for dir in dirs
2220     let allowed[s:dirpath(s:plug_fnamemodify(dir, ':h:h'))] = 1
2221     let allowed[dir] = 1
2222     for child in s:glob_dir(dir)
2223       let allowed[child] = 1
2224     endfor
2225   endfor
2226
2227   let todo = []
2228   let found = sort(s:glob_dir(g:plug_home))
2229   while !empty(found)
2230     let f = remove(found, 0)
2231     if !has_key(allowed, f) && isdirectory(f)
2232       call add(todo, f)
2233       call append(line('$'), '- ' . f)
2234       if has_key(errs, f)
2235         call append(line('$'), '    ' . errs[f])
2236       endif
2237       let found = filter(found, 'stridx(v:val, f) != 0')
2238     end
2239   endwhile
2240
2241   4
2242   redraw
2243   if empty(todo)
2244     call append(line('$'), 'Already clean.')
2245   else
2246     let s:clean_count = 0
2247     call append(3, ['Directories to delete:', ''])
2248     redraw!
2249     if a:force || s:ask_no_interrupt('Delete all directories?')
2250       call s:delete([6, line('$')], 1)
2251     else
2252       call setline(4, 'Cancelled.')
2253       nnoremap <silent> <buffer> d :set opfunc=<sid>delete_op<cr>g@
2254       nmap     <silent> <buffer> dd d_
2255       xnoremap <silent> <buffer> d :<c-u>call <sid>delete_op(visualmode(), 1)<cr>
2256       echo 'Delete the lines (d{motion}) to delete the corresponding directories'
2257     endif
2258   endif
2259   4
2260   setlocal nomodifiable
2261 endfunction
2262
2263 function! s:delete_op(type, ...)
2264   call s:delete(a:0 ? [line("'<"), line("'>")] : [line("'["), line("']")], 0)
2265 endfunction
2266
2267 function! s:delete(range, force)
2268   let [l1, l2] = a:range
2269   let force = a:force
2270   while l1 <= l2
2271     let line = getline(l1)
2272     if line =~ '^- ' && isdirectory(line[2:])
2273       execute l1
2274       redraw!
2275       let answer = force ? 1 : s:ask('Delete '.line[2:].'?', 1)
2276       let force = force || answer > 1
2277       if answer
2278         call s:rm_rf(line[2:])
2279         setlocal modifiable
2280         call setline(l1, '~'.line[1:])
2281         let s:clean_count += 1
2282         call setline(4, printf('Removed %d directories.', s:clean_count))
2283         setlocal nomodifiable
2284       endif
2285     endif
2286     let l1 += 1
2287   endwhile
2288 endfunction
2289
2290 function! s:upgrade()
2291   echo 'Downloading the latest version of vim-plug'
2292   redraw
2293   let tmp = s:plug_tempname()
2294   let new = tmp . '/plug.vim'
2295
2296   try
2297     let out = s:system(printf('git clone --depth 1 %s %s', plug#shellescape(s:plug_src), plug#shellescape(tmp)))
2298     if v:shell_error
2299       return s:err('Error upgrading vim-plug: '. out)
2300     endif
2301
2302     if readfile(s:me) ==# readfile(new)
2303       echo 'vim-plug is already up-to-date'
2304       return 0
2305     else
2306       call rename(s:me, s:me . '.old')
2307       call rename(new, s:me)
2308       unlet g:loaded_plug
2309       echo 'vim-plug has been upgraded'
2310       return 1
2311     endif
2312   finally
2313     silent! call s:rm_rf(tmp)
2314   endtry
2315 endfunction
2316
2317 function! s:upgrade_specs()
2318   for spec in values(g:plugs)
2319     let spec.frozen = get(spec, 'frozen', 0)
2320   endfor
2321 endfunction
2322
2323 function! s:status()
2324   call s:prepare()
2325   call append(0, 'Checking plugins')
2326   call append(1, '')
2327
2328   let ecnt = 0
2329   let unloaded = 0
2330   let [cnt, total] = [0, len(g:plugs)]
2331   for [name, spec] in items(g:plugs)
2332     let is_dir = isdirectory(spec.dir)
2333     if has_key(spec, 'uri')
2334       if is_dir
2335         let [err, _] = s:git_validate(spec, 1)
2336         let [valid, msg] = [empty(err), empty(err) ? 'OK' : err]
2337       else
2338         let [valid, msg] = [0, 'Not found. Try PlugInstall.']
2339       endif
2340     else
2341       if is_dir
2342         let [valid, msg] = [1, 'OK']
2343       else
2344         let [valid, msg] = [0, 'Not found.']
2345       endif
2346     endif
2347     let cnt += 1
2348     let ecnt += !valid
2349     " `s:loaded` entry can be missing if PlugUpgraded
2350     if is_dir && get(s:loaded, name, -1) == 0
2351       let unloaded = 1
2352       let msg .= ' (not loaded)'
2353     endif
2354     call s:progress_bar(2, repeat('=', cnt), total)
2355     call append(3, s:format_message(valid ? '-' : 'x', name, msg))
2356     normal! 2G
2357     redraw
2358   endfor
2359   call setline(1, 'Finished. '.ecnt.' error(s).')
2360   normal! gg
2361   setlocal nomodifiable
2362   if unloaded
2363     echo "Press 'L' on each line to load plugin, or 'U' to update"
2364     nnoremap <silent> <buffer> L :call <SID>status_load(line('.'))<cr>
2365     xnoremap <silent> <buffer> L :call <SID>status_load(line('.'))<cr>
2366   end
2367 endfunction
2368
2369 function! s:extract_name(str, prefix, suffix)
2370   return matchstr(a:str, '^'.a:prefix.' \zs[^:]\+\ze:.*'.a:suffix.'$')
2371 endfunction
2372
2373 function! s:status_load(lnum)
2374   let line = getline(a:lnum)
2375   let name = s:extract_name(line, '-', '(not loaded)')
2376   if !empty(name)
2377     call plug#load(name)
2378     setlocal modifiable
2379     call setline(a:lnum, substitute(line, ' (not loaded)$', '', ''))
2380     setlocal nomodifiable
2381   endif
2382 endfunction
2383
2384 function! s:status_update() range
2385   let lines = getline(a:firstline, a:lastline)
2386   let names = filter(map(lines, 's:extract_name(v:val, "[x-]", "")'), '!empty(v:val)')
2387   if !empty(names)
2388     echo
2389     execute 'PlugUpdate' join(names)
2390   endif
2391 endfunction
2392
2393 function! s:is_preview_window_open()
2394   silent! wincmd P
2395   if &previewwindow
2396     wincmd p
2397     return 1
2398   endif
2399 endfunction
2400
2401 function! s:find_name(lnum)
2402   for lnum in reverse(range(1, a:lnum))
2403     let line = getline(lnum)
2404     if empty(line)
2405       return ''
2406     endif
2407     let name = s:extract_name(line, '-', '')
2408     if !empty(name)
2409       return name
2410     endif
2411   endfor
2412   return ''
2413 endfunction
2414
2415 function! s:preview_commit()
2416   if b:plug_preview < 0
2417     let b:plug_preview = !s:is_preview_window_open()
2418   endif
2419
2420   let sha = matchstr(getline('.'), '^  \X*\zs[0-9a-f]\{7,9}')
2421   if empty(sha)
2422     return
2423   endif
2424
2425   let name = s:find_name(line('.'))
2426   if empty(name) || !has_key(g:plugs, name) || !isdirectory(g:plugs[name].dir)
2427     return
2428   endif
2429
2430   if exists('g:plug_pwindow') && !s:is_preview_window_open()
2431     execute g:plug_pwindow
2432     execute 'e' sha
2433   else
2434     execute 'pedit' sha
2435     wincmd P
2436   endif
2437   setlocal previewwindow filetype=git buftype=nofile nobuflisted modifiable
2438   let batchfile = ''
2439   try
2440     let [sh, shellcmdflag, shrd] = s:chsh(1)
2441     let cmd = 'cd '.plug#shellescape(g:plugs[name].dir).' && git show --no-color --pretty=medium '.sha
2442     if s:is_win
2443       let [batchfile, cmd] = s:batchfile(cmd)
2444     endif
2445     execute 'silent %!' cmd
2446   finally
2447     let [&shell, &shellcmdflag, &shellredir] = [sh, shellcmdflag, shrd]
2448     if s:is_win && filereadable(batchfile)
2449       call delete(batchfile)
2450     endif
2451   endtry
2452   setlocal nomodifiable
2453   nnoremap <silent> <buffer> q :q<cr>
2454   wincmd p
2455 endfunction
2456
2457 function! s:section(flags)
2458   call search('\(^[x-] \)\@<=[^:]\+:', a:flags)
2459 endfunction
2460
2461 function! s:format_git_log(line)
2462   let indent = '  '
2463   let tokens = split(a:line, nr2char(1))
2464   if len(tokens) != 5
2465     return indent.substitute(a:line, '\s*$', '', '')
2466   endif
2467   let [graph, sha, refs, subject, date] = tokens
2468   let tag = matchstr(refs, 'tag: [^,)]\+')
2469   let tag = empty(tag) ? ' ' : ' ('.tag.') '
2470   return printf('%s%s%s%s%s (%s)', indent, graph, sha, tag, subject, date)
2471 endfunction
2472
2473 function! s:append_ul(lnum, text)
2474   call append(a:lnum, ['', a:text, repeat('-', len(a:text))])
2475 endfunction
2476
2477 function! s:diff()
2478   call s:prepare()
2479   call append(0, ['Collecting changes ...', ''])
2480   let cnts = [0, 0]
2481   let bar = ''
2482   let total = filter(copy(g:plugs), 's:is_managed(v:key) && isdirectory(v:val.dir)')
2483   call s:progress_bar(2, bar, len(total))
2484   for origin in [1, 0]
2485     let plugs = reverse(sort(items(filter(copy(total), (origin ? '' : '!').'(has_key(v:val, "commit") || has_key(v:val, "tag"))'))))
2486     if empty(plugs)
2487       continue
2488     endif
2489     call s:append_ul(2, origin ? 'Pending updates:' : 'Last update:')
2490     for [k, v] in plugs
2491       let range = origin ? '..origin/'.v.branch : 'HEAD@{1}..'
2492       let cmd = 'git log --graph --color=never '
2493       \ . (s:git_version_requirement(2, 10, 0) ? '--no-show-signature ' : '')
2494       \ . join(map(['--pretty=format:%x01%h%x01%d%x01%s%x01%cr', range], 'plug#shellescape(v:val)'))
2495       if has_key(v, 'rtp')
2496         let cmd .= ' -- '.plug#shellescape(v.rtp)
2497       endif
2498       let diff = s:system_chomp(cmd, v.dir)
2499       if !empty(diff)
2500         let ref = has_key(v, 'tag') ? (' (tag: '.v.tag.')') : has_key(v, 'commit') ? (' '.v.commit) : ''
2501         call append(5, extend(['', '- '.k.':'.ref], map(s:lines(diff), 's:format_git_log(v:val)')))
2502         let cnts[origin] += 1
2503       endif
2504       let bar .= '='
2505       call s:progress_bar(2, bar, len(total))
2506       normal! 2G
2507       redraw
2508     endfor
2509     if !cnts[origin]
2510       call append(5, ['', 'N/A'])
2511     endif
2512   endfor
2513   call setline(1, printf('%d plugin(s) updated.', cnts[0])
2514         \ . (cnts[1] ? printf(' %d plugin(s) have pending updates.', cnts[1]) : ''))
2515
2516   if cnts[0] || cnts[1]
2517     nnoremap <silent> <buffer> <plug>(plug-preview) :silent! call <SID>preview_commit()<cr>
2518     if empty(maparg("\<cr>", 'n'))
2519       nmap <buffer> <cr> <plug>(plug-preview)
2520     endif
2521     if empty(maparg('o', 'n'))
2522       nmap <buffer> o <plug>(plug-preview)
2523     endif
2524   endif
2525   if cnts[0]
2526     nnoremap <silent> <buffer> X :call <SID>revert()<cr>
2527     echo "Press 'X' on each block to revert the update"
2528   endif
2529   normal! gg
2530   setlocal nomodifiable
2531 endfunction
2532
2533 function! s:revert()
2534   if search('^Pending updates', 'bnW')
2535     return
2536   endif
2537
2538   let name = s:find_name(line('.'))
2539   if empty(name) || !has_key(g:plugs, name) ||
2540     \ input(printf('Revert the update of %s? (y/N) ', name)) !~? '^y'
2541     return
2542   endif
2543
2544   call s:system('git reset --hard HEAD@{1} && git checkout '.plug#shellescape(g:plugs[name].branch).' --', g:plugs[name].dir)
2545   setlocal modifiable
2546   normal! "_dap
2547   setlocal nomodifiable
2548   echo 'Reverted'
2549 endfunction
2550
2551 function! s:snapshot(force, ...) abort
2552   call s:prepare()
2553   setf vim
2554   call append(0, ['" Generated by vim-plug',
2555                 \ '" '.strftime("%c"),
2556                 \ '" :source this file in vim to restore the snapshot',
2557                 \ '" or execute: vim -S snapshot.vim',
2558                 \ '', '', 'PlugUpdate!'])
2559   1
2560   let anchor = line('$') - 3
2561   let names = sort(keys(filter(copy(g:plugs),
2562         \'has_key(v:val, "uri") && !has_key(v:val, "commit") && isdirectory(v:val.dir)')))
2563   for name in reverse(names)
2564     let sha = s:system_chomp('git rev-parse --short HEAD', g:plugs[name].dir)
2565     if !empty(sha)
2566       call append(anchor, printf("silent! let g:plugs['%s'].commit = '%s'", name, sha))
2567       redraw
2568     endif
2569   endfor
2570
2571   if a:0 > 0
2572     let fn = s:plug_expand(a:1)
2573     if filereadable(fn) && !(a:force || s:ask(a:1.' already exists. Overwrite?'))
2574       return
2575     endif
2576     call writefile(getline(1, '$'), fn)
2577     echo 'Saved as '.a:1
2578     silent execute 'e' s:esc(fn)
2579     setf vim
2580   endif
2581 endfunction
2582
2583 function! s:split_rtp()
2584   return split(&rtp, '\\\@<!,')
2585 endfunction
2586
2587 let s:first_rtp = s:escrtp(get(s:split_rtp(), 0, ''))
2588 let s:last_rtp  = s:escrtp(get(s:split_rtp(), -1, ''))
2589
2590 if exists('g:plugs')
2591   let g:plugs_order = get(g:, 'plugs_order', keys(g:plugs))
2592   call s:upgrade_specs()
2593   call s:define_commands()
2594 endif
2595
2596 let &cpo = s:cpo_save
2597 unlet s:cpo_save