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