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