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