]> git.r.bdr.sh - rbdr/dotfiles/blame - vim/autoload/plug.vim
Add license
[rbdr/dotfiles] / vim / autoload / plug.vim
CommitLineData
3f0234c4
BB
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"
b63b23c0 28" " Using a non-default branch
3f0234c4
BB
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
b63b23c0 109let s:base_spec = { 'branch': '', 'frozen': 0 }
3f0234c4
BB
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
b63b23c0
RBR
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
3f0234c4
BB
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
b63b23c0 270 \ && (&shell =~# 'cmd\(\.exe\)\?$' || s:is_powershell(&shell))
3f0234c4
BB
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
f60007b3 408 \ [['i', '<C-\><C-O>', ''], ['n', '', ''], ['v', '', 'gv'], ['o', '', '']]
3f0234c4
BB
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')
b63b23c0 463 let s:git_version = map(split(split(s:system(['git', '--version']))[2], '\.'), 'str2nr(v:val)')
3f0234c4
BB
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})
b63b23c0 510 if s:is_powershell(&shell)
3f0234c4
BB
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
b63b23c0 723 return s:err(repo . ' ' . v:exception)
3f0234c4
BB
724 endtry
725endfunction
726
727function! s:parse_options(arg)
728 let opts = copy(s:base_spec)
729 let type = type(a:arg)
b63b23c0 730 let opt_errfmt = 'Invalid argument for "%s" option of :Plug (expected: %s)'
3f0234c4 731 if type == s:TYPE.string
b63b23c0
RBR
732 if empty(a:arg)
733 throw printf(opt_errfmt, 'tag', 'string')
734 endif
3f0234c4
BB
735 let opts.tag = a:arg
736 elseif type == s:TYPE.dict
b63b23c0
RBR
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
3f0234c4
BB
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
b63b23c0 811 syn match plugDash /^-\{1}\ /
3f0234c4
BB
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-\+$/
b63b23c0 829 syn match plugH2 /^-\{2,}/
3f0234c4
BB
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
b63b23c0 942 nnoremap <silent> <buffer> q :call <SID>close_pane()<cr>
3f0234c4
BB
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
b63b23c0
RBR
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
3f0234c4
BB
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]
b63b23c0
RBR
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
3f0234c4
BB
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)
b63b23c0 1028 return !empty(s:system_chomp(['git', 'log', '--pretty=format:%h', 'HEAD...HEAD@{1}'], a:dir))
3f0234c4
BB
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
b63b23c0 1065 call s:load_plugin(spec)
3f0234c4
BB
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
b63b23c0
RBR
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= ' : ''
3f0234c4 1095 let output = s:system(
b63b23c0 1096 \ 'git '.credential_helper.'fetch --depth 999999 && git checkout '.plug#shellescape(sha).' --', a:spec.dir)
3f0234c4
BB
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
f60007b3
RBR
1211 " Set remote name, overriding a possible user git config's clone.defaultRemoteName
1212 let s:clone_opt = ['--origin', 'origin']
b63b23c0
RBR
1213 if get(g:, 'plug_shallow', 1)
1214 call extend(s:clone_opt, ['--depth', '1'])
1215 if s:git_version_requirement(1, 7, 10)
1216 call add(s:clone_opt, '--no-single-branch')
1217 endif
1218 endif
3f0234c4
BB
1219
1220 if has('win32unix') || has('wsl')
b63b23c0 1221 call extend(s:clone_opt, ['-c', 'core.eol=lf', '-c', 'core.autocrlf=input'])
3f0234c4
BB
1222 endif
1223
1224 let s:submodule_opt = s:git_version_requirement(2, 8) ? ' --jobs='.threads : ''
1225
1226 " Python version requirement (>= 2.7)
1227 if python && !has('python3') && !ruby && !use_job && s:update.threads > 1
1228 redir => pyv
1229 silent python import platform; print platform.python_version()
1230 redir END
1231 let python = s:version_requirement(
1232 \ map(split(split(pyv)[0], '\.'), 'str2nr(v:val)'), [2, 6])
1233 endif
1234
1235 if (python || ruby) && s:update.threads > 1
1236 try
1237 let imd = &imd
1238 if s:mac_gui
1239 set noimd
1240 endif
1241 if ruby
1242 call s:update_ruby()
1243 else
1244 call s:update_python()
1245 endif
1246 catch
1247 let lines = getline(4, '$')
1248 let printed = {}
1249 silent! 4,$d _
1250 for line in lines
1251 let name = s:extract_name(line, '.', '')
1252 if empty(name) || !has_key(printed, name)
1253 call append('$', line)
1254 if !empty(name)
1255 let printed[name] = 1
1256 if line[0] == 'x' && index(s:update.errors, name) < 0
1257 call add(s:update.errors, name)
1258 end
1259 endif
1260 endif
1261 endfor
1262 finally
1263 let &imd = imd
1264 call s:update_finish()
1265 endtry
1266 else
1267 call s:update_vim()
1268 while use_job && sync
1269 sleep 100m
1270 if s:update.fin
1271 break
1272 endif
1273 endwhile
1274 endif
1275endfunction
1276
1277function! s:log4(name, msg)
1278 call setline(4, printf('- %s (%s)', a:msg, a:name))
1279 redraw
1280endfunction
1281
1282function! s:update_finish()
1283 if exists('s:git_terminal_prompt')
1284 let $GIT_TERMINAL_PROMPT = s:git_terminal_prompt
1285 endif
1286 if s:switch_in()
1287 call append(3, '- Updating ...') | 4
1288 for [name, spec] in items(filter(copy(s:update.all), 'index(s:update.errors, v:key) < 0 && (s:update.force || s:update.pull || has_key(s:update.new, v:key))'))
1289 let [pos, _] = s:logpos(name)
1290 if !pos
1291 continue
1292 endif
1293 if has_key(spec, 'commit')
1294 call s:log4(name, 'Checking out '.spec.commit)
1295 let out = s:checkout(spec)
1296 elseif has_key(spec, 'tag')
1297 let tag = spec.tag
1298 if tag =~ '\*'
1299 let tags = s:lines(s:system('git tag --list '.plug#shellescape(tag).' --sort -version:refname 2>&1', spec.dir))
1300 if !v:shell_error && !empty(tags)
1301 let tag = tags[0]
1302 call s:log4(name, printf('Latest tag for %s -> %s', spec.tag, tag))
1303 call append(3, '')
1304 endif
1305 endif
1306 call s:log4(name, 'Checking out '.tag)
1307 let out = s:system('git checkout -q '.plug#shellescape(tag).' -- 2>&1', spec.dir)
1308 else
b63b23c0 1309 let branch = s:git_origin_branch(spec)
3f0234c4
BB
1310 call s:log4(name, 'Merging origin/'.s:esc(branch))
1311 let out = s:system('git checkout -q '.plug#shellescape(branch).' -- 2>&1'
1312 \. (has_key(s:update.new, name) ? '' : ('&& git merge --ff-only '.plug#shellescape('origin/'.branch).' 2>&1')), spec.dir)
1313 endif
1314 if !v:shell_error && filereadable(spec.dir.'/.gitmodules') &&
1315 \ (s:update.force || has_key(s:update.new, name) || s:is_updated(spec.dir))
1316 call s:log4(name, 'Updating submodules. This may take a while.')
1317 let out .= s:bang('git submodule update --init --recursive'.s:submodule_opt.' 2>&1', spec.dir)
1318 endif
1319 let msg = s:format_message(v:shell_error ? 'x': '-', name, out)
1320 if v:shell_error
1321 call add(s:update.errors, name)
1322 call s:regress_bar()
1323 silent execute pos 'd _'
1324 call append(4, msg) | 4
1325 elseif !empty(out)
1326 call setline(pos, msg[0])
1327 endif
1328 redraw
1329 endfor
1330 silent 4 d _
1331 try
1332 call s:do(s:update.pull, s:update.force, filter(copy(s:update.all), 'index(s:update.errors, v:key) < 0 && has_key(v:val, "do")'))
1333 catch
1334 call s:warn('echom', v:exception)
1335 call s:warn('echo', '')
1336 return
1337 endtry
1338 call s:finish(s:update.pull)
1339 call setline(1, 'Updated. Elapsed time: ' . split(reltimestr(reltime(s:update.start)))[0] . ' sec.')
1340 call s:switch_out('normal! gg')
1341 endif
1342endfunction
1343
1344function! s:job_abort()
1345 if (!s:nvim && !s:vim8) || !exists('s:jobs')
1346 return
1347 endif
1348
1349 for [name, j] in items(s:jobs)
1350 if s:nvim
1351 silent! call jobstop(j.jobid)
1352 elseif s:vim8
1353 silent! call job_stop(j.jobid)
1354 endif
1355 if j.new
1356 call s:rm_rf(g:plugs[name].dir)
1357 endif
1358 endfor
1359 let s:jobs = {}
1360endfunction
1361
1362function! s:last_non_empty_line(lines)
1363 let len = len(a:lines)
1364 for idx in range(len)
1365 let line = a:lines[len-idx-1]
1366 if !empty(line)
1367 return line
1368 endif
1369 endfor
1370 return ''
1371endfunction
1372
1373function! s:job_out_cb(self, data) abort
1374 let self = a:self
1375 let data = remove(self.lines, -1) . a:data
1376 let lines = map(split(data, "\n", 1), 'split(v:val, "\r", 1)[-1]')
1377 call extend(self.lines, lines)
1378 " To reduce the number of buffer updates
1379 let self.tick = get(self, 'tick', -1) + 1
1380 if !self.running || self.tick % len(s:jobs) == 0
1381 let bullet = self.running ? (self.new ? '+' : '*') : (self.error ? 'x' : '-')
1382 let result = self.error ? join(self.lines, "\n") : s:last_non_empty_line(self.lines)
1383 call s:log(bullet, self.name, result)
1384 endif
1385endfunction
1386
1387function! s:job_exit_cb(self, data) abort
1388 let a:self.running = 0
1389 let a:self.error = a:data != 0
1390 call s:reap(a:self.name)
1391 call s:tick()
1392endfunction
1393
1394function! s:job_cb(fn, job, ch, data)
1395 if !s:plug_window_exists() " plug window closed
1396 return s:job_abort()
1397 endif
1398 call call(a:fn, [a:job, a:data])
1399endfunction
1400
1401function! s:nvim_cb(job_id, data, event) dict abort
b63b23c0 1402 return (a:event == 'stdout' || a:event == 'stderr') ?
3f0234c4
BB
1403 \ s:job_cb('s:job_out_cb', self, 0, join(a:data, "\n")) :
1404 \ s:job_cb('s:job_exit_cb', self, 0, a:data)
1405endfunction
1406
1407function! s:spawn(name, cmd, opts)
1408 let job = { 'name': a:name, 'running': 1, 'error': 0, 'lines': [''],
1409 \ 'new': get(a:opts, 'new', 0) }
1410 let s:jobs[a:name] = job
3f0234c4
BB
1411
1412 if s:nvim
b63b23c0
RBR
1413 if has_key(a:opts, 'dir')
1414 let job.cwd = a:opts.dir
1415 endif
1416 let argv = a:cmd
3f0234c4
BB
1417 call extend(job, {
1418 \ 'on_stdout': function('s:nvim_cb'),
b63b23c0 1419 \ 'on_stderr': function('s:nvim_cb'),
3f0234c4
BB
1420 \ 'on_exit': function('s:nvim_cb'),
1421 \ })
1422 let jid = s:plug_call('jobstart', argv, job)
1423 if jid > 0
1424 let job.jobid = jid
1425 else
1426 let job.running = 0
1427 let job.error = 1
1428 let job.lines = [jid < 0 ? argv[0].' is not executable' :
1429 \ 'Invalid arguments (or job table is full)']
1430 endif
1431 elseif s:vim8
b63b23c0
RBR
1432 let cmd = join(map(copy(a:cmd), 'plug#shellescape(v:val, {"script": 0})'))
1433 if has_key(a:opts, 'dir')
1434 let cmd = s:with_cd(cmd, a:opts.dir, 0)
1435 endif
1436 let argv = s:is_win ? ['cmd', '/s', '/c', '"'.cmd.'"'] : ['sh', '-c', cmd]
3f0234c4
BB
1437 let jid = job_start(s:is_win ? join(argv, ' ') : argv, {
1438 \ 'out_cb': function('s:job_cb', ['s:job_out_cb', job]),
b63b23c0 1439 \ 'err_cb': function('s:job_cb', ['s:job_out_cb', job]),
3f0234c4 1440 \ 'exit_cb': function('s:job_cb', ['s:job_exit_cb', job]),
b63b23c0 1441 \ 'err_mode': 'raw',
3f0234c4
BB
1442 \ 'out_mode': 'raw'
1443 \})
1444 if job_status(jid) == 'run'
1445 let job.jobid = jid
1446 else
1447 let job.running = 0
1448 let job.error = 1
1449 let job.lines = ['Failed to start job']
1450 endif
1451 else
b63b23c0 1452 let job.lines = s:lines(call('s:system', has_key(a:opts, 'dir') ? [a:cmd, a:opts.dir] : [a:cmd]))
3f0234c4
BB
1453 let job.error = v:shell_error != 0
1454 let job.running = 0
1455 endif
1456endfunction
1457
1458function! s:reap(name)
1459 let job = s:jobs[a:name]
1460 if job.error
1461 call add(s:update.errors, a:name)
1462 elseif get(job, 'new', 0)
1463 let s:update.new[a:name] = 1
1464 endif
1465 let s:update.bar .= job.error ? 'x' : '='
1466
1467 let bullet = job.error ? 'x' : '-'
1468 let result = job.error ? join(job.lines, "\n") : s:last_non_empty_line(job.lines)
1469 call s:log(bullet, a:name, empty(result) ? 'OK' : result)
1470 call s:bar()
1471
1472 call remove(s:jobs, a:name)
1473endfunction
1474
1475function! s:bar()
1476 if s:switch_in()
1477 let total = len(s:update.all)
1478 call setline(1, (s:update.pull ? 'Updating' : 'Installing').
1479 \ ' plugins ('.len(s:update.bar).'/'.total.')')
1480 call s:progress_bar(2, s:update.bar, total)
1481 call s:switch_out()
1482 endif
1483endfunction
1484
1485function! s:logpos(name)
1486 let max = line('$')
1487 for i in range(4, max > 4 ? max : 4)
1488 if getline(i) =~# '^[-+x*] '.a:name.':'
1489 for j in range(i + 1, max > 5 ? max : 5)
1490 if getline(j) !~ '^ '
1491 return [i, j - 1]
1492 endif
1493 endfor
1494 return [i, i]
1495 endif
1496 endfor
1497 return [0, 0]
1498endfunction
1499
1500function! s:log(bullet, name, lines)
1501 if s:switch_in()
1502 let [b, e] = s:logpos(a:name)
1503 if b > 0
1504 silent execute printf('%d,%d d _', b, e)
1505 if b > winheight('.')
1506 let b = 4
1507 endif
1508 else
1509 let b = 4
1510 endif
1511 " FIXME For some reason, nomodifiable is set after :d in vim8
1512 setlocal modifiable
1513 call append(b - 1, s:format_message(a:bullet, a:name, a:lines))
1514 call s:switch_out()
1515 endif
1516endfunction
1517
1518function! s:update_vim()
1519 let s:jobs = {}
1520
1521 call s:bar()
1522 call s:tick()
1523endfunction
1524
1525function! s:tick()
1526 let pull = s:update.pull
1527 let prog = s:progress_opt(s:nvim || s:vim8)
1528while 1 " Without TCO, Vim stack is bound to explode
1529 if empty(s:update.todo)
1530 if empty(s:jobs) && !s:update.fin
1531 call s:update_finish()
1532 let s:update.fin = 1
1533 endif
1534 return
1535 endif
1536
1537 let name = keys(s:update.todo)[0]
1538 let spec = remove(s:update.todo, name)
1539 let new = empty(globpath(spec.dir, '.git', 1))
1540
1541 call s:log(new ? '+' : '*', name, pull ? 'Updating ...' : 'Installing ...')
1542 redraw
1543
1544 let has_tag = has_key(spec, 'tag')
1545 if !new
1546 let [error, _] = s:git_validate(spec, 0)
1547 if empty(error)
1548 if pull
b63b23c0
RBR
1549 let cmd = s:git_version_requirement(2) ? ['git', '-c', 'credential.helper=', 'fetch'] : ['git', 'fetch']
1550 if has_tag && !empty(globpath(spec.dir, '.git/shallow'))
1551 call extend(cmd, ['--depth', '99999999'])
1552 endif
1553 if !empty(prog)
1554 call add(cmd, prog)
1555 endif
1556 call s:spawn(name, cmd, { 'dir': spec.dir })
3f0234c4
BB
1557 else
1558 let s:jobs[name] = { 'running': 0, 'lines': ['Already installed'], 'error': 0 }
1559 endif
1560 else
1561 let s:jobs[name] = { 'running': 0, 'lines': s:lines(error), 'error': 1 }
1562 endif
1563 else
b63b23c0
RBR
1564 let cmd = ['git', 'clone']
1565 if !has_tag
1566 call extend(cmd, s:clone_opt)
1567 endif
1568 if !empty(prog)
1569 call add(cmd, prog)
1570 endif
1571 call s:spawn(name, extend(cmd, [spec.uri, s:trim(spec.dir)]), { 'new': 1 })
3f0234c4
BB
1572 endif
1573
1574 if !s:jobs[name].running
1575 call s:reap(name)
1576 endif
1577 if len(s:jobs) >= s:update.threads
1578 break
1579 endif
1580endwhile
1581endfunction
1582
1583function! s:update_python()
1584let py_exe = has('python') ? 'python' : 'python3'
1585execute py_exe "<< EOF"
1586import datetime
1587import functools
1588import os
1589try:
1590 import queue
1591except ImportError:
1592 import Queue as queue
1593import random
1594import re
1595import shutil
1596import signal
1597import subprocess
1598import tempfile
1599import threading as thr
1600import time
1601import traceback
1602import vim
1603
1604G_NVIM = vim.eval("has('nvim')") == '1'
1605G_PULL = vim.eval('s:update.pull') == '1'
1606G_RETRIES = int(vim.eval('get(g:, "plug_retries", 2)')) + 1
1607G_TIMEOUT = int(vim.eval('get(g:, "plug_timeout", 60)'))
b63b23c0 1608G_CLONE_OPT = ' '.join(vim.eval('s:clone_opt'))
3f0234c4
BB
1609G_PROGRESS = vim.eval('s:progress_opt(1)')
1610G_LOG_PROB = 1.0 / int(vim.eval('s:update.threads'))
1611G_STOP = thr.Event()
1612G_IS_WIN = vim.eval('s:is_win') == '1'
1613
1614class PlugError(Exception):
1615 def __init__(self, msg):
1616 self.msg = msg
1617class CmdTimedOut(PlugError):
1618 pass
1619class CmdFailed(PlugError):
1620 pass
1621class InvalidURI(PlugError):
1622 pass
1623class Action(object):
1624 INSTALL, UPDATE, ERROR, DONE = ['+', '*', 'x', '-']
1625
1626class Buffer(object):
1627 def __init__(self, lock, num_plugs, is_pull):
1628 self.bar = ''
1629 self.event = 'Updating' if is_pull else 'Installing'
1630 self.lock = lock
1631 self.maxy = int(vim.eval('winheight(".")'))
1632 self.num_plugs = num_plugs
1633
1634 def __where(self, name):
1635 """ Find first line with name in current buffer. Return line num. """
1636 found, lnum = False, 0
1637 matcher = re.compile('^[-+x*] {0}:'.format(name))
1638 for line in vim.current.buffer:
1639 if matcher.search(line) is not None:
1640 found = True
1641 break
1642 lnum += 1
1643
1644 if not found:
1645 lnum = -1
1646 return lnum
1647
1648 def header(self):
1649 curbuf = vim.current.buffer
1650 curbuf[0] = self.event + ' plugins ({0}/{1})'.format(len(self.bar), self.num_plugs)
1651
1652 num_spaces = self.num_plugs - len(self.bar)
1653 curbuf[1] = '[{0}{1}]'.format(self.bar, num_spaces * ' ')
1654
1655 with self.lock:
1656 vim.command('normal! 2G')
1657 vim.command('redraw')
1658
1659 def write(self, action, name, lines):
1660 first, rest = lines[0], lines[1:]
1661 msg = ['{0} {1}{2}{3}'.format(action, name, ': ' if first else '', first)]
1662 msg.extend([' ' + line for line in rest])
1663
1664 try:
1665 if action == Action.ERROR:
1666 self.bar += 'x'
1667 vim.command("call add(s:update.errors, '{0}')".format(name))
1668 elif action == Action.DONE:
1669 self.bar += '='
1670
1671 curbuf = vim.current.buffer
1672 lnum = self.__where(name)
1673 if lnum != -1: # Found matching line num
1674 del curbuf[lnum]
1675 if lnum > self.maxy and action in set([Action.INSTALL, Action.UPDATE]):
1676 lnum = 3
1677 else:
1678 lnum = 3
1679 curbuf.append(msg, lnum)
1680
1681 self.header()
1682 except vim.error:
1683 pass
1684
1685class Command(object):
1686 CD = 'cd /d' if G_IS_WIN else 'cd'
1687
1688 def __init__(self, cmd, cmd_dir=None, timeout=60, cb=None, clean=None):
1689 self.cmd = cmd
1690 if cmd_dir:
1691 self.cmd = '{0} {1} && {2}'.format(Command.CD, cmd_dir, self.cmd)
1692 self.timeout = timeout
1693 self.callback = cb if cb else (lambda msg: None)
1694 self.clean = clean if clean else (lambda: None)
1695 self.proc = None
1696
1697 @property
1698 def alive(self):
1699 """ Returns true only if command still running. """
1700 return self.proc and self.proc.poll() is None
1701
1702 def execute(self, ntries=3):
1703 """ Execute the command with ntries if CmdTimedOut.
1704 Returns the output of the command if no Exception.
1705 """
1706 attempt, finished, limit = 0, False, self.timeout
1707
1708 while not finished:
1709 try:
1710 attempt += 1
1711 result = self.try_command()
1712 finished = True
1713 return result
1714 except CmdTimedOut:
1715 if attempt != ntries:
1716 self.notify_retry()
1717 self.timeout += limit
1718 else:
1719 raise
1720
1721 def notify_retry(self):
1722 """ Retry required for command, notify user. """
1723 for count in range(3, 0, -1):
1724 if G_STOP.is_set():
1725 raise KeyboardInterrupt
1726 msg = 'Timeout. Will retry in {0} second{1} ...'.format(
1727 count, 's' if count != 1 else '')
1728 self.callback([msg])
1729 time.sleep(1)
1730 self.callback(['Retrying ...'])
1731
1732 def try_command(self):
1733 """ Execute a cmd & poll for callback. Returns list of output.
1734 Raises CmdFailed -> return code for Popen isn't 0
1735 Raises CmdTimedOut -> command exceeded timeout without new output
1736 """
1737 first_line = True
1738
1739 try:
1740 tfile = tempfile.NamedTemporaryFile(mode='w+b')
1741 preexec_fn = not G_IS_WIN and os.setsid or None
1742 self.proc = subprocess.Popen(self.cmd, stdout=tfile,
1743 stderr=subprocess.STDOUT,
1744 stdin=subprocess.PIPE, shell=True,
1745 preexec_fn=preexec_fn)
1746 thrd = thr.Thread(target=(lambda proc: proc.wait()), args=(self.proc,))
1747 thrd.start()
1748
1749 thread_not_started = True
1750 while thread_not_started:
1751 try:
1752 thrd.join(0.1)
1753 thread_not_started = False
1754 except RuntimeError:
1755 pass
1756
1757 while self.alive:
1758 if G_STOP.is_set():
1759 raise KeyboardInterrupt
1760
1761 if first_line or random.random() < G_LOG_PROB:
1762 first_line = False
1763 line = '' if G_IS_WIN else nonblock_read(tfile.name)
1764 if line:
1765 self.callback([line])
1766
1767 time_diff = time.time() - os.path.getmtime(tfile.name)
1768 if time_diff > self.timeout:
1769 raise CmdTimedOut(['Timeout!'])
1770
1771 thrd.join(0.5)
1772
1773 tfile.seek(0)
1774 result = [line.decode('utf-8', 'replace').rstrip() for line in tfile]
1775
1776 if self.proc.returncode != 0:
1777 raise CmdFailed([''] + result)
1778
1779 return result
1780 except:
1781 self.terminate()
1782 raise
1783
1784 def terminate(self):
1785 """ Terminate process and cleanup. """
1786 if self.alive:
1787 if G_IS_WIN:
1788 os.kill(self.proc.pid, signal.SIGINT)
1789 else:
1790 os.killpg(self.proc.pid, signal.SIGTERM)
1791 self.clean()
1792
1793class Plugin(object):
1794 def __init__(self, name, args, buf_q, lock):
1795 self.name = name
1796 self.args = args
1797 self.buf_q = buf_q
1798 self.lock = lock
1799 self.tag = args.get('tag', 0)
1800
1801 def manage(self):
1802 try:
1803 if os.path.exists(self.args['dir']):
1804 self.update()
1805 else:
1806 self.install()
1807 with self.lock:
1808 thread_vim_command("let s:update.new['{0}'] = 1".format(self.name))
1809 except PlugError as exc:
1810 self.write(Action.ERROR, self.name, exc.msg)
1811 except KeyboardInterrupt:
1812 G_STOP.set()
1813 self.write(Action.ERROR, self.name, ['Interrupted!'])
1814 except:
1815 # Any exception except those above print stack trace
1816 msg = 'Trace:\n{0}'.format(traceback.format_exc().rstrip())
1817 self.write(Action.ERROR, self.name, msg.split('\n'))
1818 raise
1819
1820 def install(self):
1821 target = self.args['dir']
1822 if target[-1] == '\\':
1823 target = target[0:-1]
1824
1825 def clean(target):
1826 def _clean():
1827 try:
1828 shutil.rmtree(target)
1829 except OSError:
1830 pass
1831 return _clean
1832
1833 self.write(Action.INSTALL, self.name, ['Installing ...'])
1834 callback = functools.partial(self.write, Action.INSTALL, self.name)
1835 cmd = 'git clone {0} {1} {2} {3} 2>&1'.format(
1836 '' if self.tag else G_CLONE_OPT, G_PROGRESS, self.args['uri'],
1837 esc(target))
1838 com = Command(cmd, None, G_TIMEOUT, callback, clean(target))
1839 result = com.execute(G_RETRIES)
1840 self.write(Action.DONE, self.name, result[-1:])
1841
1842 def repo_uri(self):
1843 cmd = 'git rev-parse --abbrev-ref HEAD 2>&1 && git config -f .git/config remote.origin.url'
1844 command = Command(cmd, self.args['dir'], G_TIMEOUT,)
1845 result = command.execute(G_RETRIES)
1846 return result[-1]
1847
1848 def update(self):
1849 actual_uri = self.repo_uri()
1850 expect_uri = self.args['uri']
1851 regex = re.compile(r'^(?:\w+://)?(?:[^@/]*@)?([^:/]*(?::[0-9]*)?)[:/](.*?)(?:\.git)?/?$')
1852 ma = regex.match(actual_uri)
1853 mb = regex.match(expect_uri)
1854 if ma is None or mb is None or ma.groups() != mb.groups():
1855 msg = ['',
1856 'Invalid URI: {0}'.format(actual_uri),
1857 'Expected {0}'.format(expect_uri),
1858 'PlugClean required.']
1859 raise InvalidURI(msg)
1860
1861 if G_PULL:
1862 self.write(Action.UPDATE, self.name, ['Updating ...'])
1863 callback = functools.partial(self.write, Action.UPDATE, self.name)
1864 fetch_opt = '--depth 99999999' if self.tag and os.path.isfile(os.path.join(self.args['dir'], '.git/shallow')) else ''
1865 cmd = 'git fetch {0} {1} 2>&1'.format(fetch_opt, G_PROGRESS)
1866 com = Command(cmd, self.args['dir'], G_TIMEOUT, callback)
1867 result = com.execute(G_RETRIES)
1868 self.write(Action.DONE, self.name, result[-1:])
1869 else:
1870 self.write(Action.DONE, self.name, ['Already installed'])
1871
1872 def write(self, action, name, msg):
1873 self.buf_q.put((action, name, msg))
1874
1875class PlugThread(thr.Thread):
1876 def __init__(self, tname, args):
1877 super(PlugThread, self).__init__()
1878 self.tname = tname
1879 self.args = args
1880
1881 def run(self):
1882 thr.current_thread().name = self.tname
1883 buf_q, work_q, lock = self.args
1884
1885 try:
1886 while not G_STOP.is_set():
1887 name, args = work_q.get_nowait()
1888 plug = Plugin(name, args, buf_q, lock)
1889 plug.manage()
1890 work_q.task_done()
1891 except queue.Empty:
1892 pass
1893
1894class RefreshThread(thr.Thread):
1895 def __init__(self, lock):
1896 super(RefreshThread, self).__init__()
1897 self.lock = lock
1898 self.running = True
1899
1900 def run(self):
1901 while self.running:
1902 with self.lock:
1903 thread_vim_command('noautocmd normal! a')
1904 time.sleep(0.33)
1905
1906 def stop(self):
1907 self.running = False
1908
1909if G_NVIM:
1910 def thread_vim_command(cmd):
1911 vim.session.threadsafe_call(lambda: vim.command(cmd))
1912else:
1913 def thread_vim_command(cmd):
1914 vim.command(cmd)
1915
1916def esc(name):
1917 return '"' + name.replace('"', '\"') + '"'
1918
1919def nonblock_read(fname):
1920 """ Read a file with nonblock flag. Return the last line. """
1921 fread = os.open(fname, os.O_RDONLY | os.O_NONBLOCK)
1922 buf = os.read(fread, 100000).decode('utf-8', 'replace')
1923 os.close(fread)
1924
1925 line = buf.rstrip('\r\n')
1926 left = max(line.rfind('\r'), line.rfind('\n'))
1927 if left != -1:
1928 left += 1
1929 line = line[left:]
1930
1931 return line
1932
1933def main():
1934 thr.current_thread().name = 'main'
1935 nthreads = int(vim.eval('s:update.threads'))
1936 plugs = vim.eval('s:update.todo')
1937 mac_gui = vim.eval('s:mac_gui') == '1'
1938
1939 lock = thr.Lock()
1940 buf = Buffer(lock, len(plugs), G_PULL)
1941 buf_q, work_q = queue.Queue(), queue.Queue()
1942 for work in plugs.items():
1943 work_q.put(work)
1944
1945 start_cnt = thr.active_count()
1946 for num in range(nthreads):
1947 tname = 'PlugT-{0:02}'.format(num)
1948 thread = PlugThread(tname, (buf_q, work_q, lock))
1949 thread.start()
1950 if mac_gui:
1951 rthread = RefreshThread(lock)
1952 rthread.start()
1953
1954 while not buf_q.empty() or thr.active_count() != start_cnt:
1955 try:
1956 action, name, msg = buf_q.get(True, 0.25)
1957 buf.write(action, name, ['OK'] if not msg else msg)
1958 buf_q.task_done()
1959 except queue.Empty:
1960 pass
1961 except KeyboardInterrupt:
1962 G_STOP.set()
1963
1964 if mac_gui:
1965 rthread.stop()
1966 rthread.join()
1967
1968main()
1969EOF
1970endfunction
1971
1972function! s:update_ruby()
1973 ruby << EOF
1974 module PlugStream
1975 SEP = ["\r", "\n", nil]
1976 def get_line
1977 buffer = ''
1978 loop do
1979 char = readchar rescue return
1980 if SEP.include? char.chr
1981 buffer << $/
1982 break
1983 else
1984 buffer << char
1985 end
1986 end
1987 buffer
1988 end
1989 end unless defined?(PlugStream)
1990
1991 def esc arg
1992 %["#{arg.gsub('"', '\"')}"]
1993 end
1994
1995 def killall pid
1996 pids = [pid]
1997 if /mswin|mingw|bccwin/ =~ RUBY_PLATFORM
1998 pids.each { |pid| Process.kill 'INT', pid.to_i rescue nil }
1999 else
2000 unless `which pgrep 2> /dev/null`.empty?
2001 children = pids
2002 until children.empty?
2003 children = children.map { |pid|
2004 `pgrep -P #{pid}`.lines.map { |l| l.chomp }
2005 }.flatten
2006 pids += children
2007 end
2008 end
2009 pids.each { |pid| Process.kill 'TERM', pid.to_i rescue nil }
2010 end
2011 end
2012
2013 def compare_git_uri a, b
2014 regex = %r{^(?:\w+://)?(?:[^@/]*@)?([^:/]*(?::[0-9]*)?)[:/](.*?)(?:\.git)?/?$}
2015 regex.match(a).to_a.drop(1) == regex.match(b).to_a.drop(1)
2016 end
2017
2018 require 'thread'
2019 require 'fileutils'
2020 require 'timeout'
2021 running = true
2022 iswin = VIM::evaluate('s:is_win').to_i == 1
2023 pull = VIM::evaluate('s:update.pull').to_i == 1
2024 base = VIM::evaluate('g:plug_home')
2025 all = VIM::evaluate('s:update.todo')
2026 limit = VIM::evaluate('get(g:, "plug_timeout", 60)')
2027 tries = VIM::evaluate('get(g:, "plug_retries", 2)') + 1
2028 nthr = VIM::evaluate('s:update.threads').to_i
2029 maxy = VIM::evaluate('winheight(".")').to_i
2030 vim7 = VIM::evaluate('v:version').to_i <= 703 && RUBY_PLATFORM =~ /darwin/
2031 cd = iswin ? 'cd /d' : 'cd'
2032 tot = VIM::evaluate('len(s:update.todo)') || 0
2033 bar = ''
2034 skip = 'Already installed'
2035 mtx = Mutex.new
2036 take1 = proc { mtx.synchronize { running && all.shift } }
2037 logh = proc {
2038 cnt = bar.length
2039 $curbuf[1] = "#{pull ? 'Updating' : 'Installing'} plugins (#{cnt}/#{tot})"
2040 $curbuf[2] = '[' + bar.ljust(tot) + ']'
2041 VIM::command('normal! 2G')
2042 VIM::command('redraw')
2043 }
2044 where = proc { |name| (1..($curbuf.length)).find { |l| $curbuf[l] =~ /^[-+x*] #{name}:/ } }
2045 log = proc { |name, result, type|
2046 mtx.synchronize do
2047 ing = ![true, false].include?(type)
2048 bar += type ? '=' : 'x' unless ing
2049 b = case type
2050 when :install then '+' when :update then '*'
2051 when true, nil then '-' else
2052 VIM::command("call add(s:update.errors, '#{name}')")
2053 'x'
2054 end
2055 result =
2056 if type || type.nil?
2057 ["#{b} #{name}: #{result.lines.to_a.last || 'OK'}"]
2058 elsif result =~ /^Interrupted|^Timeout/
2059 ["#{b} #{name}: #{result}"]
2060 else
2061 ["#{b} #{name}"] + result.lines.map { |l| " " << l }
2062 end
2063 if lnum = where.call(name)
2064 $curbuf.delete lnum
2065 lnum = 4 if ing && lnum > maxy
2066 end
2067 result.each_with_index do |line, offset|
2068 $curbuf.append((lnum || 4) - 1 + offset, line.gsub(/\e\[./, '').chomp)
2069 end
2070 logh.call
2071 end
2072 }
2073 bt = proc { |cmd, name, type, cleanup|
2074 tried = timeout = 0
2075 begin
2076 tried += 1
2077 timeout += limit
2078 fd = nil
2079 data = ''
2080 if iswin
2081 Timeout::timeout(timeout) do
2082 tmp = VIM::evaluate('tempname()')
2083 system("(#{cmd}) > #{tmp}")
2084 data = File.read(tmp).chomp
2085 File.unlink tmp rescue nil
2086 end
2087 else
2088 fd = IO.popen(cmd).extend(PlugStream)
2089 first_line = true
2090 log_prob = 1.0 / nthr
2091 while line = Timeout::timeout(timeout) { fd.get_line }
2092 data << line
2093 log.call name, line.chomp, type if name && (first_line || rand < log_prob)
2094 first_line = false
2095 end
2096 fd.close
2097 end
2098 [$? == 0, data.chomp]
2099 rescue Timeout::Error, Interrupt => e
2100 if fd && !fd.closed?
2101 killall fd.pid
2102 fd.close
2103 end
2104 cleanup.call if cleanup
2105 if e.is_a?(Timeout::Error) && tried < tries
2106 3.downto(1) do |countdown|
2107 s = countdown > 1 ? 's' : ''
2108 log.call name, "Timeout. Will retry in #{countdown} second#{s} ...", type
2109 sleep 1
2110 end
2111 log.call name, 'Retrying ...', type
2112 retry
2113 end
2114 [false, e.is_a?(Interrupt) ? "Interrupted!" : "Timeout!"]
2115 end
2116 }
2117 main = Thread.current
2118 threads = []
2119 watcher = Thread.new {
2120 if vim7
2121 while VIM::evaluate('getchar(1)')
2122 sleep 0.1
2123 end
2124 else
2125 require 'io/console' # >= Ruby 1.9
2126 nil until IO.console.getch == 3.chr
2127 end
2128 mtx.synchronize do
2129 running = false
2130 threads.each { |t| t.raise Interrupt } unless vim7
2131 end
2132 threads.each { |t| t.join rescue nil }
2133 main.kill
2134 }
2135 refresh = Thread.new {
2136 while true
2137 mtx.synchronize do
2138 break unless running
2139 VIM::command('noautocmd normal! a')
2140 end
2141 sleep 0.2
2142 end
2143 } if VIM::evaluate('s:mac_gui') == 1
2144
b63b23c0 2145 clone_opt = VIM::evaluate('s:clone_opt').join(' ')
3f0234c4
BB
2146 progress = VIM::evaluate('s:progress_opt(1)')
2147 nthr.times do
2148 mtx.synchronize do
2149 threads << Thread.new {
2150 while pair = take1.call
2151 name = pair.first
2152 dir, uri, tag = pair.last.values_at *%w[dir uri tag]
2153 exists = File.directory? dir
2154 ok, result =
2155 if exists
2156 chdir = "#{cd} #{iswin ? dir : esc(dir)}"
2157 ret, data = bt.call "#{chdir} && git rev-parse --abbrev-ref HEAD 2>&1 && git config -f .git/config remote.origin.url", nil, nil, nil
2158 current_uri = data.lines.to_a.last
2159 if !ret
2160 if data =~ /^Interrupted|^Timeout/
2161 [false, data]
2162 else
2163 [false, [data.chomp, "PlugClean required."].join($/)]
2164 end
2165 elsif !compare_git_uri(current_uri, uri)
2166 [false, ["Invalid URI: #{current_uri}",
2167 "Expected: #{uri}",
2168 "PlugClean required."].join($/)]
2169 else
2170 if pull
2171 log.call name, 'Updating ...', :update
2172 fetch_opt = (tag && File.exist?(File.join(dir, '.git/shallow'))) ? '--depth 99999999' : ''
2173 bt.call "#{chdir} && git fetch #{fetch_opt} #{progress} 2>&1", name, :update, nil
2174 else
2175 [true, skip]
2176 end
2177 end
2178 else
2179 d = esc dir.sub(%r{[\\/]+$}, '')
2180 log.call name, 'Installing ...', :install
2181 bt.call "git clone #{clone_opt unless tag} #{progress} #{uri} #{d} 2>&1", name, :install, proc {
2182 FileUtils.rm_rf dir
2183 }
2184 end
2185 mtx.synchronize { VIM::command("let s:update.new['#{name}'] = 1") } if !exists && ok
2186 log.call name, result, ok
2187 end
2188 } if running
2189 end
2190 end
2191 threads.each { |t| t.join rescue nil }
2192 logh.call
2193 refresh.kill if refresh
2194 watcher.kill
2195EOF
2196endfunction
2197
2198function! s:shellesc_cmd(arg, script)
2199 let escaped = substitute('"'.a:arg.'"', '[&|<>()@^!"]', '^&', 'g')
2200 return substitute(escaped, '%', (a:script ? '%' : '^') . '&', 'g')
2201endfunction
2202
2203function! s:shellesc_ps1(arg)
2204 return "'".substitute(escape(a:arg, '\"'), "'", "''", 'g')."'"
2205endfunction
2206
2207function! s:shellesc_sh(arg)
2208 return "'".substitute(a:arg, "'", "'\\\\''", 'g')."'"
2209endfunction
2210
b63b23c0
RBR
2211" Escape the shell argument based on the shell.
2212" Vim and Neovim's shellescape() are insufficient.
2213" 1. shellslash determines whether to use single/double quotes.
2214" Double-quote escaping is fragile for cmd.exe.
2215" 2. It does not work for powershell.
2216" 3. It does not work for *sh shells if the command is executed
2217" via cmd.exe (ie. cmd.exe /c sh -c command command_args)
2218" 4. It does not support batchfile syntax.
2219"
2220" Accepts an optional dictionary with the following keys:
2221" - shell: same as Vim/Neovim 'shell' option.
2222" If unset, fallback to 'cmd.exe' on Windows or 'sh'.
2223" - script: If truthy and shell is cmd.exe, escape for batchfile syntax.
3f0234c4 2224function! plug#shellescape(arg, ...)
b63b23c0
RBR
2225 if a:arg =~# '^[A-Za-z0-9_/:.-]\+$'
2226 return a:arg
2227 endif
3f0234c4
BB
2228 let opts = a:0 > 0 && type(a:1) == s:TYPE.dict ? a:1 : {}
2229 let shell = get(opts, 'shell', s:is_win ? 'cmd.exe' : 'sh')
2230 let script = get(opts, 'script', 1)
b63b23c0 2231 if shell =~# 'cmd\(\.exe\)\?$'
3f0234c4 2232 return s:shellesc_cmd(a:arg, script)
b63b23c0 2233 elseif s:is_powershell(shell)
3f0234c4
BB
2234 return s:shellesc_ps1(a:arg)
2235 endif
2236 return s:shellesc_sh(a:arg)
2237endfunction
2238
2239function! s:glob_dir(path)
2240 return map(filter(s:glob(a:path, '**'), 'isdirectory(v:val)'), 's:dirpath(v:val)')
2241endfunction
2242
2243function! s:progress_bar(line, bar, total)
2244 call setline(a:line, '[' . s:lpad(a:bar, a:total) . ']')
2245endfunction
2246
2247function! s:compare_git_uri(a, b)
2248 " See `git help clone'
2249 " https:// [user@] github.com[:port] / junegunn/vim-plug [.git]
2250 " [git@] github.com[:port] : junegunn/vim-plug [.git]
2251 " file:// / junegunn/vim-plug [/]
2252 " / junegunn/vim-plug [/]
2253 let pat = '^\%(\w\+://\)\='.'\%([^@/]*@\)\='.'\([^:/]*\%(:[0-9]*\)\=\)'.'[:/]'.'\(.\{-}\)'.'\%(\.git\)\=/\?$'
2254 let ma = matchlist(a:a, pat)
2255 let mb = matchlist(a:b, pat)
2256 return ma[1:2] ==# mb[1:2]
2257endfunction
2258
2259function! s:format_message(bullet, name, message)
2260 if a:bullet != 'x'
2261 return [printf('%s %s: %s', a:bullet, a:name, s:lastline(a:message))]
2262 else
2263 let lines = map(s:lines(a:message), '" ".v:val')
2264 return extend([printf('x %s:', a:name)], lines)
2265 endif
2266endfunction
2267
2268function! s:with_cd(cmd, dir, ...)
2269 let script = a:0 > 0 ? a:1 : 1
2270 return printf('cd%s %s && %s', s:is_win ? ' /d' : '', plug#shellescape(a:dir, {'script': script}), a:cmd)
2271endfunction
2272
2273function! s:system(cmd, ...)
2274 let batchfile = ''
2275 try
2276 let [sh, shellcmdflag, shrd] = s:chsh(1)
b63b23c0
RBR
2277 if type(a:cmd) == s:TYPE.list
2278 " Neovim's system() supports list argument to bypass the shell
2279 " but it cannot set the working directory for the command.
2280 " Assume that the command does not rely on the shell.
2281 if has('nvim') && a:0 == 0
2282 return system(a:cmd)
2283 endif
2284 let cmd = join(map(copy(a:cmd), 'plug#shellescape(v:val, {"shell": &shell, "script": 0})'))
2285 if s:is_powershell(&shell)
2286 let cmd = '& ' . cmd
2287 endif
2288 else
2289 let cmd = a:cmd
2290 endif
2291 if a:0 > 0
2292 let cmd = s:with_cd(cmd, a:1, type(a:cmd) != s:TYPE.list)
2293 endif
2294 if s:is_win && type(a:cmd) != s:TYPE.list
3f0234c4
BB
2295 let [batchfile, cmd] = s:batchfile(cmd)
2296 endif
2297 return system(cmd)
2298 finally
2299 let [&shell, &shellcmdflag, &shellredir] = [sh, shellcmdflag, shrd]
2300 if s:is_win && filereadable(batchfile)
2301 call delete(batchfile)
2302 endif
2303 endtry
2304endfunction
2305
2306function! s:system_chomp(...)
2307 let ret = call('s:system', a:000)
2308 return v:shell_error ? '' : substitute(ret, '\n$', '', '')
2309endfunction
2310
2311function! s:git_validate(spec, check_branch)
2312 let err = ''
2313 if isdirectory(a:spec.dir)
b63b23c0 2314 let result = [s:git_local_branch(a:spec.dir), s:git_origin_url(a:spec.dir)]
3f0234c4 2315 let remote = result[-1]
b63b23c0 2316 if empty(remote)
3f0234c4
BB
2317 let err = join([remote, 'PlugClean required.'], "\n")
2318 elseif !s:compare_git_uri(remote, a:spec.uri)
2319 let err = join(['Invalid URI: '.remote,
2320 \ 'Expected: '.a:spec.uri,
2321 \ 'PlugClean required.'], "\n")
2322 elseif a:check_branch && has_key(a:spec, 'commit')
b63b23c0
RBR
2323 let sha = s:git_revision(a:spec.dir)
2324 if empty(sha)
3f0234c4
BB
2325 let err = join(add(result, 'PlugClean required.'), "\n")
2326 elseif !s:hash_match(sha, a:spec.commit)
2327 let err = join([printf('Invalid HEAD (expected: %s, actual: %s)',
2328 \ a:spec.commit[:6], sha[:6]),
2329 \ 'PlugUpdate required.'], "\n")
2330 endif
2331 elseif a:check_branch
b63b23c0 2332 let current_branch = result[0]
3f0234c4 2333 " Check tag
b63b23c0 2334 let origin_branch = s:git_origin_branch(a:spec)
3f0234c4
BB
2335 if has_key(a:spec, 'tag')
2336 let tag = s:system_chomp('git describe --exact-match --tags HEAD 2>&1', a:spec.dir)
2337 if a:spec.tag !=# tag && a:spec.tag !~ '\*'
2338 let err = printf('Invalid tag: %s (expected: %s). Try PlugUpdate.',
2339 \ (empty(tag) ? 'N/A' : tag), a:spec.tag)
2340 endif
2341 " Check branch
b63b23c0 2342 elseif origin_branch !=# current_branch
3f0234c4 2343 let err = printf('Invalid branch: %s (expected: %s). Try PlugUpdate.',
b63b23c0 2344 \ current_branch, origin_branch)
3f0234c4
BB
2345 endif
2346 if empty(err)
b63b23c0
RBR
2347 let [ahead, behind] = split(s:lastline(s:system([
2348 \ 'git', 'rev-list', '--count', '--left-right',
2349 \ printf('HEAD...origin/%s', origin_branch)
2350 \ ], a:spec.dir)), '\t')
3f0234c4
BB
2351 if !v:shell_error && ahead
2352 if behind
2353 " Only mention PlugClean if diverged, otherwise it's likely to be
2354 " pushable (and probably not that messed up).
2355 let err = printf(
2356 \ "Diverged from origin/%s (%d commit(s) ahead and %d commit(s) behind!\n"
b63b23c0 2357 \ .'Backup local changes and run PlugClean and PlugUpdate to reinstall it.', origin_branch, ahead, behind)
3f0234c4
BB
2358 else
2359 let err = printf("Ahead of origin/%s by %d commit(s).\n"
2360 \ .'Cannot update until local changes are pushed.',
b63b23c0 2361 \ origin_branch, ahead)
3f0234c4
BB
2362 endif
2363 endif
2364 endif
2365 endif
2366 else
2367 let err = 'Not found'
2368 endif
2369 return [err, err =~# 'PlugClean']
2370endfunction
2371
2372function! s:rm_rf(dir)
2373 if isdirectory(a:dir)
b63b23c0
RBR
2374 return s:system(s:is_win
2375 \ ? 'rmdir /S /Q '.plug#shellescape(a:dir)
2376 \ : ['rm', '-rf', a:dir])
3f0234c4
BB
2377 endif
2378endfunction
2379
2380function! s:clean(force)
2381 call s:prepare()
2382 call append(0, 'Searching for invalid plugins in '.g:plug_home)
2383 call append(1, '')
2384
2385 " List of valid directories
2386 let dirs = []
2387 let errs = {}
2388 let [cnt, total] = [0, len(g:plugs)]
2389 for [name, spec] in items(g:plugs)
2390 if !s:is_managed(name)
2391 call add(dirs, spec.dir)
2392 else
2393 let [err, clean] = s:git_validate(spec, 1)
2394 if clean
2395 let errs[spec.dir] = s:lines(err)[0]
2396 else
2397 call add(dirs, spec.dir)
2398 endif
2399 endif
2400 let cnt += 1
2401 call s:progress_bar(2, repeat('=', cnt), total)
2402 normal! 2G
2403 redraw
2404 endfor
2405
2406 let allowed = {}
2407 for dir in dirs
2408 let allowed[s:dirpath(s:plug_fnamemodify(dir, ':h:h'))] = 1
2409 let allowed[dir] = 1
2410 for child in s:glob_dir(dir)
2411 let allowed[child] = 1
2412 endfor
2413 endfor
2414
2415 let todo = []
2416 let found = sort(s:glob_dir(g:plug_home))
2417 while !empty(found)
2418 let f = remove(found, 0)
2419 if !has_key(allowed, f) && isdirectory(f)
2420 call add(todo, f)
2421 call append(line('$'), '- ' . f)
2422 if has_key(errs, f)
2423 call append(line('$'), ' ' . errs[f])
2424 endif
2425 let found = filter(found, 'stridx(v:val, f) != 0')
2426 end
2427 endwhile
2428
2429 4
2430 redraw
2431 if empty(todo)
2432 call append(line('$'), 'Already clean.')
2433 else
2434 let s:clean_count = 0
2435 call append(3, ['Directories to delete:', ''])
2436 redraw!
2437 if a:force || s:ask_no_interrupt('Delete all directories?')
2438 call s:delete([6, line('$')], 1)
2439 else
2440 call setline(4, 'Cancelled.')
2441 nnoremap <silent> <buffer> d :set opfunc=<sid>delete_op<cr>g@
2442 nmap <silent> <buffer> dd d_
2443 xnoremap <silent> <buffer> d :<c-u>call <sid>delete_op(visualmode(), 1)<cr>
2444 echo 'Delete the lines (d{motion}) to delete the corresponding directories'
2445 endif
2446 endif
2447 4
2448 setlocal nomodifiable
2449endfunction
2450
2451function! s:delete_op(type, ...)
2452 call s:delete(a:0 ? [line("'<"), line("'>")] : [line("'["), line("']")], 0)
2453endfunction
2454
2455function! s:delete(range, force)
2456 let [l1, l2] = a:range
2457 let force = a:force
b63b23c0 2458 let err_count = 0
3f0234c4
BB
2459 while l1 <= l2
2460 let line = getline(l1)
2461 if line =~ '^- ' && isdirectory(line[2:])
2462 execute l1
2463 redraw!
2464 let answer = force ? 1 : s:ask('Delete '.line[2:].'?', 1)
2465 let force = force || answer > 1
2466 if answer
b63b23c0 2467 let err = s:rm_rf(line[2:])
3f0234c4 2468 setlocal modifiable
b63b23c0
RBR
2469 if empty(err)
2470 call setline(l1, '~'.line[1:])
2471 let s:clean_count += 1
2472 else
2473 delete _
2474 call append(l1 - 1, s:format_message('x', line[1:], err))
2475 let l2 += len(s:lines(err))
2476 let err_count += 1
2477 endif
2478 let msg = printf('Removed %d directories.', s:clean_count)
2479 if err_count > 0
2480 let msg .= printf(' Failed to remove %d directories.', err_count)
2481 endif
2482 call setline(4, msg)
3f0234c4
BB
2483 setlocal nomodifiable
2484 endif
2485 endif
2486 let l1 += 1
2487 endwhile
2488endfunction
2489
2490function! s:upgrade()
2491 echo 'Downloading the latest version of vim-plug'
2492 redraw
2493 let tmp = s:plug_tempname()
2494 let new = tmp . '/plug.vim'
2495
2496 try
b63b23c0 2497 let out = s:system(['git', 'clone', '--depth', '1', s:plug_src, tmp])
3f0234c4
BB
2498 if v:shell_error
2499 return s:err('Error upgrading vim-plug: '. out)
2500 endif
2501
2502 if readfile(s:me) ==# readfile(new)
2503 echo 'vim-plug is already up-to-date'
2504 return 0
2505 else
2506 call rename(s:me, s:me . '.old')
2507 call rename(new, s:me)
2508 unlet g:loaded_plug
2509 echo 'vim-plug has been upgraded'
2510 return 1
2511 endif
2512 finally
2513 silent! call s:rm_rf(tmp)
2514 endtry
2515endfunction
2516
2517function! s:upgrade_specs()
2518 for spec in values(g:plugs)
2519 let spec.frozen = get(spec, 'frozen', 0)
2520 endfor
2521endfunction
2522
2523function! s:status()
2524 call s:prepare()
2525 call append(0, 'Checking plugins')
2526 call append(1, '')
2527
2528 let ecnt = 0
2529 let unloaded = 0
2530 let [cnt, total] = [0, len(g:plugs)]
2531 for [name, spec] in items(g:plugs)
2532 let is_dir = isdirectory(spec.dir)
2533 if has_key(spec, 'uri')
2534 if is_dir
2535 let [err, _] = s:git_validate(spec, 1)
2536 let [valid, msg] = [empty(err), empty(err) ? 'OK' : err]
2537 else
2538 let [valid, msg] = [0, 'Not found. Try PlugInstall.']
2539 endif
2540 else
2541 if is_dir
2542 let [valid, msg] = [1, 'OK']
2543 else
2544 let [valid, msg] = [0, 'Not found.']
2545 endif
2546 endif
2547 let cnt += 1
2548 let ecnt += !valid
2549 " `s:loaded` entry can be missing if PlugUpgraded
2550 if is_dir && get(s:loaded, name, -1) == 0
2551 let unloaded = 1
2552 let msg .= ' (not loaded)'
2553 endif
2554 call s:progress_bar(2, repeat('=', cnt), total)
2555 call append(3, s:format_message(valid ? '-' : 'x', name, msg))
2556 normal! 2G
2557 redraw
2558 endfor
2559 call setline(1, 'Finished. '.ecnt.' error(s).')
2560 normal! gg
2561 setlocal nomodifiable
2562 if unloaded
2563 echo "Press 'L' on each line to load plugin, or 'U' to update"
2564 nnoremap <silent> <buffer> L :call <SID>status_load(line('.'))<cr>
2565 xnoremap <silent> <buffer> L :call <SID>status_load(line('.'))<cr>
2566 end
2567endfunction
2568
2569function! s:extract_name(str, prefix, suffix)
2570 return matchstr(a:str, '^'.a:prefix.' \zs[^:]\+\ze:.*'.a:suffix.'$')
2571endfunction
2572
2573function! s:status_load(lnum)
2574 let line = getline(a:lnum)
2575 let name = s:extract_name(line, '-', '(not loaded)')
2576 if !empty(name)
2577 call plug#load(name)
2578 setlocal modifiable
2579 call setline(a:lnum, substitute(line, ' (not loaded)$', '', ''))
2580 setlocal nomodifiable
2581 endif
2582endfunction
2583
2584function! s:status_update() range
2585 let lines = getline(a:firstline, a:lastline)
2586 let names = filter(map(lines, 's:extract_name(v:val, "[x-]", "")'), '!empty(v:val)')
2587 if !empty(names)
2588 echo
2589 execute 'PlugUpdate' join(names)
2590 endif
2591endfunction
2592
2593function! s:is_preview_window_open()
2594 silent! wincmd P
2595 if &previewwindow
2596 wincmd p
2597 return 1
2598 endif
2599endfunction
2600
2601function! s:find_name(lnum)
2602 for lnum in reverse(range(1, a:lnum))
2603 let line = getline(lnum)
2604 if empty(line)
2605 return ''
2606 endif
2607 let name = s:extract_name(line, '-', '')
2608 if !empty(name)
2609 return name
2610 endif
2611 endfor
2612 return ''
2613endfunction
2614
2615function! s:preview_commit()
2616 if b:plug_preview < 0
2617 let b:plug_preview = !s:is_preview_window_open()
2618 endif
2619
2620 let sha = matchstr(getline('.'), '^ \X*\zs[0-9a-f]\{7,9}')
2621 if empty(sha)
2622 return
2623 endif
2624
2625 let name = s:find_name(line('.'))
2626 if empty(name) || !has_key(g:plugs, name) || !isdirectory(g:plugs[name].dir)
2627 return
2628 endif
2629
2630 if exists('g:plug_pwindow') && !s:is_preview_window_open()
2631 execute g:plug_pwindow
2632 execute 'e' sha
2633 else
2634 execute 'pedit' sha
2635 wincmd P
2636 endif
2637 setlocal previewwindow filetype=git buftype=nofile nobuflisted modifiable
2638 let batchfile = ''
2639 try
2640 let [sh, shellcmdflag, shrd] = s:chsh(1)
2641 let cmd = 'cd '.plug#shellescape(g:plugs[name].dir).' && git show --no-color --pretty=medium '.sha
2642 if s:is_win
2643 let [batchfile, cmd] = s:batchfile(cmd)
2644 endif
2645 execute 'silent %!' cmd
2646 finally
2647 let [&shell, &shellcmdflag, &shellredir] = [sh, shellcmdflag, shrd]
2648 if s:is_win && filereadable(batchfile)
2649 call delete(batchfile)
2650 endif
2651 endtry
2652 setlocal nomodifiable
2653 nnoremap <silent> <buffer> q :q<cr>
2654 wincmd p
2655endfunction
2656
2657function! s:section(flags)
2658 call search('\(^[x-] \)\@<=[^:]\+:', a:flags)
2659endfunction
2660
2661function! s:format_git_log(line)
2662 let indent = ' '
2663 let tokens = split(a:line, nr2char(1))
2664 if len(tokens) != 5
2665 return indent.substitute(a:line, '\s*$', '', '')
2666 endif
2667 let [graph, sha, refs, subject, date] = tokens
2668 let tag = matchstr(refs, 'tag: [^,)]\+')
2669 let tag = empty(tag) ? ' ' : ' ('.tag.') '
2670 return printf('%s%s%s%s%s (%s)', indent, graph, sha, tag, subject, date)
2671endfunction
2672
2673function! s:append_ul(lnum, text)
2674 call append(a:lnum, ['', a:text, repeat('-', len(a:text))])
2675endfunction
2676
2677function! s:diff()
2678 call s:prepare()
2679 call append(0, ['Collecting changes ...', ''])
2680 let cnts = [0, 0]
2681 let bar = ''
2682 let total = filter(copy(g:plugs), 's:is_managed(v:key) && isdirectory(v:val.dir)')
2683 call s:progress_bar(2, bar, len(total))
2684 for origin in [1, 0]
2685 let plugs = reverse(sort(items(filter(copy(total), (origin ? '' : '!').'(has_key(v:val, "commit") || has_key(v:val, "tag"))'))))
2686 if empty(plugs)
2687 continue
2688 endif
2689 call s:append_ul(2, origin ? 'Pending updates:' : 'Last update:')
2690 for [k, v] in plugs
b63b23c0
RBR
2691 let branch = s:git_origin_branch(v)
2692 if len(branch)
2693 let range = origin ? '..origin/'.branch : 'HEAD@{1}..'
2694 let cmd = ['git', 'log', '--graph', '--color=never']
2695 if s:git_version_requirement(2, 10, 0)
2696 call add(cmd, '--no-show-signature')
2697 endif
2698 call extend(cmd, ['--pretty=format:%x01%h%x01%d%x01%s%x01%cr', range])
2699 if has_key(v, 'rtp')
2700 call extend(cmd, ['--', v.rtp])
2701 endif
2702 let diff = s:system_chomp(cmd, v.dir)
2703 if !empty(diff)
2704 let ref = has_key(v, 'tag') ? (' (tag: '.v.tag.')') : has_key(v, 'commit') ? (' '.v.commit) : ''
2705 call append(5, extend(['', '- '.k.':'.ref], map(s:lines(diff), 's:format_git_log(v:val)')))
2706 let cnts[origin] += 1
2707 endif
3f0234c4
BB
2708 endif
2709 let bar .= '='
2710 call s:progress_bar(2, bar, len(total))
2711 normal! 2G
2712 redraw
2713 endfor
2714 if !cnts[origin]
2715 call append(5, ['', 'N/A'])
2716 endif
2717 endfor
2718 call setline(1, printf('%d plugin(s) updated.', cnts[0])
2719 \ . (cnts[1] ? printf(' %d plugin(s) have pending updates.', cnts[1]) : ''))
2720
2721 if cnts[0] || cnts[1]
2722 nnoremap <silent> <buffer> <plug>(plug-preview) :silent! call <SID>preview_commit()<cr>
2723 if empty(maparg("\<cr>", 'n'))
2724 nmap <buffer> <cr> <plug>(plug-preview)
2725 endif
2726 if empty(maparg('o', 'n'))
2727 nmap <buffer> o <plug>(plug-preview)
2728 endif
2729 endif
2730 if cnts[0]
2731 nnoremap <silent> <buffer> X :call <SID>revert()<cr>
2732 echo "Press 'X' on each block to revert the update"
2733 endif
2734 normal! gg
2735 setlocal nomodifiable
2736endfunction
2737
2738function! s:revert()
2739 if search('^Pending updates', 'bnW')
2740 return
2741 endif
2742
2743 let name = s:find_name(line('.'))
2744 if empty(name) || !has_key(g:plugs, name) ||
2745 \ input(printf('Revert the update of %s? (y/N) ', name)) !~? '^y'
2746 return
2747 endif
2748
2749 call s:system('git reset --hard HEAD@{1} && git checkout '.plug#shellescape(g:plugs[name].branch).' --', g:plugs[name].dir)
2750 setlocal modifiable
2751 normal! "_dap
2752 setlocal nomodifiable
2753 echo 'Reverted'
2754endfunction
2755
2756function! s:snapshot(force, ...) abort
2757 call s:prepare()
2758 setf vim
2759 call append(0, ['" Generated by vim-plug',
2760 \ '" '.strftime("%c"),
2761 \ '" :source this file in vim to restore the snapshot',
2762 \ '" or execute: vim -S snapshot.vim',
2763 \ '', '', 'PlugUpdate!'])
2764 1
2765 let anchor = line('$') - 3
2766 let names = sort(keys(filter(copy(g:plugs),
2767 \'has_key(v:val, "uri") && !has_key(v:val, "commit") && isdirectory(v:val.dir)')))
2768 for name in reverse(names)
b63b23c0 2769 let sha = s:git_revision(g:plugs[name].dir)
3f0234c4
BB
2770 if !empty(sha)
2771 call append(anchor, printf("silent! let g:plugs['%s'].commit = '%s'", name, sha))
2772 redraw
2773 endif
2774 endfor
2775
2776 if a:0 > 0
2777 let fn = s:plug_expand(a:1)
2778 if filereadable(fn) && !(a:force || s:ask(a:1.' already exists. Overwrite?'))
2779 return
2780 endif
2781 call writefile(getline(1, '$'), fn)
2782 echo 'Saved as '.a:1
2783 silent execute 'e' s:esc(fn)
2784 setf vim
2785 endif
2786endfunction
2787
2788function! s:split_rtp()
2789 return split(&rtp, '\\\@<!,')
2790endfunction
2791
2792let s:first_rtp = s:escrtp(get(s:split_rtp(), 0, ''))
2793let s:last_rtp = s:escrtp(get(s:split_rtp(), -1, ''))
2794
2795if exists('g:plugs')
2796 let g:plugs_order = get(g:, 'plugs_order', keys(g:plugs))
2797 call s:upgrade_specs()
2798 call s:define_commands()
2799endif
2800
2801let &cpo = s:cpo_save
2802unlet s:cpo_save