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