1 " vim-plug: Vim plugin manager
2 " ============================
4 " Download plug.vim and put it in ~/.vim/autoload
6 " curl -fLo ~/.vim/autoload/plug.vim --create-dirs \
7 " https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim
11 " call plug#begin('~/.vim/plugged')
13 " " Make sure you use single quotes
15 " " Shorthand notation; fetches https://github.com/junegunn/vim-easy-align
16 " Plug 'junegunn/vim-easy-align'
18 " " Any valid git URL is allowed
19 " Plug 'https://github.com/junegunn/vim-github-dashboard.git'
21 " " Multiple Plug commands can be written in a single line using | separators
22 " Plug 'SirVer/ultisnips' | Plug 'honza/vim-snippets'
25 " Plug 'scrooloose/nerdtree', { 'on': 'NERDTreeToggle' }
26 " Plug 'tpope/vim-fireplace', { 'for': 'clojure' }
28 " " Using a non-master branch
29 " Plug 'rdnetto/YCM-Generator', { 'branch': 'stable' }
31 " " Using a tagged release; wildcard allowed (requires git 1.9.2 or above)
32 " Plug 'fatih/vim-go', { 'tag': '*' }
35 " Plug 'nsf/gocode', { 'tag': 'v.20150303', 'rtp': 'vim' }
37 " " Plugin outside ~/.vim/plugged with post-update hook
38 " Plug 'junegunn/fzf', { 'dir': '~/.fzf', 'do': './install --all' }
40 " " Unmanaged plugin (manually installed and updated)
41 " Plug '~/my-prototype-plugin'
43 " " Initialize plugin system
46 " Then reload .vimrc and :PlugInstall to install plugins.
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 |
61 " More information: https://github.com/junegunn/vim-plug
64 " Copyright (c) 2017 Junegunn Choi
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:
76 " The above copyright notice and this permission notice shall be
77 " included in all copies or substantial portions of the Software.
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.
87 if exists('g:loaded_plug')
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
104 let s:me = resolve(expand('<sfile>:p'))
107 let s:me = resolve(expand('<sfile>:p'))
109 let s:base_spec = { 'branch': 'master', 'frozen': 0 }
111 \ 'string': type(''),
114 \ 'funcref': type(function('call'))
116 let s:loaded = get(s:, 'loaded', {})
117 let s:triggers = get(s:, 'triggers', {})
120 function! s:plug_call(fn, ...)
121 let shellslash = &shellslash
124 return call(a:fn, a:000)
126 let &shellslash = shellslash
130 function! s:plug_call(fn, ...)
131 return call(a:fn, a:000)
135 function! s:plug_getcwd()
136 return s:plug_call('getcwd')
139 function! s:plug_fnamemodify(fname, mods)
140 return s:plug_call('fnamemodify', a:fname, a:mods)
143 function! s:plug_expand(fmt)
144 return s:plug_call('expand', a:fmt, 1)
147 function! s:plug_tempname()
148 return s:plug_call('tempname')
151 function! plug#begin(...)
153 let s:plug_home_org = a:1
154 let home = s:path(s:plug_fnamemodify(s:plug_expand(a:1), ':p'))
155 elseif exists('g:plug_home')
156 let home = s:path(g:plug_home)
158 let home = s:path(split(&rtp, ',')[0]) . '/plugged'
160 return s:err('Unable to determine plug home. Try calling plug#begin() with a path argument.')
162 if s:plug_fnamemodify(home, ':t') ==# 'plugin' && s:plug_fnamemodify(home, ':h') ==# s:first_rtp
163 return s:err('Invalid plug home. '.home.' is a standard Vim runtime path and is not allowed.')
166 let g:plug_home = home
168 let g:plugs_order = []
171 call s:define_commands()
175 function! s:define_commands()
176 command! -nargs=+ -bar Plug call plug#(<args>)
177 if !executable('git')
178 return s:err('`git` executable not found. Most commands will not be available. To suppress this message, prepend `silent!` to `call plug#begin(...)`.')
182 \ && (&shell =~# 'cmd\.exe' || &shell =~# 'powershell\.exe')
183 return s:err('vim-plug does not support shell, ' . &shell . ', when shellslash is set.')
186 \ && (has('win32') || has('win32unix'))
187 \ && !has('multi_byte')
188 return s:err('Vim needs +multi_byte feature on Windows to run shell commands. Enable +iconv for best results.')
190 command! -nargs=* -bar -bang -complete=customlist,s:names PlugInstall call s:install(<bang>0, [<f-args>])
191 command! -nargs=* -bar -bang -complete=customlist,s:names PlugUpdate call s:update(<bang>0, [<f-args>])
192 command! -nargs=0 -bar -bang PlugClean call s:clean(<bang>0)
193 command! -nargs=0 -bar PlugUpgrade if s:upgrade() | execute 'source' s:esc(s:me) | endif
194 command! -nargs=0 -bar PlugStatus call s:status()
195 command! -nargs=0 -bar PlugDiff call s:diff()
196 command! -nargs=? -bar -bang -complete=file PlugSnapshot call s:snapshot(<bang>0, <f-args>)
200 return type(a:v) == s:TYPE.list ? a:v : [a:v]
204 return type(a:v) == s:TYPE.string ? a:v : join(a:v, "\n") . "\n"
207 function! s:glob(from, pattern)
208 return s:lines(globpath(a:from, a:pattern))
211 function! s:source(from, ...)
214 for vim in s:glob(a:from, pattern)
215 execute 'source' s:esc(vim)
222 function! s:assoc(dict, key, val)
223 let a:dict[a:key] = add(get(a:dict, a:key, []), a:val)
226 function! s:ask(message, ...)
229 let answer = input(a:message.(a:0 ? ' (y/N/a) ' : ' (y/N) '))
233 return (a:0 && answer =~? '^a') ? 2 : (answer =~? '^y') ? 1 : 0
236 function! s:ask_no_interrupt(...)
238 return call('s:ask', a:000)
244 function! s:lazy(plug, opt)
245 return has_key(a:plug, a:opt) &&
246 \ (empty(s:to_a(a:plug[a:opt])) ||
247 \ !isdirectory(a:plug.dir) ||
248 \ len(s:glob(s:rtp(a:plug), 'plugin')) ||
249 \ len(s:glob(s:rtp(a:plug), 'after/plugin')))
253 if !exists('g:plugs')
254 return s:err('plug#end() called without calling plug#begin() first')
257 if exists('#PlugLOD')
263 let lod = { 'ft': {}, 'map': {}, 'cmd': {} }
265 if exists('g:did_load_filetypes')
268 for name in g:plugs_order
269 if !has_key(g:plugs, name)
272 let plug = g:plugs[name]
273 if get(s:loaded, name, 0) || !s:lazy(plug, 'on') && !s:lazy(plug, 'for')
274 let s:loaded[name] = 1
278 if has_key(plug, 'on')
279 let s:triggers[name] = { 'map': [], 'cmd': [] }
280 for cmd in s:to_a(plug.on)
281 if cmd =~? '^<Plug>.\+'
282 if empty(mapcheck(cmd)) && empty(mapcheck(cmd, 'i'))
283 call s:assoc(lod.map, cmd, name)
285 call add(s:triggers[name].map, cmd)
286 elseif cmd =~# '^[A-Z]'
287 let cmd = substitute(cmd, '!*$', '', '')
288 if exists(':'.cmd) != 2
289 call s:assoc(lod.cmd, cmd, name)
291 call add(s:triggers[name].cmd, cmd)
293 call s:err('Invalid `on` option: '.cmd.
294 \ '. Should start with an uppercase letter or `<Plug>`.')
299 if has_key(plug, 'for')
300 let types = s:to_a(plug.for)
302 augroup filetypedetect
303 call s:source(s:rtp(plug), 'ftdetect/**/*.vim', 'after/ftdetect/**/*.vim')
307 call s:assoc(lod.ft, type, name)
312 for [cmd, names] in items(lod.cmd)
314 \ 'command! -nargs=* -range -bang -complete=file %s call s:lod_cmd(%s, "<bang>", <line1>, <line2>, <q-args>, %s)',
315 \ cmd, string(cmd), string(names))
318 for [map, names] in items(lod.map)
319 for [mode, map_prefix, key_prefix] in
320 \ [['i', '<C-O>', ''], ['n', '', ''], ['v', '', 'gv'], ['o', '', '']]
322 \ '%snoremap <silent> %s %s:<C-U>call <SID>lod_map(%s, %s, %s, "%s")<CR>',
323 \ mode, map, map_prefix, string(map), string(names), mode != 'i', key_prefix)
327 for [ft, names] in items(lod.ft)
329 execute printf('autocmd FileType %s call <SID>lod_ft(%s, %s)',
330 \ ft, string(ft), string(names))
335 filetype plugin indent on
336 if has('vim_starting')
337 if has('syntax') && !exists('g:syntax_on')
341 call s:reload_plugins()
345 function! s:loaded_names()
346 return filter(copy(g:plugs_order), 'get(s:loaded, v:val, 0)')
349 function! s:load_plugin(spec)
350 call s:source(s:rtp(a:spec), 'plugin/**/*.vim', 'after/plugin/**/*.vim')
353 function! s:reload_plugins()
354 for name in s:loaded_names()
355 call s:load_plugin(g:plugs[name])
359 function! s:trim(str)
360 return substitute(a:str, '[\/]\+$', '', '')
363 function! s:version_requirement(val, min)
364 for idx in range(0, len(a:min) - 1)
365 let v = get(a:val, idx, 0)
366 if v < a:min[idx] | return 0
367 elseif v > a:min[idx] | return 1
373 function! s:git_version_requirement(...)
374 if !exists('s:git_version')
375 let s:git_version = map(split(split(s:system(['git', '--version']))[2], '\.'), 'str2nr(v:val)')
377 return s:version_requirement(s:git_version, a:000)
380 function! s:progress_opt(base)
381 return a:base && !s:is_win &&
382 \ s:git_version_requirement(1, 7, 1) ? '--progress' : ''
385 function! s:rtp(spec)
386 return s:path(a:spec.dir . get(a:spec, 'rtp', ''))
390 function! s:path(path)
391 return s:trim(substitute(a:path, '/', '\', 'g'))
394 function! s:dirpath(path)
395 return s:path(a:path) . '\'
398 function! s:is_local_plug(repo)
399 return a:repo =~? '^[a-z]:\|^[%~]'
403 function! s:wrap_cmds(cmds)
406 \ 'setlocal enabledelayedexpansion']
407 \ + (type(a:cmds) == type([]) ? a:cmds : [a:cmds])
410 if !exists('s:codepage')
411 let s:codepage = libcallnr('kernel32.dll', 'GetACP', 0)
413 return map(cmds, printf('iconv(v:val."\r", "%s", "cp%d")', &encoding, s:codepage))
415 return map(cmds, 'v:val."\r"')
418 function! s:batchfile(cmd)
419 let batchfile = s:plug_tempname().'.bat'
420 call writefile(s:wrap_cmds(a:cmd), batchfile)
421 let cmd = plug#shellescape(batchfile, {'shell': &shell, 'script': 0})
422 if &shell =~# 'powershell\.exe'
425 return [batchfile, cmd]
428 function! s:path(path)
429 return s:trim(a:path)
432 function! s:dirpath(path)
433 return substitute(a:path, '[/\\]*$', '/', '')
436 function! s:is_local_plug(repo)
437 return a:repo[0] =~ '[/$~]'
443 echom '[vim-plug] '.a:msg
447 function! s:warn(cmd, msg)
449 execute a:cmd 'a:msg'
453 function! s:esc(path)
454 return escape(a:path, ' ')
457 function! s:escrtp(path)
458 return escape(a:path, ' ,')
461 function! s:remove_rtp()
462 for name in s:loaded_names()
463 let rtp = s:rtp(g:plugs[name])
464 execute 'set rtp-='.s:escrtp(rtp)
465 let after = globpath(rtp, 'after')
466 if isdirectory(after)
467 execute 'set rtp-='.s:escrtp(after)
472 function! s:reorg_rtp()
473 if !empty(s:first_rtp)
474 execute 'set rtp-='.s:first_rtp
475 execute 'set rtp-='.s:last_rtp
478 " &rtp is modified from outside
479 if exists('s:prtp') && s:prtp !=# &rtp
484 let s:middle = get(s:, 'middle', &rtp)
485 let rtps = map(s:loaded_names(), 's:rtp(g:plugs[v:val])')
486 let afters = filter(map(copy(rtps), 'globpath(v:val, "after")'), '!empty(v:val)')
487 let rtp = join(map(rtps, 'escape(v:val, ",")'), ',')
489 \ . join(map(afters, 'escape(v:val, ",")'), ',')
490 let &rtp = substitute(substitute(rtp, ',,*', ',', 'g'), '^,\|,$', '', 'g')
493 if !empty(s:first_rtp)
494 execute 'set rtp^='.s:first_rtp
495 execute 'set rtp+='.s:last_rtp
499 function! s:doautocmd(...)
500 if exists('#'.join(a:000, '#'))
501 execute 'doautocmd' ((v:version > 703 || has('patch442')) ? '<nomodeline>' : '') join(a:000)
505 function! s:dobufread(names)
507 let path = s:rtp(g:plugs[name])
508 for dir in ['ftdetect', 'ftplugin', 'after/ftdetect', 'after/ftplugin']
509 if len(finddir(dir, path))
510 if exists('#BufRead')
519 function! plug#load(...)
521 return s:err('Argument missing: plugin name(s) required')
523 if !exists('g:plugs')
524 return s:err('plug#begin was not called')
526 let names = a:0 == 1 && type(a:1) == s:TYPE.list ? a:1 : a:000
527 let unknowns = filter(copy(names), '!has_key(g:plugs, v:val)')
529 let s = len(unknowns) > 1 ? 's' : ''
530 return s:err(printf('Unknown plugin%s: %s', s, join(unknowns, ', ')))
532 let unloaded = filter(copy(names), '!get(s:loaded, v:val, 0)')
535 call s:lod([name], ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin'])
537 call s:dobufread(unloaded)
543 function! s:remove_triggers(name)
544 if !has_key(s:triggers, a:name)
547 for cmd in s:triggers[a:name].cmd
548 execute 'silent! delc' cmd
550 for map in s:triggers[a:name].map
551 execute 'silent! unmap' map
552 execute 'silent! iunmap' map
554 call remove(s:triggers, a:name)
557 function! s:lod(names, types, ...)
559 call s:remove_triggers(name)
560 let s:loaded[name] = 1
565 let rtp = s:rtp(g:plugs[name])
567 call s:source(rtp, dir.'/**/*.vim')
570 if !s:source(rtp, a:1) && !empty(s:glob(rtp, a:2))
571 execute 'runtime' a:1
573 call s:source(rtp, a:2)
575 call s:doautocmd('User', name)
579 function! s:lod_ft(pat, names)
580 let syn = 'syntax/'.a:pat.'.vim'
581 call s:lod(a:names, ['plugin', 'after/plugin'], syn, 'after/'.syn)
582 execute 'autocmd! PlugLOD FileType' a:pat
583 call s:doautocmd('filetypeplugin', 'FileType')
584 call s:doautocmd('filetypeindent', 'FileType')
587 function! s:lod_cmd(cmd, bang, l1, l2, args, names)
588 call s:lod(a:names, ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin'])
589 call s:dobufread(a:names)
590 execute printf('%s%s%s %s', (a:l1 == a:l2 ? '' : (a:l1.','.a:l2)), a:cmd, a:bang, a:args)
593 function! s:lod_map(map, names, with_prefix, prefix)
594 call s:lod(a:names, ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin'])
595 call s:dobufread(a:names)
602 let extra .= nr2char(c)
606 let prefix = v:count ? v:count : ''
607 let prefix .= '"'.v:register.a:prefix
610 let prefix = "\<esc>" . prefix
612 let prefix .= v:operator
614 call feedkeys(prefix, 'n')
616 call feedkeys(substitute(a:map, '^<Plug>', "\<Plug>", '') . extra)
619 function! plug#(repo, ...)
621 return s:err('Invalid number of arguments (1..2)')
625 let repo = s:trim(a:repo)
626 let opts = a:0 == 1 ? s:parse_options(a:1) : s:base_spec
627 let name = get(opts, 'as', s:plug_fnamemodify(repo, ':t:s?\.git$??'))
628 let spec = extend(s:infer_properties(name, repo), opts)
629 if !has_key(g:plugs, name)
630 call add(g:plugs_order, name)
632 let g:plugs[name] = spec
633 let s:loaded[name] = get(s:loaded, name, 0)
635 return s:err(v:exception)
639 function! s:parse_options(arg)
640 let opts = copy(s:base_spec)
641 let type = type(a:arg)
642 if type == s:TYPE.string
644 elseif type == s:TYPE.dict
645 call extend(opts, a:arg)
646 if has_key(opts, 'dir')
647 let opts.dir = s:dirpath(s:plug_expand(opts.dir))
650 throw 'Invalid argument type (expected: string or dictionary)'
655 function! s:infer_properties(name, repo)
657 if s:is_local_plug(repo)
658 return { 'dir': s:dirpath(s:plug_expand(repo)) }
664 throw printf('Invalid argument: %s (implicit `vim-scripts'' expansion is deprecated)', repo)
666 let fmt = get(g:, 'plug_url_format', 'https://git::@github.com/%s.git')
667 let uri = printf(fmt, repo)
669 return { 'dir': s:dirpath(g:plug_home.'/'.a:name), 'uri': uri }
673 function! s:install(force, names)
674 call s:update_impl(0, a:force, a:names)
677 function! s:update(force, names)
678 call s:update_impl(1, a:force, a:names)
681 function! plug#helptags()
682 if !exists('g:plugs')
683 return s:err('plug#begin was not called')
685 for spec in values(g:plugs)
686 let docd = join([s:rtp(spec), 'doc'], '/')
688 silent! execute 'helptags' s:esc(docd)
696 syntax region plug1 start=/\%1l/ end=/\%2l/ contains=plugNumber
697 syntax region plug2 start=/\%2l/ end=/\%3l/ contains=plugBracket,plugX
698 syn match plugNumber /[0-9]\+[0-9.]*/ contained
699 syn match plugBracket /[[\]]/ contained
700 syn match plugX /x/ contained
701 syn match plugDash /^-/
702 syn match plugPlus /^+/
703 syn match plugStar /^*/
704 syn match plugMessage /\(^- \)\@<=.*/
705 syn match plugName /\(^- \)\@<=[^ ]*:/
706 syn match plugSha /\%(: \)\@<=[0-9a-f]\{4,}$/
707 syn match plugTag /(tag: [^)]\+)/
708 syn match plugInstall /\(^+ \)\@<=[^:]*/
709 syn match plugUpdate /\(^* \)\@<=[^:]*/
710 syn match plugCommit /^ \X*[0-9a-f]\{7,9} .*/ contains=plugRelDate,plugEdge,plugTag
711 syn match plugEdge /^ \X\+$/
712 syn match plugEdge /^ \X*/ contained nextgroup=plugSha
713 syn match plugSha /[0-9a-f]\{7,9}/ contained
714 syn match plugRelDate /([^)]*)$/ contained
715 syn match plugNotLoaded /(not loaded)$/
716 syn match plugError /^x.*/
717 syn region plugDeleted start=/^\~ .*/ end=/^\ze\S/
718 syn match plugH2 /^.*:\n-\+$/
719 syn keyword Function PlugInstall PlugStatus PlugUpdate PlugClean
720 hi def link plug1 Title
721 hi def link plug2 Repeat
722 hi def link plugH2 Type
723 hi def link plugX Exception
724 hi def link plugBracket Structure
725 hi def link plugNumber Number
727 hi def link plugDash Special
728 hi def link plugPlus Constant
729 hi def link plugStar Boolean
731 hi def link plugMessage Function
732 hi def link plugName Label
733 hi def link plugInstall Function
734 hi def link plugUpdate Type
736 hi def link plugError Error
737 hi def link plugDeleted Ignore
738 hi def link plugRelDate Comment
739 hi def link plugEdge PreProc
740 hi def link plugSha Identifier
741 hi def link plugTag Constant
743 hi def link plugNotLoaded Comment
746 function! s:lpad(str, len)
747 return a:str . repeat(' ', a:len - len(a:str))
750 function! s:lines(msg)
751 return split(a:msg, "[\r\n]")
754 function! s:lastline(msg)
755 return get(s:lines(a:msg), -1, '')
758 function! s:new_window()
759 execute get(g:, 'plug_window', 'vertical topleft new')
762 function! s:plug_window_exists()
763 let buflist = tabpagebuflist(s:plug_tab)
764 return !empty(buflist) && index(buflist, s:plug_buf) >= 0
767 function! s:switch_in()
768 if !s:plug_window_exists()
772 if winbufnr(0) != s:plug_buf
773 let s:pos = [tabpagenr(), winnr(), winsaveview()]
774 execute 'normal!' s:plug_tab.'gt'
775 let winnr = bufwinnr(s:plug_buf)
776 execute winnr.'wincmd w'
777 call add(s:pos, winsaveview())
779 let s:pos = [winsaveview()]
786 function! s:switch_out(...)
787 call winrestview(s:pos[-1])
788 setlocal nomodifiable
794 execute 'normal!' s:pos[0].'gt'
795 execute s:pos[1] 'wincmd w'
796 call winrestview(s:pos[2])
800 function! s:finish_bindings()
801 nnoremap <silent> <buffer> R :call <SID>retry()<cr>
802 nnoremap <silent> <buffer> D :PlugDiff<cr>
803 nnoremap <silent> <buffer> S :PlugStatus<cr>
804 nnoremap <silent> <buffer> U :call <SID>status_update()<cr>
805 xnoremap <silent> <buffer> U :call <SID>status_update()<cr>
806 nnoremap <silent> <buffer> ]] :silent! call <SID>section('')<cr>
807 nnoremap <silent> <buffer> [[ :silent! call <SID>section('b')<cr>
810 function! s:prepare(...)
811 if empty(s:plug_getcwd())
812 throw 'Invalid current working directory. Cannot proceed.'
815 for evar in ['$GIT_DIR', '$GIT_WORK_TREE']
817 throw evar.' detected. Cannot proceed.'
823 if b:plug_preview == 1
831 nnoremap <silent> <buffer> q :if b:plug_preview==1<bar>pc<bar>endif<bar>bd<cr>
833 call s:finish_bindings()
835 let b:plug_preview = -1
836 let s:plug_tab = tabpagenr()
837 let s:plug_buf = winbufnr(0)
840 for k in ['<cr>', 'L', 'o', 'X', 'd', 'dd']
841 execute 'silent! unmap <buffer>' k
843 setlocal buftype=nofile bufhidden=wipe nobuflisted nolist noswapfile nowrap cursorline modifiable nospell
844 if exists('+colorcolumn')
845 setlocal colorcolumn=
848 if exists('g:syntax_on')
853 function! s:assign_name()
855 let prefix = '[Plugins]'
858 while bufexists(name)
859 let name = printf('%s (%s)', prefix, idx)
862 silent! execute 'f' fnameescape(name)
865 function! s:chsh(swap)
866 let prev = [&shell, &shellcmdflag, &shellredir]
871 if &shell =~# 'powershell\.exe' || &shell =~# 'pwsh$'
872 let &shellredir = '2>&1 | Out-File -Encoding UTF8 %s'
873 elseif &shell =~# 'sh' || &shell =~# 'cmd\.exe'
874 set shellredir=>%s\ 2>&1
880 function! s:bang(cmd, ...)
883 let [sh, shellcmdflag, shrd] = s:chsh(a:0)
884 " FIXME: Escaping is incomplete. We could use shellescape with eval,
885 " but it won't work on Windows.
886 let cmd = a:0 ? s:with_cd(a:cmd, a:1) : a:cmd
888 let [batchfile, cmd] = s:batchfile(cmd)
890 let g:_plug_bang = (s:is_win && has('gui_running') ? 'silent ' : '').'!'.escape(cmd, '#!%')
891 execute "normal! :execute g:_plug_bang\<cr>\<cr>"
894 let [&shell, &shellcmdflag, &shellredir] = [sh, shellcmdflag, shrd]
895 if s:is_win && filereadable(batchfile)
896 call delete(batchfile)
899 return v:shell_error ? 'Exit status: ' . v:shell_error : ''
902 function! s:regress_bar()
903 let bar = substitute(getline(2)[1:-2], '.*\zs=', 'x', '')
904 call s:progress_bar(2, bar, len(bar))
907 function! s:is_updated(dir)
908 return !empty(s:system_chomp(['git', 'log', '--pretty=format:%h', 'HEAD...HEAD@{1}'], a:dir))
911 function! s:do(pull, force, todo)
912 for [name, spec] in items(a:todo)
913 if !isdirectory(spec.dir)
916 let installed = has_key(s:update.new, name)
917 let updated = installed ? 0 :
918 \ (a:pull && index(s:update.errors, name) < 0 && s:is_updated(spec.dir))
919 if a:force || installed || updated
920 execute 'cd' s:esc(spec.dir)
921 call append(3, '- Post-update hook for '. name .' ... ')
923 let type = type(spec.do)
924 if type == s:TYPE.string
926 if !get(s:loaded, name, 0)
927 let s:loaded[name] = 1
930 call s:load_plugin(spec)
934 let error = v:exception
936 if !s:plug_window_exists()
938 throw 'Warning: vim-plug was terminated by the post-update hook of '.name
941 let error = s:bang(spec.do)
943 elseif type == s:TYPE.funcref
945 call s:load_plugin(spec)
946 let status = installed ? 'installed' : (updated ? 'updated' : 'unchanged')
947 call spec.do({ 'name': name, 'status': status, 'force': a:force })
949 let error = v:exception
952 let error = 'Invalid hook type'
955 call setline(4, empty(error) ? (getline(4) . 'OK')
956 \ : ('x' . getline(4)[1:] . error))
958 call add(s:update.errors, name)
966 function! s:hash_match(a, b)
967 return stridx(a:a, a:b) == 0 || stridx(a:b, a:a) == 0
970 function! s:checkout(spec)
971 let sha = a:spec.commit
972 let output = s:system(['git', 'rev-parse', 'HEAD'], a:spec.dir)
973 if !v:shell_error && !s:hash_match(sha, s:lines(output)[0])
974 let output = s:system(
975 \ 'git fetch --depth 999999 && git checkout '.plug#shellescape(sha).' --', a:spec.dir)
980 function! s:finish(pull)
981 let new_frozen = len(filter(keys(s:update.new), 'g:plugs[v:val].frozen'))
983 let s = new_frozen > 1 ? 's' : ''
984 call append(3, printf('- Installed %d frozen plugin%s', new_frozen, s))
986 call append(3, '- Finishing ... ') | 4
990 call setline(4, getline(4) . 'Done!')
993 if !empty(s:update.errors)
994 call add(msgs, "Press 'R' to retry.")
996 if a:pull && len(s:update.new) < len(filter(getline(5, '$'),
997 \ "v:val =~ '^- ' && v:val !~# 'Already up.to.date'"))
998 call add(msgs, "Press 'D' to see the updated changes.")
1000 echo join(msgs, ' ')
1001 call s:finish_bindings()
1005 if empty(s:update.errors)
1009 call s:update_impl(s:update.pull, s:update.force,
1010 \ extend(copy(s:update.errors), [s:update.threads]))
1013 function! s:is_managed(name)
1014 return has_key(g:plugs[a:name], 'uri')
1017 function! s:names(...)
1018 return sort(filter(keys(g:plugs), 'stridx(v:val, a:1) == 0 && s:is_managed(v:val)'))
1021 function! s:check_ruby()
1022 silent! ruby require 'thread'; VIM::command("let g:plug_ruby = '#{RUBY_VERSION}'")
1023 if !exists('g:plug_ruby')
1025 return s:warn('echom', 'Warning: Ruby interface is broken')
1027 let ruby_version = split(g:plug_ruby, '\.')
1029 return s:version_requirement(ruby_version, [1, 8, 7])
1032 function! s:update_impl(pull, force, args) abort
1033 let sync = index(a:args, '--sync') >= 0 || has('vim_starting')
1034 let args = filter(copy(a:args), 'v:val != "--sync"')
1035 let threads = (len(args) > 0 && args[-1] =~ '^[1-9][0-9]*$') ?
1036 \ remove(args, -1) : get(g:, 'plug_threads', 16)
1038 let managed = filter(copy(g:plugs), 's:is_managed(v:key)')
1039 let todo = empty(args) ? filter(managed, '!v:val.frozen || !isdirectory(v:val.dir)') :
1040 \ filter(managed, 'index(args, v:key) >= 0')
1043 return s:warn('echo', 'No plugin to '. (a:pull ? 'update' : 'install'))
1046 if !s:is_win && s:git_version_requirement(2, 3)
1047 let s:git_terminal_prompt = exists('$GIT_TERMINAL_PROMPT') ? $GIT_TERMINAL_PROMPT : ''
1048 let $GIT_TERMINAL_PROMPT = 0
1049 for plug in values(todo)
1050 let plug.uri = substitute(plug.uri,
1051 \ '^https://git::@github\.com', 'https://github.com', '')
1055 if !isdirectory(g:plug_home)
1057 call mkdir(g:plug_home, 'p')
1059 return s:err(printf('Invalid plug directory: %s. '.
1060 \ 'Try to call plug#begin with a valid directory', g:plug_home))
1064 if has('nvim') && !exists('*jobwait') && threads > 1
1065 call s:warn('echom', '[vim-plug] Update Neovim for parallel installer')
1068 let use_job = s:nvim || s:vim8
1069 let python = (has('python') || has('python3')) && !use_job
1070 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()
1073 \ 'start': reltime(),
1075 \ 'todo': copy(todo),
1080 \ 'threads': (python || ruby || use_job) ? min([len(todo), threads]) : 1,
1086 call append(0, ['', ''])
1090 let s:clone_opt = []
1091 if get(g:, 'plug_shallow', 1)
1092 call extend(s:clone_opt, ['--depth', '1'])
1093 if s:git_version_requirement(1, 7, 10)
1094 call add(s:clone_opt, '--no-single-branch')
1098 if has('win32unix') || has('wsl')
1099 call extend(s:clone_opt, ['-c', 'core.eol=lf', '-c', 'core.autocrlf=input'])
1102 let s:submodule_opt = s:git_version_requirement(2, 8) ? ' --jobs='.threads : ''
1104 " Python version requirement (>= 2.7)
1105 if python && !has('python3') && !ruby && !use_job && s:update.threads > 1
1107 silent python import platform; print platform.python_version()
1109 let python = s:version_requirement(
1110 \ map(split(split(pyv)[0], '\.'), 'str2nr(v:val)'), [2, 6])
1113 if (python || ruby) && s:update.threads > 1
1120 call s:update_ruby()
1122 call s:update_python()
1125 let lines = getline(4, '$')
1129 let name = s:extract_name(line, '.', '')
1130 if empty(name) || !has_key(printed, name)
1131 call append('$', line)
1133 let printed[name] = 1
1134 if line[0] == 'x' && index(s:update.errors, name) < 0
1135 call add(s:update.errors, name)
1142 call s:update_finish()
1146 while use_job && sync
1155 function! s:log4(name, msg)
1156 call setline(4, printf('- %s (%s)', a:msg, a:name))
1160 function! s:update_finish()
1161 if exists('s:git_terminal_prompt')
1162 let $GIT_TERMINAL_PROMPT = s:git_terminal_prompt
1165 call append(3, '- Updating ...') | 4
1166 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))'))
1167 let [pos, _] = s:logpos(name)
1171 if has_key(spec, 'commit')
1172 call s:log4(name, 'Checking out '.spec.commit)
1173 let out = s:checkout(spec)
1174 elseif has_key(spec, 'tag')
1177 let tags = s:lines(s:system('git tag --list '.plug#shellescape(tag).' --sort -version:refname 2>&1', spec.dir))
1178 if !v:shell_error && !empty(tags)
1180 call s:log4(name, printf('Latest tag for %s -> %s', spec.tag, tag))
1184 call s:log4(name, 'Checking out '.tag)
1185 let out = s:system('git checkout -q '.plug#shellescape(tag).' -- 2>&1', spec.dir)
1187 let branch = get(spec, 'branch', 'master')
1188 call s:log4(name, 'Merging origin/'.s:esc(branch))
1189 let out = s:system('git checkout -q '.plug#shellescape(branch).' -- 2>&1'
1190 \. (has_key(s:update.new, name) ? '' : ('&& git merge --ff-only '.plug#shellescape('origin/'.branch).' 2>&1')), spec.dir)
1192 if !v:shell_error && filereadable(spec.dir.'/.gitmodules') &&
1193 \ (s:update.force || has_key(s:update.new, name) || s:is_updated(spec.dir))
1194 call s:log4(name, 'Updating submodules. This may take a while.')
1195 let out .= s:bang('git submodule update --init --recursive'.s:submodule_opt.' 2>&1', spec.dir)
1197 let msg = s:format_message(v:shell_error ? 'x': '-', name, out)
1199 call add(s:update.errors, name)
1200 call s:regress_bar()
1201 silent execute pos 'd _'
1202 call append(4, msg) | 4
1204 call setline(pos, msg[0])
1210 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")'))
1212 call s:warn('echom', v:exception)
1213 call s:warn('echo', '')
1216 call s:finish(s:update.pull)
1217 call setline(1, 'Updated. Elapsed time: ' . split(reltimestr(reltime(s:update.start)))[0] . ' sec.')
1218 call s:switch_out('normal! gg')
1222 function! s:job_abort()
1223 if (!s:nvim && !s:vim8) || !exists('s:jobs')
1227 for [name, j] in items(s:jobs)
1229 silent! call jobstop(j.jobid)
1231 silent! call job_stop(j.jobid)
1234 call s:rm_rf(g:plugs[name].dir)
1240 function! s:last_non_empty_line(lines)
1241 let len = len(a:lines)
1242 for idx in range(len)
1243 let line = a:lines[len-idx-1]
1251 function! s:job_out_cb(self, data) abort
1253 let data = remove(self.lines, -1) . a:data
1254 let lines = map(split(data, "\n", 1), 'split(v:val, "\r", 1)[-1]')
1255 call extend(self.lines, lines)
1256 " To reduce the number of buffer updates
1257 let self.tick = get(self, 'tick', -1) + 1
1258 if !self.running || self.tick % len(s:jobs) == 0
1259 let bullet = self.running ? (self.new ? '+' : '*') : (self.error ? 'x' : '-')
1260 let result = self.error ? join(self.lines, "\n") : s:last_non_empty_line(self.lines)
1261 call s:log(bullet, self.name, result)
1265 function! s:job_exit_cb(self, data) abort
1266 let a:self.running = 0
1267 let a:self.error = a:data != 0
1268 call s:reap(a:self.name)
1272 function! s:job_cb(fn, job, ch, data)
1273 if !s:plug_window_exists() " plug window closed
1274 return s:job_abort()
1276 call call(a:fn, [a:job, a:data])
1279 function! s:nvim_cb(job_id, data, event) dict abort
1280 return (a:event == 'stdout' || a:event == 'stderr') ?
1281 \ s:job_cb('s:job_out_cb', self, 0, join(a:data, "\n")) :
1282 \ s:job_cb('s:job_exit_cb', self, 0, a:data)
1285 function! s:spawn(name, cmd, opts)
1286 let job = { 'name': a:name, 'running': 1, 'error': 0, 'lines': [''],
1287 \ 'new': get(a:opts, 'new', 0) }
1288 let s:jobs[a:name] = job
1291 if has_key(a:opts, 'dir')
1292 let job.cwd = a:opts.dir
1296 \ 'on_stdout': function('s:nvim_cb'),
1297 \ 'on_stderr': function('s:nvim_cb'),
1298 \ 'on_exit': function('s:nvim_cb'),
1300 let jid = s:plug_call('jobstart', argv, job)
1306 let job.lines = [jid < 0 ? argv[0].' is not executable' :
1307 \ 'Invalid arguments (or job table is full)']
1310 let cmd = join(map(copy(a:cmd), 'plug#shellescape(v:val, {"script": 0})'))
1311 if has_key(a:opts, 'dir')
1312 let cmd = s:with_cd(cmd, a:opts.dir, 0)
1314 let argv = s:is_win ? ['cmd', '/s', '/c', '"'.cmd.'"'] : ['sh', '-c', cmd]
1315 let jid = job_start(s:is_win ? join(argv, ' ') : argv, {
1316 \ 'out_cb': function('s:job_cb', ['s:job_out_cb', job]),
1317 \ 'err_cb': function('s:job_cb', ['s:job_out_cb', job]),
1318 \ 'exit_cb': function('s:job_cb', ['s:job_exit_cb', job]),
1319 \ 'err_mode': 'raw',
1322 if job_status(jid) == 'run'
1327 let job.lines = ['Failed to start job']
1330 let job.lines = s:lines(call('s:system', has_key(a:opts, 'dir') ? [a:cmd, a:opts.dir] : [a:cmd]))
1331 let job.error = v:shell_error != 0
1336 function! s:reap(name)
1337 let job = s:jobs[a:name]
1339 call add(s:update.errors, a:name)
1340 elseif get(job, 'new', 0)
1341 let s:update.new[a:name] = 1
1343 let s:update.bar .= job.error ? 'x' : '='
1345 let bullet = job.error ? 'x' : '-'
1346 let result = job.error ? join(job.lines, "\n") : s:last_non_empty_line(job.lines)
1347 call s:log(bullet, a:name, empty(result) ? 'OK' : result)
1350 call remove(s:jobs, a:name)
1355 let total = len(s:update.all)
1356 call setline(1, (s:update.pull ? 'Updating' : 'Installing').
1357 \ ' plugins ('.len(s:update.bar).'/'.total.')')
1358 call s:progress_bar(2, s:update.bar, total)
1363 function! s:logpos(name)
1365 for i in range(4, max > 4 ? max : 4)
1366 if getline(i) =~# '^[-+x*] '.a:name.':'
1367 for j in range(i + 1, max > 5 ? max : 5)
1368 if getline(j) !~ '^ '
1378 function! s:log(bullet, name, lines)
1380 let [b, e] = s:logpos(a:name)
1382 silent execute printf('%d,%d d _', b, e)
1383 if b > winheight('.')
1389 " FIXME For some reason, nomodifiable is set after :d in vim8
1391 call append(b - 1, s:format_message(a:bullet, a:name, a:lines))
1396 function! s:update_vim()
1404 let pull = s:update.pull
1405 let prog = s:progress_opt(s:nvim || s:vim8)
1406 while 1 " Without TCO, Vim stack is bound to explode
1407 if empty(s:update.todo)
1408 if empty(s:jobs) && !s:update.fin
1409 call s:update_finish()
1410 let s:update.fin = 1
1415 let name = keys(s:update.todo)[0]
1416 let spec = remove(s:update.todo, name)
1417 let new = empty(globpath(spec.dir, '.git', 1))
1419 call s:log(new ? '+' : '*', name, pull ? 'Updating ...' : 'Installing ...')
1422 let has_tag = has_key(spec, 'tag')
1424 let [error, _] = s:git_validate(spec, 0)
1427 let cmd = ['git', 'fetch']
1428 if has_tag && !empty(globpath(spec.dir, '.git/shallow'))
1429 call extend(cmd, ['--depth', '99999999'])
1434 call s:spawn(name, cmd, { 'dir': spec.dir })
1436 let s:jobs[name] = { 'running': 0, 'lines': ['Already installed'], 'error': 0 }
1439 let s:jobs[name] = { 'running': 0, 'lines': s:lines(error), 'error': 1 }
1442 let cmd = ['git', 'clone']
1444 call extend(cmd, s:clone_opt)
1449 call s:spawn(name, extend(cmd, [spec.uri, s:trim(spec.dir)]), { 'new': 1 })
1452 if !s:jobs[name].running
1455 if len(s:jobs) >= s:update.threads
1461 function! s:update_python()
1462 let py_exe = has('python') ? 'python' : 'python3'
1463 execute py_exe "<< EOF"
1470 import Queue as queue
1477 import threading as thr
1482 G_NVIM = vim.eval("has('nvim')") == '1'
1483 G_PULL = vim.eval('s:update.pull') == '1'
1484 G_RETRIES = int(vim.eval('get(g:, "plug_retries", 2)')) + 1
1485 G_TIMEOUT = int(vim.eval('get(g:, "plug_timeout", 60)'))
1486 G_CLONE_OPT = ' '.join(vim.eval('s:clone_opt'))
1487 G_PROGRESS = vim.eval('s:progress_opt(1)')
1488 G_LOG_PROB = 1.0 / int(vim.eval('s:update.threads'))
1489 G_STOP = thr.Event()
1490 G_IS_WIN = vim.eval('s:is_win') == '1'
1492 class PlugError(Exception):
1493 def __init__(self, msg):
1495 class CmdTimedOut(PlugError):
1497 class CmdFailed(PlugError):
1499 class InvalidURI(PlugError):
1501 class Action(object):
1502 INSTALL, UPDATE, ERROR, DONE = ['+', '*', 'x', '-']
1504 class Buffer(object):
1505 def __init__(self, lock, num_plugs, is_pull):
1507 self.event = 'Updating' if is_pull else 'Installing'
1509 self.maxy = int(vim.eval('winheight(".")'))
1510 self.num_plugs = num_plugs
1512 def __where(self, name):
1513 """ Find first line with name in current buffer. Return line num. """
1514 found, lnum = False, 0
1515 matcher = re.compile('^[-+x*] {0}:'.format(name))
1516 for line in vim.current.buffer:
1517 if matcher.search(line) is not None:
1527 curbuf = vim.current.buffer
1528 curbuf[0] = self.event + ' plugins ({0}/{1})'.format(len(self.bar), self.num_plugs)
1530 num_spaces = self.num_plugs - len(self.bar)
1531 curbuf[1] = '[{0}{1}]'.format(self.bar, num_spaces * ' ')
1534 vim.command('normal! 2G')
1535 vim.command('redraw')
1537 def write(self, action, name, lines):
1538 first, rest = lines[0], lines[1:]
1539 msg = ['{0} {1}{2}{3}'.format(action, name, ': ' if first else '', first)]
1540 msg.extend([' ' + line for line in rest])
1543 if action == Action.ERROR:
1545 vim.command("call add(s:update.errors, '{0}')".format(name))
1546 elif action == Action.DONE:
1549 curbuf = vim.current.buffer
1550 lnum = self.__where(name)
1551 if lnum != -1: # Found matching line num
1553 if lnum > self.maxy and action in set([Action.INSTALL, Action.UPDATE]):
1557 curbuf.append(msg, lnum)
1563 class Command(object):
1564 CD = 'cd /d' if G_IS_WIN else 'cd'
1566 def __init__(self, cmd, cmd_dir=None, timeout=60, cb=None, clean=None):
1569 self.cmd = '{0} {1} && {2}'.format(Command.CD, cmd_dir, self.cmd)
1570 self.timeout = timeout
1571 self.callback = cb if cb else (lambda msg: None)
1572 self.clean = clean if clean else (lambda: None)
1577 """ Returns true only if command still running. """
1578 return self.proc and self.proc.poll() is None
1580 def execute(self, ntries=3):
1581 """ Execute the command with ntries if CmdTimedOut.
1582 Returns the output of the command if no Exception.
1584 attempt, finished, limit = 0, False, self.timeout
1589 result = self.try_command()
1593 if attempt != ntries:
1595 self.timeout += limit
1599 def notify_retry(self):
1600 """ Retry required for command, notify user. """
1601 for count in range(3, 0, -1):
1603 raise KeyboardInterrupt
1604 msg = 'Timeout. Will retry in {0} second{1} ...'.format(
1605 count, 's' if count != 1 else '')
1606 self.callback([msg])
1608 self.callback(['Retrying ...'])
1610 def try_command(self):
1611 """ Execute a cmd & poll for callback. Returns list of output.
1612 Raises CmdFailed -> return code for Popen isn't 0
1613 Raises CmdTimedOut -> command exceeded timeout without new output
1618 tfile = tempfile.NamedTemporaryFile(mode='w+b')
1619 preexec_fn = not G_IS_WIN and os.setsid or None
1620 self.proc = subprocess.Popen(self.cmd, stdout=tfile,
1621 stderr=subprocess.STDOUT,
1622 stdin=subprocess.PIPE, shell=True,
1623 preexec_fn=preexec_fn)
1624 thrd = thr.Thread(target=(lambda proc: proc.wait()), args=(self.proc,))
1627 thread_not_started = True
1628 while thread_not_started:
1631 thread_not_started = False
1632 except RuntimeError:
1637 raise KeyboardInterrupt
1639 if first_line or random.random() < G_LOG_PROB:
1641 line = '' if G_IS_WIN else nonblock_read(tfile.name)
1643 self.callback([line])
1645 time_diff = time.time() - os.path.getmtime(tfile.name)
1646 if time_diff > self.timeout:
1647 raise CmdTimedOut(['Timeout!'])
1652 result = [line.decode('utf-8', 'replace').rstrip() for line in tfile]
1654 if self.proc.returncode != 0:
1655 raise CmdFailed([''] + result)
1662 def terminate(self):
1663 """ Terminate process and cleanup. """
1666 os.kill(self.proc.pid, signal.SIGINT)
1668 os.killpg(self.proc.pid, signal.SIGTERM)
1671 class Plugin(object):
1672 def __init__(self, name, args, buf_q, lock):
1677 self.tag = args.get('tag', 0)
1681 if os.path.exists(self.args['dir']):
1686 thread_vim_command("let s:update.new['{0}'] = 1".format(self.name))
1687 except PlugError as exc:
1688 self.write(Action.ERROR, self.name, exc.msg)
1689 except KeyboardInterrupt:
1691 self.write(Action.ERROR, self.name, ['Interrupted!'])
1693 # Any exception except those above print stack trace
1694 msg = 'Trace:\n{0}'.format(traceback.format_exc().rstrip())
1695 self.write(Action.ERROR, self.name, msg.split('\n'))
1699 target = self.args['dir']
1700 if target[-1] == '\\':
1701 target = target[0:-1]
1706 shutil.rmtree(target)
1711 self.write(Action.INSTALL, self.name, ['Installing ...'])
1712 callback = functools.partial(self.write, Action.INSTALL, self.name)
1713 cmd = 'git clone {0} {1} {2} {3} 2>&1'.format(
1714 '' if self.tag else G_CLONE_OPT, G_PROGRESS, self.args['uri'],
1716 com = Command(cmd, None, G_TIMEOUT, callback, clean(target))
1717 result = com.execute(G_RETRIES)
1718 self.write(Action.DONE, self.name, result[-1:])
1721 cmd = 'git rev-parse --abbrev-ref HEAD 2>&1 && git config -f .git/config remote.origin.url'
1722 command = Command(cmd, self.args['dir'], G_TIMEOUT,)
1723 result = command.execute(G_RETRIES)
1727 actual_uri = self.repo_uri()
1728 expect_uri = self.args['uri']
1729 regex = re.compile(r'^(?:\w+://)?(?:[^@/]*@)?([^:/]*(?::[0-9]*)?)[:/](.*?)(?:\.git)?/?$')
1730 ma = regex.match(actual_uri)
1731 mb = regex.match(expect_uri)
1732 if ma is None or mb is None or ma.groups() != mb.groups():
1734 'Invalid URI: {0}'.format(actual_uri),
1735 'Expected {0}'.format(expect_uri),
1736 'PlugClean required.']
1737 raise InvalidURI(msg)
1740 self.write(Action.UPDATE, self.name, ['Updating ...'])
1741 callback = functools.partial(self.write, Action.UPDATE, self.name)
1742 fetch_opt = '--depth 99999999' if self.tag and os.path.isfile(os.path.join(self.args['dir'], '.git/shallow')) else ''
1743 cmd = 'git fetch {0} {1} 2>&1'.format(fetch_opt, G_PROGRESS)
1744 com = Command(cmd, self.args['dir'], G_TIMEOUT, callback)
1745 result = com.execute(G_RETRIES)
1746 self.write(Action.DONE, self.name, result[-1:])
1748 self.write(Action.DONE, self.name, ['Already installed'])
1750 def write(self, action, name, msg):
1751 self.buf_q.put((action, name, msg))
1753 class PlugThread(thr.Thread):
1754 def __init__(self, tname, args):
1755 super(PlugThread, self).__init__()
1760 thr.current_thread().name = self.tname
1761 buf_q, work_q, lock = self.args
1764 while not G_STOP.is_set():
1765 name, args = work_q.get_nowait()
1766 plug = Plugin(name, args, buf_q, lock)
1772 class RefreshThread(thr.Thread):
1773 def __init__(self, lock):
1774 super(RefreshThread, self).__init__()
1781 thread_vim_command('noautocmd normal! a')
1785 self.running = False
1788 def thread_vim_command(cmd):
1789 vim.session.threadsafe_call(lambda: vim.command(cmd))
1791 def thread_vim_command(cmd):
1795 return '"' + name.replace('"', '\"') + '"'
1797 def nonblock_read(fname):
1798 """ Read a file with nonblock flag. Return the last line. """
1799 fread = os.open(fname, os.O_RDONLY | os.O_NONBLOCK)
1800 buf = os.read(fread, 100000).decode('utf-8', 'replace')
1803 line = buf.rstrip('\r\n')
1804 left = max(line.rfind('\r'), line.rfind('\n'))
1812 thr.current_thread().name = 'main'
1813 nthreads = int(vim.eval('s:update.threads'))
1814 plugs = vim.eval('s:update.todo')
1815 mac_gui = vim.eval('s:mac_gui') == '1'
1818 buf = Buffer(lock, len(plugs), G_PULL)
1819 buf_q, work_q = queue.Queue(), queue.Queue()
1820 for work in plugs.items():
1823 start_cnt = thr.active_count()
1824 for num in range(nthreads):
1825 tname = 'PlugT-{0:02}'.format(num)
1826 thread = PlugThread(tname, (buf_q, work_q, lock))
1829 rthread = RefreshThread(lock)
1832 while not buf_q.empty() or thr.active_count() != start_cnt:
1834 action, name, msg = buf_q.get(True, 0.25)
1835 buf.write(action, name, ['OK'] if not msg else msg)
1839 except KeyboardInterrupt:
1850 function! s:update_ruby()
1853 SEP = ["\r", "\n", nil]
1857 char = readchar rescue return
1858 if SEP.include? char.chr
1867 end unless defined?(PlugStream)
1870 %["#{arg.gsub('"', '\"')}"]
1875 if /mswin|mingw|bccwin/ =~ RUBY_PLATFORM
1876 pids.each { |pid| Process.kill 'INT', pid.to_i rescue nil }
1878 unless `which pgrep 2> /dev/null`.empty?
1880 until children.empty?
1881 children = children.map { |pid|
1882 `pgrep -P #{pid}`.lines.map { |l| l.chomp }
1887 pids.each { |pid| Process.kill 'TERM', pid.to_i rescue nil }
1891 def compare_git_uri a, b
1892 regex = %r{^(?:\w+://)?(?:[^@/]*@)?([^:/]*(?::[0-9]*)?)[:/](.*?)(?:\.git)?/?$}
1893 regex.match(a).to_a.drop(1) == regex.match(b).to_a.drop(1)
1900 iswin = VIM::evaluate('s:is_win').to_i == 1
1901 pull = VIM::evaluate('s:update.pull').to_i == 1
1902 base = VIM::evaluate('g:plug_home')
1903 all = VIM::evaluate('s:update.todo')
1904 limit = VIM::evaluate('get(g:, "plug_timeout", 60)')
1905 tries = VIM::evaluate('get(g:, "plug_retries", 2)') + 1
1906 nthr = VIM::evaluate('s:update.threads').to_i
1907 maxy = VIM::evaluate('winheight(".")').to_i
1908 vim7 = VIM::evaluate('v:version').to_i <= 703 && RUBY_PLATFORM =~ /darwin/
1909 cd = iswin ? 'cd /d' : 'cd'
1910 tot = VIM::evaluate('len(s:update.todo)') || 0
1912 skip = 'Already installed'
1914 take1 = proc { mtx.synchronize { running && all.shift } }
1917 $curbuf[1] = "#{pull ? 'Updating' : 'Installing'} plugins (#{cnt}/#{tot})"
1918 $curbuf[2] = '[' + bar.ljust(tot) + ']'
1919 VIM::command('normal! 2G')
1920 VIM::command('redraw')
1922 where = proc { |name| (1..($curbuf.length)).find { |l| $curbuf[l] =~ /^[-+x*] #{name}:/ } }
1923 log = proc { |name, result, type|
1925 ing = ![true, false].include?(type)
1926 bar += type ? '=' : 'x' unless ing
1928 when :install then '+' when :update then '*'
1929 when true, nil then '-' else
1930 VIM::command("call add(s:update.errors, '#{name}')")
1934 if type || type.nil?
1935 ["#{b} #{name}: #{result.lines.to_a.last || 'OK'}"]
1936 elsif result =~ /^Interrupted|^Timeout/
1937 ["#{b} #{name}: #{result}"]
1939 ["#{b} #{name}"] + result.lines.map { |l| " " << l }
1941 if lnum = where.call(name)
1943 lnum = 4 if ing && lnum > maxy
1945 result.each_with_index do |line, offset|
1946 $curbuf.append((lnum || 4) - 1 + offset, line.gsub(/\e\[./, '').chomp)
1951 bt = proc { |cmd, name, type, cleanup|
1959 Timeout::timeout(timeout) do
1960 tmp = VIM::evaluate('tempname()')
1961 system("(#{cmd}) > #{tmp}")
1962 data = File.read(tmp).chomp
1963 File.unlink tmp rescue nil
1966 fd = IO.popen(cmd).extend(PlugStream)
1968 log_prob = 1.0 / nthr
1969 while line = Timeout::timeout(timeout) { fd.get_line }
1971 log.call name, line.chomp, type if name && (first_line || rand < log_prob)
1976 [$? == 0, data.chomp]
1977 rescue Timeout::Error, Interrupt => e
1978 if fd && !fd.closed?
1982 cleanup.call if cleanup
1983 if e.is_a?(Timeout::Error) && tried < tries
1984 3.downto(1) do |countdown|
1985 s = countdown > 1 ? 's' : ''
1986 log.call name, "Timeout. Will retry in #{countdown} second#{s} ...", type
1989 log.call name, 'Retrying ...', type
1992 [false, e.is_a?(Interrupt) ? "Interrupted!" : "Timeout!"]
1995 main = Thread.current
1997 watcher = Thread.new {
1999 while VIM::evaluate('getchar(1)')
2003 require 'io/console' # >= Ruby 1.9
2004 nil until IO.console.getch == 3.chr
2008 threads.each { |t| t.raise Interrupt } unless vim7
2010 threads.each { |t| t.join rescue nil }
2013 refresh = Thread.new {
2016 break unless running
2017 VIM::command('noautocmd normal! a')
2021 } if VIM::evaluate('s:mac_gui') == 1
2023 clone_opt = VIM::evaluate('s:clone_opt').join(' ')
2024 progress = VIM::evaluate('s:progress_opt(1)')
2027 threads << Thread.new {
2028 while pair = take1.call
2030 dir, uri, tag = pair.last.values_at *%w[dir uri tag]
2031 exists = File.directory? dir
2034 chdir = "#{cd} #{iswin ? dir : esc(dir)}"
2035 ret, data = bt.call "#{chdir} && git rev-parse --abbrev-ref HEAD 2>&1 && git config -f .git/config remote.origin.url", nil, nil, nil
2036 current_uri = data.lines.to_a.last
2038 if data =~ /^Interrupted|^Timeout/
2041 [false, [data.chomp, "PlugClean required."].join($/)]
2043 elsif !compare_git_uri(current_uri, uri)
2044 [false, ["Invalid URI: #{current_uri}",
2046 "PlugClean required."].join($/)]
2049 log.call name, 'Updating ...', :update
2050 fetch_opt = (tag && File.exist?(File.join(dir, '.git/shallow'))) ? '--depth 99999999' : ''
2051 bt.call "#{chdir} && git fetch #{fetch_opt} #{progress} 2>&1", name, :update, nil
2057 d = esc dir.sub(%r{[\\/]+$}, '')
2058 log.call name, 'Installing ...', :install
2059 bt.call "git clone #{clone_opt unless tag} #{progress} #{uri} #{d} 2>&1", name, :install, proc {
2063 mtx.synchronize { VIM::command("let s:update.new['#{name}'] = 1") } if !exists && ok
2064 log.call name, result, ok
2069 threads.each { |t| t.join rescue nil }
2071 refresh.kill if refresh
2076 function! s:shellesc_cmd(arg, script)
2077 let escaped = substitute('"'.a:arg.'"', '[&|<>()@^!"]', '^&', 'g')
2078 return substitute(escaped, '%', (a:script ? '%' : '^') . '&', 'g')
2081 function! s:shellesc_ps1(arg)
2082 return "'".substitute(escape(a:arg, '\"'), "'", "''", 'g')."'"
2085 function! s:shellesc_sh(arg)
2086 return "'".substitute(a:arg, "'", "'\\\\''", 'g')."'"
2089 " Escape the shell argument based on the shell.
2090 " Vim and Neovim's shellescape() are insufficient.
2091 " 1. shellslash determines whether to use single/double quotes.
2092 " Double-quote escaping is fragile for cmd.exe.
2093 " 2. It does not work for powershell.
2094 " 3. It does not work for *sh shells if the command is executed
2095 " via cmd.exe (ie. cmd.exe /c sh -c command command_args)
2096 " 4. It does not support batchfile syntax.
2098 " Accepts an optional dictionary with the following keys:
2099 " - shell: same as Vim/Neovim 'shell' option.
2100 " If unset, fallback to 'cmd.exe' on Windows or 'sh'.
2101 " - script: If truthy and shell is cmd.exe, escape for batchfile syntax.
2102 function! plug#shellescape(arg, ...)
2103 if a:arg =~# '^[A-Za-z0-9_/:.-]\+$'
2106 let opts = a:0 > 0 && type(a:1) == s:TYPE.dict ? a:1 : {}
2107 let shell = get(opts, 'shell', s:is_win ? 'cmd.exe' : 'sh')
2108 let script = get(opts, 'script', 1)
2109 if shell =~# 'cmd\.exe'
2110 return s:shellesc_cmd(a:arg, script)
2111 elseif shell =~# 'powershell\.exe' || shell =~# 'pwsh$'
2112 return s:shellesc_ps1(a:arg)
2114 return s:shellesc_sh(a:arg)
2117 function! s:glob_dir(path)
2118 return map(filter(s:glob(a:path, '**'), 'isdirectory(v:val)'), 's:dirpath(v:val)')
2121 function! s:progress_bar(line, bar, total)
2122 call setline(a:line, '[' . s:lpad(a:bar, a:total) . ']')
2125 function! s:compare_git_uri(a, b)
2126 " See `git help clone'
2127 " https:// [user@] github.com[:port] / junegunn/vim-plug [.git]
2128 " [git@] github.com[:port] : junegunn/vim-plug [.git]
2129 " file:// / junegunn/vim-plug [/]
2130 " / junegunn/vim-plug [/]
2131 let pat = '^\%(\w\+://\)\='.'\%([^@/]*@\)\='.'\([^:/]*\%(:[0-9]*\)\=\)'.'[:/]'.'\(.\{-}\)'.'\%(\.git\)\=/\?$'
2132 let ma = matchlist(a:a, pat)
2133 let mb = matchlist(a:b, pat)
2134 return ma[1:2] ==# mb[1:2]
2137 function! s:format_message(bullet, name, message)
2139 return [printf('%s %s: %s', a:bullet, a:name, s:lastline(a:message))]
2141 let lines = map(s:lines(a:message), '" ".v:val')
2142 return extend([printf('x %s:', a:name)], lines)
2146 function! s:with_cd(cmd, dir, ...)
2147 let script = a:0 > 0 ? a:1 : 1
2148 return printf('cd%s %s && %s', s:is_win ? ' /d' : '', plug#shellescape(a:dir, {'script': script}), a:cmd)
2151 function! s:system(cmd, ...)
2154 let [sh, shellcmdflag, shrd] = s:chsh(1)
2155 if type(a:cmd) == s:TYPE.list
2156 " Neovim's system() supports list argument to bypass the shell
2157 " but it cannot set the working directory for the command.
2158 " Assume that the command does not rely on the shell.
2159 if has('nvim') && a:0 == 0
2160 return system(a:cmd)
2162 let cmd = join(map(copy(a:cmd), 'plug#shellescape(v:val, {"shell": &shell, "script": 0})'))
2163 if &shell =~# 'powershell\.exe'
2164 let cmd = '& ' . cmd
2170 let cmd = s:with_cd(cmd, a:1, type(a:cmd) != s:TYPE.list)
2172 if s:is_win && type(a:cmd) != s:TYPE.list
2173 let [batchfile, cmd] = s:batchfile(cmd)
2177 let [&shell, &shellcmdflag, &shellredir] = [sh, shellcmdflag, shrd]
2178 if s:is_win && filereadable(batchfile)
2179 call delete(batchfile)
2184 function! s:system_chomp(...)
2185 let ret = call('s:system', a:000)
2186 return v:shell_error ? '' : substitute(ret, '\n$', '', '')
2189 function! s:git_validate(spec, check_branch)
2191 if isdirectory(a:spec.dir)
2192 let result = s:lines(s:system('git rev-parse --abbrev-ref HEAD 2>&1 && git config -f .git/config remote.origin.url', a:spec.dir))
2193 let remote = result[-1]
2195 let err = join([remote, 'PlugClean required.'], "\n")
2196 elseif !s:compare_git_uri(remote, a:spec.uri)
2197 let err = join(['Invalid URI: '.remote,
2198 \ 'Expected: '.a:spec.uri,
2199 \ 'PlugClean required.'], "\n")
2200 elseif a:check_branch && has_key(a:spec, 'commit')
2201 let result = s:lines(s:system('git rev-parse HEAD 2>&1', a:spec.dir))
2202 let sha = result[-1]
2204 let err = join(add(result, 'PlugClean required.'), "\n")
2205 elseif !s:hash_match(sha, a:spec.commit)
2206 let err = join([printf('Invalid HEAD (expected: %s, actual: %s)',
2207 \ a:spec.commit[:6], sha[:6]),
2208 \ 'PlugUpdate required.'], "\n")
2210 elseif a:check_branch
2211 let branch = result[0]
2213 if has_key(a:spec, 'tag')
2214 let tag = s:system_chomp('git describe --exact-match --tags HEAD 2>&1', a:spec.dir)
2215 if a:spec.tag !=# tag && a:spec.tag !~ '\*'
2216 let err = printf('Invalid tag: %s (expected: %s). Try PlugUpdate.',
2217 \ (empty(tag) ? 'N/A' : tag), a:spec.tag)
2220 elseif a:spec.branch !=# branch
2221 let err = printf('Invalid branch: %s (expected: %s). Try PlugUpdate.',
2222 \ branch, a:spec.branch)
2225 let [ahead, behind] = split(s:lastline(s:system([
2226 \ 'git', 'rev-list', '--count', '--left-right',
2227 \ printf('HEAD...origin/%s', a:spec.branch)
2228 \ ], a:spec.dir)), '\t')
2229 if !v:shell_error && ahead
2231 " Only mention PlugClean if diverged, otherwise it's likely to be
2232 " pushable (and probably not that messed up).
2234 \ "Diverged from origin/%s (%d commit(s) ahead and %d commit(s) behind!\n"
2235 \ .'Backup local changes and run PlugClean and PlugUpdate to reinstall it.', a:spec.branch, ahead, behind)
2237 let err = printf("Ahead of origin/%s by %d commit(s).\n"
2238 \ .'Cannot update until local changes are pushed.',
2239 \ a:spec.branch, ahead)
2245 let err = 'Not found'
2247 return [err, err =~# 'PlugClean']
2250 function! s:rm_rf(dir)
2251 if isdirectory(a:dir)
2252 call s:system(s:is_win
2253 \ ? 'rmdir /S /Q '.plug#shellescape(a:dir)
2254 \ : ['rm', '-rf', a:dir])
2258 function! s:clean(force)
2260 call append(0, 'Searching for invalid plugins in '.g:plug_home)
2263 " List of valid directories
2266 let [cnt, total] = [0, len(g:plugs)]
2267 for [name, spec] in items(g:plugs)
2268 if !s:is_managed(name)
2269 call add(dirs, spec.dir)
2271 let [err, clean] = s:git_validate(spec, 1)
2273 let errs[spec.dir] = s:lines(err)[0]
2275 call add(dirs, spec.dir)
2279 call s:progress_bar(2, repeat('=', cnt), total)
2286 let allowed[s:dirpath(s:plug_fnamemodify(dir, ':h:h'))] = 1
2287 let allowed[dir] = 1
2288 for child in s:glob_dir(dir)
2289 let allowed[child] = 1
2294 let found = sort(s:glob_dir(g:plug_home))
2296 let f = remove(found, 0)
2297 if !has_key(allowed, f) && isdirectory(f)
2299 call append(line('$'), '- ' . f)
2301 call append(line('$'), ' ' . errs[f])
2303 let found = filter(found, 'stridx(v:val, f) != 0')
2310 call append(line('$'), 'Already clean.')
2312 let s:clean_count = 0
2313 call append(3, ['Directories to delete:', ''])
2315 if a:force || s:ask_no_interrupt('Delete all directories?')
2316 call s:delete([6, line('$')], 1)
2318 call setline(4, 'Cancelled.')
2319 nnoremap <silent> <buffer> d :set opfunc=<sid>delete_op<cr>g@
2320 nmap <silent> <buffer> dd d_
2321 xnoremap <silent> <buffer> d :<c-u>call <sid>delete_op(visualmode(), 1)<cr>
2322 echo 'Delete the lines (d{motion}) to delete the corresponding directories'
2326 setlocal nomodifiable
2329 function! s:delete_op(type, ...)
2330 call s:delete(a:0 ? [line("'<"), line("'>")] : [line("'["), line("']")], 0)
2333 function! s:delete(range, force)
2334 let [l1, l2] = a:range
2337 let line = getline(l1)
2338 if line =~ '^- ' && isdirectory(line[2:])
2341 let answer = force ? 1 : s:ask('Delete '.line[2:].'?', 1)
2342 let force = force || answer > 1
2344 call s:rm_rf(line[2:])
2346 call setline(l1, '~'.line[1:])
2347 let s:clean_count += 1
2348 call setline(4, printf('Removed %d directories.', s:clean_count))
2349 setlocal nomodifiable
2356 function! s:upgrade()
2357 echo 'Downloading the latest version of vim-plug'
2359 let tmp = s:plug_tempname()
2360 let new = tmp . '/plug.vim'
2363 let out = s:system(['git', 'clone', '--depth', '1', s:plug_src, tmp])
2365 return s:err('Error upgrading vim-plug: '. out)
2368 if readfile(s:me) ==# readfile(new)
2369 echo 'vim-plug is already up-to-date'
2372 call rename(s:me, s:me . '.old')
2373 call rename(new, s:me)
2375 echo 'vim-plug has been upgraded'
2379 silent! call s:rm_rf(tmp)
2383 function! s:upgrade_specs()
2384 for spec in values(g:plugs)
2385 let spec.frozen = get(spec, 'frozen', 0)
2389 function! s:status()
2391 call append(0, 'Checking plugins')
2396 let [cnt, total] = [0, len(g:plugs)]
2397 for [name, spec] in items(g:plugs)
2398 let is_dir = isdirectory(spec.dir)
2399 if has_key(spec, 'uri')
2401 let [err, _] = s:git_validate(spec, 1)
2402 let [valid, msg] = [empty(err), empty(err) ? 'OK' : err]
2404 let [valid, msg] = [0, 'Not found. Try PlugInstall.']
2408 let [valid, msg] = [1, 'OK']
2410 let [valid, msg] = [0, 'Not found.']
2415 " `s:loaded` entry can be missing if PlugUpgraded
2416 if is_dir && get(s:loaded, name, -1) == 0
2418 let msg .= ' (not loaded)'
2420 call s:progress_bar(2, repeat('=', cnt), total)
2421 call append(3, s:format_message(valid ? '-' : 'x', name, msg))
2425 call setline(1, 'Finished. '.ecnt.' error(s).')
2427 setlocal nomodifiable
2429 echo "Press 'L' on each line to load plugin, or 'U' to update"
2430 nnoremap <silent> <buffer> L :call <SID>status_load(line('.'))<cr>
2431 xnoremap <silent> <buffer> L :call <SID>status_load(line('.'))<cr>
2435 function! s:extract_name(str, prefix, suffix)
2436 return matchstr(a:str, '^'.a:prefix.' \zs[^:]\+\ze:.*'.a:suffix.'$')
2439 function! s:status_load(lnum)
2440 let line = getline(a:lnum)
2441 let name = s:extract_name(line, '-', '(not loaded)')
2443 call plug#load(name)
2445 call setline(a:lnum, substitute(line, ' (not loaded)$', '', ''))
2446 setlocal nomodifiable
2450 function! s:status_update() range
2451 let lines = getline(a:firstline, a:lastline)
2452 let names = filter(map(lines, 's:extract_name(v:val, "[x-]", "")'), '!empty(v:val)')
2455 execute 'PlugUpdate' join(names)
2459 function! s:is_preview_window_open()
2467 function! s:find_name(lnum)
2468 for lnum in reverse(range(1, a:lnum))
2469 let line = getline(lnum)
2473 let name = s:extract_name(line, '-', '')
2481 function! s:preview_commit()
2482 if b:plug_preview < 0
2483 let b:plug_preview = !s:is_preview_window_open()
2486 let sha = matchstr(getline('.'), '^ \X*\zs[0-9a-f]\{7,9}')
2491 let name = s:find_name(line('.'))
2492 if empty(name) || !has_key(g:plugs, name) || !isdirectory(g:plugs[name].dir)
2496 if exists('g:plug_pwindow') && !s:is_preview_window_open()
2497 execute g:plug_pwindow
2503 setlocal previewwindow filetype=git buftype=nofile nobuflisted modifiable
2506 let [sh, shellcmdflag, shrd] = s:chsh(1)
2507 let cmd = 'cd '.plug#shellescape(g:plugs[name].dir).' && git show --no-color --pretty=medium '.sha
2509 let [batchfile, cmd] = s:batchfile(cmd)
2511 execute 'silent %!' cmd
2513 let [&shell, &shellcmdflag, &shellredir] = [sh, shellcmdflag, shrd]
2514 if s:is_win && filereadable(batchfile)
2515 call delete(batchfile)
2518 setlocal nomodifiable
2519 nnoremap <silent> <buffer> q :q<cr>
2523 function! s:section(flags)
2524 call search('\(^[x-] \)\@<=[^:]\+:', a:flags)
2527 function! s:format_git_log(line)
2529 let tokens = split(a:line, nr2char(1))
2531 return indent.substitute(a:line, '\s*$', '', '')
2533 let [graph, sha, refs, subject, date] = tokens
2534 let tag = matchstr(refs, 'tag: [^,)]\+')
2535 let tag = empty(tag) ? ' ' : ' ('.tag.') '
2536 return printf('%s%s%s%s%s (%s)', indent, graph, sha, tag, subject, date)
2539 function! s:append_ul(lnum, text)
2540 call append(a:lnum, ['', a:text, repeat('-', len(a:text))])
2545 call append(0, ['Collecting changes ...', ''])
2548 let total = filter(copy(g:plugs), 's:is_managed(v:key) && isdirectory(v:val.dir)')
2549 call s:progress_bar(2, bar, len(total))
2550 for origin in [1, 0]
2551 let plugs = reverse(sort(items(filter(copy(total), (origin ? '' : '!').'(has_key(v:val, "commit") || has_key(v:val, "tag"))'))))
2555 call s:append_ul(2, origin ? 'Pending updates:' : 'Last update:')
2557 let range = origin ? '..origin/'.v.branch : 'HEAD@{1}..'
2558 let cmd = ['git', 'log', '--graph', '--color=never']
2559 if s:git_version_requirement(2, 10, 0)
2560 call add(cmd, '--no-show-signature')
2562 call extend(cmd, ['--pretty=format:%x01%h%x01%d%x01%s%x01%cr', range])
2563 if has_key(v, 'rtp')
2564 call extend(cmd, ['--', v.rtp])
2566 let diff = s:system_chomp(cmd, v.dir)
2568 let ref = has_key(v, 'tag') ? (' (tag: '.v.tag.')') : has_key(v, 'commit') ? (' '.v.commit) : ''
2569 call append(5, extend(['', '- '.k.':'.ref], map(s:lines(diff), 's:format_git_log(v:val)')))
2570 let cnts[origin] += 1
2573 call s:progress_bar(2, bar, len(total))
2578 call append(5, ['', 'N/A'])
2581 call setline(1, printf('%d plugin(s) updated.', cnts[0])
2582 \ . (cnts[1] ? printf(' %d plugin(s) have pending updates.', cnts[1]) : ''))
2584 if cnts[0] || cnts[1]
2585 nnoremap <silent> <buffer> <plug>(plug-preview) :silent! call <SID>preview_commit()<cr>
2586 if empty(maparg("\<cr>", 'n'))
2587 nmap <buffer> <cr> <plug>(plug-preview)
2589 if empty(maparg('o', 'n'))
2590 nmap <buffer> o <plug>(plug-preview)
2594 nnoremap <silent> <buffer> X :call <SID>revert()<cr>
2595 echo "Press 'X' on each block to revert the update"
2598 setlocal nomodifiable
2601 function! s:revert()
2602 if search('^Pending updates', 'bnW')
2606 let name = s:find_name(line('.'))
2607 if empty(name) || !has_key(g:plugs, name) ||
2608 \ input(printf('Revert the update of %s? (y/N) ', name)) !~? '^y'
2612 call s:system('git reset --hard HEAD@{1} && git checkout '.plug#shellescape(g:plugs[name].branch).' --', g:plugs[name].dir)
2615 setlocal nomodifiable
2619 function! s:snapshot(force, ...) abort
2622 call append(0, ['" Generated by vim-plug',
2623 \ '" '.strftime("%c"),
2624 \ '" :source this file in vim to restore the snapshot',
2625 \ '" or execute: vim -S snapshot.vim',
2626 \ '', '', 'PlugUpdate!'])
2628 let anchor = line('$') - 3
2629 let names = sort(keys(filter(copy(g:plugs),
2630 \'has_key(v:val, "uri") && !has_key(v:val, "commit") && isdirectory(v:val.dir)')))
2631 for name in reverse(names)
2632 let sha = s:system_chomp(['git', 'rev-parse', '--short', 'HEAD'], g:plugs[name].dir)
2634 call append(anchor, printf("silent! let g:plugs['%s'].commit = '%s'", name, sha))
2640 let fn = s:plug_expand(a:1)
2641 if filereadable(fn) && !(a:force || s:ask(a:1.' already exists. Overwrite?'))
2644 call writefile(getline(1, '$'), fn)
2645 echo 'Saved as '.a:1
2646 silent execute 'e' s:esc(fn)
2651 function! s:split_rtp()
2652 return split(&rtp, '\\\@<!,')
2655 let s:first_rtp = s:escrtp(get(s:split_rtp(), 0, ''))
2656 let s:last_rtp = s:escrtp(get(s:split_rtp(), -1, ''))
2658 if exists('g:plugs')
2659 let g:plugs_order = get(g:, 'plugs_order', keys(g:plugs))
2660 call s:upgrade_specs()
2661 call s:define_commands()
2664 let &cpo = s:cpo_save