]> git.madduck.net Git - etc/neovim.git/blob - .config/nvim/autoload/plug.vim

madduck's git repository

Every one of the projects in this repository is available at the canonical URL git://git.madduck.net/madduck/pub/<projectpath> — see each project's metadata for the exact URL.

All patches and comments are welcome. Please squash your changes to logical commits before using git-format-patch and git-send-email to patches@git.madduck.net. If you'd read over the Git project's submission guidelines and adhered to them, I'd be especially grateful.

SSH access, as well as push access can be individually arranged.

If you use my repositories frequently, consider adding the following snippet to ~/.gitconfig and using the third clone URL listed for each project:

[url "git://git.madduck.net/madduck/"]
  insteadOf = madduck:

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