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(repo . ' ' . v:exception)
639 function! s:parse_options(arg)
640 let opts = copy(s:base_spec)
641 let type = type(a:arg)
642 let opt_errfmt = 'Invalid argument for "%s" option of :Plug (expected: %s)'
643 if type == s:TYPE.string
645 throw printf(opt_errfmt, 'tag', 'string')
648 elseif type == s:TYPE.dict
649 call extend(opts, a:arg)
650 for opt in ['branch', 'tag', 'commit', 'rtp', 'dir', 'as']
651 if has_key(opts, opt)
652 \ && (type(opts[opt]) != s:TYPE.string || empty(opts[opt]))
653 throw printf(opt_errfmt, opt, 'string')
656 for opt in ['on', 'for']
657 if has_key(opts, opt)
658 \ && type(opts[opt]) != s:TYPE.list
659 \ && (type(opts[opt]) != s:TYPE.string || empty(opts[opt]))
660 throw printf(opt_errfmt, opt, 'string or list')
663 if has_key(opts, 'do')
664 \ && type(opts.do) != s:TYPE.funcref
665 \ && (type(opts.do) != s:TYPE.string || empty(opts.do))
666 throw printf(opt_errfmt, 'do', 'string or funcref')
668 if has_key(opts, 'dir')
669 let opts.dir = s:dirpath(s:plug_expand(opts.dir))
672 throw 'Invalid argument type (expected: string or dictionary)'
677 function! s:infer_properties(name, repo)
679 if s:is_local_plug(repo)
680 return { 'dir': s:dirpath(s:plug_expand(repo)) }
686 throw printf('Invalid argument: %s (implicit `vim-scripts'' expansion is deprecated)', repo)
688 let fmt = get(g:, 'plug_url_format', 'https://git::@github.com/%s.git')
689 let uri = printf(fmt, repo)
691 return { 'dir': s:dirpath(g:plug_home.'/'.a:name), 'uri': uri }
695 function! s:install(force, names)
696 call s:update_impl(0, a:force, a:names)
699 function! s:update(force, names)
700 call s:update_impl(1, a:force, a:names)
703 function! plug#helptags()
704 if !exists('g:plugs')
705 return s:err('plug#begin was not called')
707 for spec in values(g:plugs)
708 let docd = join([s:rtp(spec), 'doc'], '/')
710 silent! execute 'helptags' s:esc(docd)
718 syntax region plug1 start=/\%1l/ end=/\%2l/ contains=plugNumber
719 syntax region plug2 start=/\%2l/ end=/\%3l/ contains=plugBracket,plugX
720 syn match plugNumber /[0-9]\+[0-9.]*/ contained
721 syn match plugBracket /[[\]]/ contained
722 syn match plugX /x/ contained
723 syn match plugDash /^-/
724 syn match plugPlus /^+/
725 syn match plugStar /^*/
726 syn match plugMessage /\(^- \)\@<=.*/
727 syn match plugName /\(^- \)\@<=[^ ]*:/
728 syn match plugSha /\%(: \)\@<=[0-9a-f]\{4,}$/
729 syn match plugTag /(tag: [^)]\+)/
730 syn match plugInstall /\(^+ \)\@<=[^:]*/
731 syn match plugUpdate /\(^* \)\@<=[^:]*/
732 syn match plugCommit /^ \X*[0-9a-f]\{7,9} .*/ contains=plugRelDate,plugEdge,plugTag
733 syn match plugEdge /^ \X\+$/
734 syn match plugEdge /^ \X*/ contained nextgroup=plugSha
735 syn match plugSha /[0-9a-f]\{7,9}/ contained
736 syn match plugRelDate /([^)]*)$/ contained
737 syn match plugNotLoaded /(not loaded)$/
738 syn match plugError /^x.*/
739 syn region plugDeleted start=/^\~ .*/ end=/^\ze\S/
740 syn match plugH2 /^.*:\n-\+$/
741 syn keyword Function PlugInstall PlugStatus PlugUpdate PlugClean
742 hi def link plug1 Title
743 hi def link plug2 Repeat
744 hi def link plugH2 Type
745 hi def link plugX Exception
746 hi def link plugBracket Structure
747 hi def link plugNumber Number
749 hi def link plugDash Special
750 hi def link plugPlus Constant
751 hi def link plugStar Boolean
753 hi def link plugMessage Function
754 hi def link plugName Label
755 hi def link plugInstall Function
756 hi def link plugUpdate Type
758 hi def link plugError Error
759 hi def link plugDeleted Ignore
760 hi def link plugRelDate Comment
761 hi def link plugEdge PreProc
762 hi def link plugSha Identifier
763 hi def link plugTag Constant
765 hi def link plugNotLoaded Comment
768 function! s:lpad(str, len)
769 return a:str . repeat(' ', a:len - len(a:str))
772 function! s:lines(msg)
773 return split(a:msg, "[\r\n]")
776 function! s:lastline(msg)
777 return get(s:lines(a:msg), -1, '')
780 function! s:new_window()
781 execute get(g:, 'plug_window', 'vertical topleft new')
784 function! s:plug_window_exists()
785 let buflist = tabpagebuflist(s:plug_tab)
786 return !empty(buflist) && index(buflist, s:plug_buf) >= 0
789 function! s:switch_in()
790 if !s:plug_window_exists()
794 if winbufnr(0) != s:plug_buf
795 let s:pos = [tabpagenr(), winnr(), winsaveview()]
796 execute 'normal!' s:plug_tab.'gt'
797 let winnr = bufwinnr(s:plug_buf)
798 execute winnr.'wincmd w'
799 call add(s:pos, winsaveview())
801 let s:pos = [winsaveview()]
808 function! s:switch_out(...)
809 call winrestview(s:pos[-1])
810 setlocal nomodifiable
816 execute 'normal!' s:pos[0].'gt'
817 execute s:pos[1] 'wincmd w'
818 call winrestview(s:pos[2])
822 function! s:finish_bindings()
823 nnoremap <silent> <buffer> R :call <SID>retry()<cr>
824 nnoremap <silent> <buffer> D :PlugDiff<cr>
825 nnoremap <silent> <buffer> S :PlugStatus<cr>
826 nnoremap <silent> <buffer> U :call <SID>status_update()<cr>
827 xnoremap <silent> <buffer> U :call <SID>status_update()<cr>
828 nnoremap <silent> <buffer> ]] :silent! call <SID>section('')<cr>
829 nnoremap <silent> <buffer> [[ :silent! call <SID>section('b')<cr>
832 function! s:prepare(...)
833 if empty(s:plug_getcwd())
834 throw 'Invalid current working directory. Cannot proceed.'
837 for evar in ['$GIT_DIR', '$GIT_WORK_TREE']
839 throw evar.' detected. Cannot proceed.'
845 if b:plug_preview == 1
853 nnoremap <silent> <buffer> q :if b:plug_preview==1<bar>pc<bar>endif<bar>bd<cr>
855 call s:finish_bindings()
857 let b:plug_preview = -1
858 let s:plug_tab = tabpagenr()
859 let s:plug_buf = winbufnr(0)
862 for k in ['<cr>', 'L', 'o', 'X', 'd', 'dd']
863 execute 'silent! unmap <buffer>' k
865 setlocal buftype=nofile bufhidden=wipe nobuflisted nolist noswapfile nowrap cursorline modifiable nospell
866 if exists('+colorcolumn')
867 setlocal colorcolumn=
870 if exists('g:syntax_on')
875 function! s:assign_name()
877 let prefix = '[Plugins]'
880 while bufexists(name)
881 let name = printf('%s (%s)', prefix, idx)
884 silent! execute 'f' fnameescape(name)
887 function! s:chsh(swap)
888 let prev = [&shell, &shellcmdflag, &shellredir]
893 if &shell =~# 'powershell\.exe' || &shell =~# 'pwsh$'
894 let &shellredir = '2>&1 | Out-File -Encoding UTF8 %s'
895 elseif &shell =~# 'sh' || &shell =~# 'cmd\.exe'
896 set shellredir=>%s\ 2>&1
902 function! s:bang(cmd, ...)
905 let [sh, shellcmdflag, shrd] = s:chsh(a:0)
906 " FIXME: Escaping is incomplete. We could use shellescape with eval,
907 " but it won't work on Windows.
908 let cmd = a:0 ? s:with_cd(a:cmd, a:1) : a:cmd
910 let [batchfile, cmd] = s:batchfile(cmd)
912 let g:_plug_bang = (s:is_win && has('gui_running') ? 'silent ' : '').'!'.escape(cmd, '#!%')
913 execute "normal! :execute g:_plug_bang\<cr>\<cr>"
916 let [&shell, &shellcmdflag, &shellredir] = [sh, shellcmdflag, shrd]
917 if s:is_win && filereadable(batchfile)
918 call delete(batchfile)
921 return v:shell_error ? 'Exit status: ' . v:shell_error : ''
924 function! s:regress_bar()
925 let bar = substitute(getline(2)[1:-2], '.*\zs=', 'x', '')
926 call s:progress_bar(2, bar, len(bar))
929 function! s:is_updated(dir)
930 return !empty(s:system_chomp(['git', 'log', '--pretty=format:%h', 'HEAD...HEAD@{1}'], a:dir))
933 function! s:do(pull, force, todo)
934 for [name, spec] in items(a:todo)
935 if !isdirectory(spec.dir)
938 let installed = has_key(s:update.new, name)
939 let updated = installed ? 0 :
940 \ (a:pull && index(s:update.errors, name) < 0 && s:is_updated(spec.dir))
941 if a:force || installed || updated
942 execute 'cd' s:esc(spec.dir)
943 call append(3, '- Post-update hook for '. name .' ... ')
945 let type = type(spec.do)
946 if type == s:TYPE.string
948 if !get(s:loaded, name, 0)
949 let s:loaded[name] = 1
952 call s:load_plugin(spec)
956 let error = v:exception
958 if !s:plug_window_exists()
960 throw 'Warning: vim-plug was terminated by the post-update hook of '.name
963 let error = s:bang(spec.do)
965 elseif type == s:TYPE.funcref
967 call s:load_plugin(spec)
968 let status = installed ? 'installed' : (updated ? 'updated' : 'unchanged')
969 call spec.do({ 'name': name, 'status': status, 'force': a:force })
971 let error = v:exception
974 let error = 'Invalid hook type'
977 call setline(4, empty(error) ? (getline(4) . 'OK')
978 \ : ('x' . getline(4)[1:] . error))
980 call add(s:update.errors, name)
988 function! s:hash_match(a, b)
989 return stridx(a:a, a:b) == 0 || stridx(a:b, a:a) == 0
992 function! s:checkout(spec)
993 let sha = a:spec.commit
994 let output = s:system(['git', 'rev-parse', 'HEAD'], a:spec.dir)
995 if !v:shell_error && !s:hash_match(sha, s:lines(output)[0])
996 let output = s:system(
997 \ 'git fetch --depth 999999 && git checkout '.plug#shellescape(sha).' --', a:spec.dir)
1002 function! s:finish(pull)
1003 let new_frozen = len(filter(keys(s:update.new), 'g:plugs[v:val].frozen'))
1005 let s = new_frozen > 1 ? 's' : ''
1006 call append(3, printf('- Installed %d frozen plugin%s', new_frozen, s))
1008 call append(3, '- Finishing ... ') | 4
1010 call plug#helptags()
1012 call setline(4, getline(4) . 'Done!')
1015 if !empty(s:update.errors)
1016 call add(msgs, "Press 'R' to retry.")
1018 if a:pull && len(s:update.new) < len(filter(getline(5, '$'),
1019 \ "v:val =~ '^- ' && v:val !~# 'Already up.to.date'"))
1020 call add(msgs, "Press 'D' to see the updated changes.")
1022 echo join(msgs, ' ')
1023 call s:finish_bindings()
1027 if empty(s:update.errors)
1031 call s:update_impl(s:update.pull, s:update.force,
1032 \ extend(copy(s:update.errors), [s:update.threads]))
1035 function! s:is_managed(name)
1036 return has_key(g:plugs[a:name], 'uri')
1039 function! s:names(...)
1040 return sort(filter(keys(g:plugs), 'stridx(v:val, a:1) == 0 && s:is_managed(v:val)'))
1043 function! s:check_ruby()
1044 silent! ruby require 'thread'; VIM::command("let g:plug_ruby = '#{RUBY_VERSION}'")
1045 if !exists('g:plug_ruby')
1047 return s:warn('echom', 'Warning: Ruby interface is broken')
1049 let ruby_version = split(g:plug_ruby, '\.')
1051 return s:version_requirement(ruby_version, [1, 8, 7])
1054 function! s:update_impl(pull, force, args) abort
1055 let sync = index(a:args, '--sync') >= 0 || has('vim_starting')
1056 let args = filter(copy(a:args), 'v:val != "--sync"')
1057 let threads = (len(args) > 0 && args[-1] =~ '^[1-9][0-9]*$') ?
1058 \ remove(args, -1) : get(g:, 'plug_threads', 16)
1060 let managed = filter(copy(g:plugs), 's:is_managed(v:key)')
1061 let todo = empty(args) ? filter(managed, '!v:val.frozen || !isdirectory(v:val.dir)') :
1062 \ filter(managed, 'index(args, v:key) >= 0')
1065 return s:warn('echo', 'No plugin to '. (a:pull ? 'update' : 'install'))
1068 if !s:is_win && s:git_version_requirement(2, 3)
1069 let s:git_terminal_prompt = exists('$GIT_TERMINAL_PROMPT') ? $GIT_TERMINAL_PROMPT : ''
1070 let $GIT_TERMINAL_PROMPT = 0
1071 for plug in values(todo)
1072 let plug.uri = substitute(plug.uri,
1073 \ '^https://git::@github\.com', 'https://github.com', '')
1077 if !isdirectory(g:plug_home)
1079 call mkdir(g:plug_home, 'p')
1081 return s:err(printf('Invalid plug directory: %s. '.
1082 \ 'Try to call plug#begin with a valid directory', g:plug_home))
1086 if has('nvim') && !exists('*jobwait') && threads > 1
1087 call s:warn('echom', '[vim-plug] Update Neovim for parallel installer')
1090 let use_job = s:nvim || s:vim8
1091 let python = (has('python') || has('python3')) && !use_job
1092 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()
1095 \ 'start': reltime(),
1097 \ 'todo': copy(todo),
1102 \ 'threads': (python || ruby || use_job) ? min([len(todo), threads]) : 1,
1108 call append(0, ['', ''])
1112 let s:clone_opt = []
1113 if get(g:, 'plug_shallow', 1)
1114 call extend(s:clone_opt, ['--depth', '1'])
1115 if s:git_version_requirement(1, 7, 10)
1116 call add(s:clone_opt, '--no-single-branch')
1120 if has('win32unix') || has('wsl')
1121 call extend(s:clone_opt, ['-c', 'core.eol=lf', '-c', 'core.autocrlf=input'])
1124 let s:submodule_opt = s:git_version_requirement(2, 8) ? ' --jobs='.threads : ''
1126 " Python version requirement (>= 2.7)
1127 if python && !has('python3') && !ruby && !use_job && s:update.threads > 1
1129 silent python import platform; print platform.python_version()
1131 let python = s:version_requirement(
1132 \ map(split(split(pyv)[0], '\.'), 'str2nr(v:val)'), [2, 6])
1135 if (python || ruby) && s:update.threads > 1
1142 call s:update_ruby()
1144 call s:update_python()
1147 let lines = getline(4, '$')
1151 let name = s:extract_name(line, '.', '')
1152 if empty(name) || !has_key(printed, name)
1153 call append('$', line)
1155 let printed[name] = 1
1156 if line[0] == 'x' && index(s:update.errors, name) < 0
1157 call add(s:update.errors, name)
1164 call s:update_finish()
1168 while use_job && sync
1177 function! s:log4(name, msg)
1178 call setline(4, printf('- %s (%s)', a:msg, a:name))
1182 function! s:update_finish()
1183 if exists('s:git_terminal_prompt')
1184 let $GIT_TERMINAL_PROMPT = s:git_terminal_prompt
1187 call append(3, '- Updating ...') | 4
1188 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))'))
1189 let [pos, _] = s:logpos(name)
1193 if has_key(spec, 'commit')
1194 call s:log4(name, 'Checking out '.spec.commit)
1195 let out = s:checkout(spec)
1196 elseif has_key(spec, 'tag')
1199 let tags = s:lines(s:system('git tag --list '.plug#shellescape(tag).' --sort -version:refname 2>&1', spec.dir))
1200 if !v:shell_error && !empty(tags)
1202 call s:log4(name, printf('Latest tag for %s -> %s', spec.tag, tag))
1206 call s:log4(name, 'Checking out '.tag)
1207 let out = s:system('git checkout -q '.plug#shellescape(tag).' -- 2>&1', spec.dir)
1209 let branch = get(spec, 'branch', 'master')
1210 call s:log4(name, 'Merging origin/'.s:esc(branch))
1211 let out = s:system('git checkout -q '.plug#shellescape(branch).' -- 2>&1'
1212 \. (has_key(s:update.new, name) ? '' : ('&& git merge --ff-only '.plug#shellescape('origin/'.branch).' 2>&1')), spec.dir)
1214 if !v:shell_error && filereadable(spec.dir.'/.gitmodules') &&
1215 \ (s:update.force || has_key(s:update.new, name) || s:is_updated(spec.dir))
1216 call s:log4(name, 'Updating submodules. This may take a while.')
1217 let out .= s:bang('git submodule update --init --recursive'.s:submodule_opt.' 2>&1', spec.dir)
1219 let msg = s:format_message(v:shell_error ? 'x': '-', name, out)
1221 call add(s:update.errors, name)
1222 call s:regress_bar()
1223 silent execute pos 'd _'
1224 call append(4, msg) | 4
1226 call setline(pos, msg[0])
1232 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")'))
1234 call s:warn('echom', v:exception)
1235 call s:warn('echo', '')
1238 call s:finish(s:update.pull)
1239 call setline(1, 'Updated. Elapsed time: ' . split(reltimestr(reltime(s:update.start)))[0] . ' sec.')
1240 call s:switch_out('normal! gg')
1244 function! s:job_abort()
1245 if (!s:nvim && !s:vim8) || !exists('s:jobs')
1249 for [name, j] in items(s:jobs)
1251 silent! call jobstop(j.jobid)
1253 silent! call job_stop(j.jobid)
1256 call s:rm_rf(g:plugs[name].dir)
1262 function! s:last_non_empty_line(lines)
1263 let len = len(a:lines)
1264 for idx in range(len)
1265 let line = a:lines[len-idx-1]
1273 function! s:job_out_cb(self, data) abort
1275 let data = remove(self.lines, -1) . a:data
1276 let lines = map(split(data, "\n", 1), 'split(v:val, "\r", 1)[-1]')
1277 call extend(self.lines, lines)
1278 " To reduce the number of buffer updates
1279 let self.tick = get(self, 'tick', -1) + 1
1280 if !self.running || self.tick % len(s:jobs) == 0
1281 let bullet = self.running ? (self.new ? '+' : '*') : (self.error ? 'x' : '-')
1282 let result = self.error ? join(self.lines, "\n") : s:last_non_empty_line(self.lines)
1283 call s:log(bullet, self.name, result)
1287 function! s:job_exit_cb(self, data) abort
1288 let a:self.running = 0
1289 let a:self.error = a:data != 0
1290 call s:reap(a:self.name)
1294 function! s:job_cb(fn, job, ch, data)
1295 if !s:plug_window_exists() " plug window closed
1296 return s:job_abort()
1298 call call(a:fn, [a:job, a:data])
1301 function! s:nvim_cb(job_id, data, event) dict abort
1302 return (a:event == 'stdout' || a:event == 'stderr') ?
1303 \ s:job_cb('s:job_out_cb', self, 0, join(a:data, "\n")) :
1304 \ s:job_cb('s:job_exit_cb', self, 0, a:data)
1307 function! s:spawn(name, cmd, opts)
1308 let job = { 'name': a:name, 'running': 1, 'error': 0, 'lines': [''],
1309 \ 'new': get(a:opts, 'new', 0) }
1310 let s:jobs[a:name] = job
1313 if has_key(a:opts, 'dir')
1314 let job.cwd = a:opts.dir
1318 \ 'on_stdout': function('s:nvim_cb'),
1319 \ 'on_stderr': function('s:nvim_cb'),
1320 \ 'on_exit': function('s:nvim_cb'),
1322 let jid = s:plug_call('jobstart', argv, job)
1328 let job.lines = [jid < 0 ? argv[0].' is not executable' :
1329 \ 'Invalid arguments (or job table is full)']
1332 let cmd = join(map(copy(a:cmd), 'plug#shellescape(v:val, {"script": 0})'))
1333 if has_key(a:opts, 'dir')
1334 let cmd = s:with_cd(cmd, a:opts.dir, 0)
1336 let argv = s:is_win ? ['cmd', '/s', '/c', '"'.cmd.'"'] : ['sh', '-c', cmd]
1337 let jid = job_start(s:is_win ? join(argv, ' ') : argv, {
1338 \ 'out_cb': function('s:job_cb', ['s:job_out_cb', job]),
1339 \ 'err_cb': function('s:job_cb', ['s:job_out_cb', job]),
1340 \ 'exit_cb': function('s:job_cb', ['s:job_exit_cb', job]),
1341 \ 'err_mode': 'raw',
1344 if job_status(jid) == 'run'
1349 let job.lines = ['Failed to start job']
1352 let job.lines = s:lines(call('s:system', has_key(a:opts, 'dir') ? [a:cmd, a:opts.dir] : [a:cmd]))
1353 let job.error = v:shell_error != 0
1358 function! s:reap(name)
1359 let job = s:jobs[a:name]
1361 call add(s:update.errors, a:name)
1362 elseif get(job, 'new', 0)
1363 let s:update.new[a:name] = 1
1365 let s:update.bar .= job.error ? 'x' : '='
1367 let bullet = job.error ? 'x' : '-'
1368 let result = job.error ? join(job.lines, "\n") : s:last_non_empty_line(job.lines)
1369 call s:log(bullet, a:name, empty(result) ? 'OK' : result)
1372 call remove(s:jobs, a:name)
1377 let total = len(s:update.all)
1378 call setline(1, (s:update.pull ? 'Updating' : 'Installing').
1379 \ ' plugins ('.len(s:update.bar).'/'.total.')')
1380 call s:progress_bar(2, s:update.bar, total)
1385 function! s:logpos(name)
1387 for i in range(4, max > 4 ? max : 4)
1388 if getline(i) =~# '^[-+x*] '.a:name.':'
1389 for j in range(i + 1, max > 5 ? max : 5)
1390 if getline(j) !~ '^ '
1400 function! s:log(bullet, name, lines)
1402 let [b, e] = s:logpos(a:name)
1404 silent execute printf('%d,%d d _', b, e)
1405 if b > winheight('.')
1411 " FIXME For some reason, nomodifiable is set after :d in vim8
1413 call append(b - 1, s:format_message(a:bullet, a:name, a:lines))
1418 function! s:update_vim()
1426 let pull = s:update.pull
1427 let prog = s:progress_opt(s:nvim || s:vim8)
1428 while 1 " Without TCO, Vim stack is bound to explode
1429 if empty(s:update.todo)
1430 if empty(s:jobs) && !s:update.fin
1431 call s:update_finish()
1432 let s:update.fin = 1
1437 let name = keys(s:update.todo)[0]
1438 let spec = remove(s:update.todo, name)
1439 let new = empty(globpath(spec.dir, '.git', 1))
1441 call s:log(new ? '+' : '*', name, pull ? 'Updating ...' : 'Installing ...')
1444 let has_tag = has_key(spec, 'tag')
1446 let [error, _] = s:git_validate(spec, 0)
1449 let cmd = ['git', 'fetch']
1450 if has_tag && !empty(globpath(spec.dir, '.git/shallow'))
1451 call extend(cmd, ['--depth', '99999999'])
1456 call s:spawn(name, cmd, { 'dir': spec.dir })
1458 let s:jobs[name] = { 'running': 0, 'lines': ['Already installed'], 'error': 0 }
1461 let s:jobs[name] = { 'running': 0, 'lines': s:lines(error), 'error': 1 }
1464 let cmd = ['git', 'clone']
1466 call extend(cmd, s:clone_opt)
1471 call s:spawn(name, extend(cmd, [spec.uri, s:trim(spec.dir)]), { 'new': 1 })
1474 if !s:jobs[name].running
1477 if len(s:jobs) >= s:update.threads
1483 function! s:update_python()
1484 let py_exe = has('python') ? 'python' : 'python3'
1485 execute py_exe "<< EOF"
1492 import Queue as queue
1499 import threading as thr
1504 G_NVIM = vim.eval("has('nvim')") == '1'
1505 G_PULL = vim.eval('s:update.pull') == '1'
1506 G_RETRIES = int(vim.eval('get(g:, "plug_retries", 2)')) + 1
1507 G_TIMEOUT = int(vim.eval('get(g:, "plug_timeout", 60)'))
1508 G_CLONE_OPT = ' '.join(vim.eval('s:clone_opt'))
1509 G_PROGRESS = vim.eval('s:progress_opt(1)')
1510 G_LOG_PROB = 1.0 / int(vim.eval('s:update.threads'))
1511 G_STOP = thr.Event()
1512 G_IS_WIN = vim.eval('s:is_win') == '1'
1514 class PlugError(Exception):
1515 def __init__(self, msg):
1517 class CmdTimedOut(PlugError):
1519 class CmdFailed(PlugError):
1521 class InvalidURI(PlugError):
1523 class Action(object):
1524 INSTALL, UPDATE, ERROR, DONE = ['+', '*', 'x', '-']
1526 class Buffer(object):
1527 def __init__(self, lock, num_plugs, is_pull):
1529 self.event = 'Updating' if is_pull else 'Installing'
1531 self.maxy = int(vim.eval('winheight(".")'))
1532 self.num_plugs = num_plugs
1534 def __where(self, name):
1535 """ Find first line with name in current buffer. Return line num. """
1536 found, lnum = False, 0
1537 matcher = re.compile('^[-+x*] {0}:'.format(name))
1538 for line in vim.current.buffer:
1539 if matcher.search(line) is not None:
1549 curbuf = vim.current.buffer
1550 curbuf[0] = self.event + ' plugins ({0}/{1})'.format(len(self.bar), self.num_plugs)
1552 num_spaces = self.num_plugs - len(self.bar)
1553 curbuf[1] = '[{0}{1}]'.format(self.bar, num_spaces * ' ')
1556 vim.command('normal! 2G')
1557 vim.command('redraw')
1559 def write(self, action, name, lines):
1560 first, rest = lines[0], lines[1:]
1561 msg = ['{0} {1}{2}{3}'.format(action, name, ': ' if first else '', first)]
1562 msg.extend([' ' + line for line in rest])
1565 if action == Action.ERROR:
1567 vim.command("call add(s:update.errors, '{0}')".format(name))
1568 elif action == Action.DONE:
1571 curbuf = vim.current.buffer
1572 lnum = self.__where(name)
1573 if lnum != -1: # Found matching line num
1575 if lnum > self.maxy and action in set([Action.INSTALL, Action.UPDATE]):
1579 curbuf.append(msg, lnum)
1585 class Command(object):
1586 CD = 'cd /d' if G_IS_WIN else 'cd'
1588 def __init__(self, cmd, cmd_dir=None, timeout=60, cb=None, clean=None):
1591 self.cmd = '{0} {1} && {2}'.format(Command.CD, cmd_dir, self.cmd)
1592 self.timeout = timeout
1593 self.callback = cb if cb else (lambda msg: None)
1594 self.clean = clean if clean else (lambda: None)
1599 """ Returns true only if command still running. """
1600 return self.proc and self.proc.poll() is None
1602 def execute(self, ntries=3):
1603 """ Execute the command with ntries if CmdTimedOut.
1604 Returns the output of the command if no Exception.
1606 attempt, finished, limit = 0, False, self.timeout
1611 result = self.try_command()
1615 if attempt != ntries:
1617 self.timeout += limit
1621 def notify_retry(self):
1622 """ Retry required for command, notify user. """
1623 for count in range(3, 0, -1):
1625 raise KeyboardInterrupt
1626 msg = 'Timeout. Will retry in {0} second{1} ...'.format(
1627 count, 's' if count != 1 else '')
1628 self.callback([msg])
1630 self.callback(['Retrying ...'])
1632 def try_command(self):
1633 """ Execute a cmd & poll for callback. Returns list of output.
1634 Raises CmdFailed -> return code for Popen isn't 0
1635 Raises CmdTimedOut -> command exceeded timeout without new output
1640 tfile = tempfile.NamedTemporaryFile(mode='w+b')
1641 preexec_fn = not G_IS_WIN and os.setsid or None
1642 self.proc = subprocess.Popen(self.cmd, stdout=tfile,
1643 stderr=subprocess.STDOUT,
1644 stdin=subprocess.PIPE, shell=True,
1645 preexec_fn=preexec_fn)
1646 thrd = thr.Thread(target=(lambda proc: proc.wait()), args=(self.proc,))
1649 thread_not_started = True
1650 while thread_not_started:
1653 thread_not_started = False
1654 except RuntimeError:
1659 raise KeyboardInterrupt
1661 if first_line or random.random() < G_LOG_PROB:
1663 line = '' if G_IS_WIN else nonblock_read(tfile.name)
1665 self.callback([line])
1667 time_diff = time.time() - os.path.getmtime(tfile.name)
1668 if time_diff > self.timeout:
1669 raise CmdTimedOut(['Timeout!'])
1674 result = [line.decode('utf-8', 'replace').rstrip() for line in tfile]
1676 if self.proc.returncode != 0:
1677 raise CmdFailed([''] + result)
1684 def terminate(self):
1685 """ Terminate process and cleanup. """
1688 os.kill(self.proc.pid, signal.SIGINT)
1690 os.killpg(self.proc.pid, signal.SIGTERM)
1693 class Plugin(object):
1694 def __init__(self, name, args, buf_q, lock):
1699 self.tag = args.get('tag', 0)
1703 if os.path.exists(self.args['dir']):
1708 thread_vim_command("let s:update.new['{0}'] = 1".format(self.name))
1709 except PlugError as exc:
1710 self.write(Action.ERROR, self.name, exc.msg)
1711 except KeyboardInterrupt:
1713 self.write(Action.ERROR, self.name, ['Interrupted!'])
1715 # Any exception except those above print stack trace
1716 msg = 'Trace:\n{0}'.format(traceback.format_exc().rstrip())
1717 self.write(Action.ERROR, self.name, msg.split('\n'))
1721 target = self.args['dir']
1722 if target[-1] == '\\':
1723 target = target[0:-1]
1728 shutil.rmtree(target)
1733 self.write(Action.INSTALL, self.name, ['Installing ...'])
1734 callback = functools.partial(self.write, Action.INSTALL, self.name)
1735 cmd = 'git clone {0} {1} {2} {3} 2>&1'.format(
1736 '' if self.tag else G_CLONE_OPT, G_PROGRESS, self.args['uri'],
1738 com = Command(cmd, None, G_TIMEOUT, callback, clean(target))
1739 result = com.execute(G_RETRIES)
1740 self.write(Action.DONE, self.name, result[-1:])
1743 cmd = 'git rev-parse --abbrev-ref HEAD 2>&1 && git config -f .git/config remote.origin.url'
1744 command = Command(cmd, self.args['dir'], G_TIMEOUT,)
1745 result = command.execute(G_RETRIES)
1749 actual_uri = self.repo_uri()
1750 expect_uri = self.args['uri']
1751 regex = re.compile(r'^(?:\w+://)?(?:[^@/]*@)?([^:/]*(?::[0-9]*)?)[:/](.*?)(?:\.git)?/?$')
1752 ma = regex.match(actual_uri)
1753 mb = regex.match(expect_uri)
1754 if ma is None or mb is None or ma.groups() != mb.groups():
1756 'Invalid URI: {0}'.format(actual_uri),
1757 'Expected {0}'.format(expect_uri),
1758 'PlugClean required.']
1759 raise InvalidURI(msg)
1762 self.write(Action.UPDATE, self.name, ['Updating ...'])
1763 callback = functools.partial(self.write, Action.UPDATE, self.name)
1764 fetch_opt = '--depth 99999999' if self.tag and os.path.isfile(os.path.join(self.args['dir'], '.git/shallow')) else ''
1765 cmd = 'git fetch {0} {1} 2>&1'.format(fetch_opt, G_PROGRESS)
1766 com = Command(cmd, self.args['dir'], G_TIMEOUT, callback)
1767 result = com.execute(G_RETRIES)
1768 self.write(Action.DONE, self.name, result[-1:])
1770 self.write(Action.DONE, self.name, ['Already installed'])
1772 def write(self, action, name, msg):
1773 self.buf_q.put((action, name, msg))
1775 class PlugThread(thr.Thread):
1776 def __init__(self, tname, args):
1777 super(PlugThread, self).__init__()
1782 thr.current_thread().name = self.tname
1783 buf_q, work_q, lock = self.args
1786 while not G_STOP.is_set():
1787 name, args = work_q.get_nowait()
1788 plug = Plugin(name, args, buf_q, lock)
1794 class RefreshThread(thr.Thread):
1795 def __init__(self, lock):
1796 super(RefreshThread, self).__init__()
1803 thread_vim_command('noautocmd normal! a')
1807 self.running = False
1810 def thread_vim_command(cmd):
1811 vim.session.threadsafe_call(lambda: vim.command(cmd))
1813 def thread_vim_command(cmd):
1817 return '"' + name.replace('"', '\"') + '"'
1819 def nonblock_read(fname):
1820 """ Read a file with nonblock flag. Return the last line. """
1821 fread = os.open(fname, os.O_RDONLY | os.O_NONBLOCK)
1822 buf = os.read(fread, 100000).decode('utf-8', 'replace')
1825 line = buf.rstrip('\r\n')
1826 left = max(line.rfind('\r'), line.rfind('\n'))
1834 thr.current_thread().name = 'main'
1835 nthreads = int(vim.eval('s:update.threads'))
1836 plugs = vim.eval('s:update.todo')
1837 mac_gui = vim.eval('s:mac_gui') == '1'
1840 buf = Buffer(lock, len(plugs), G_PULL)
1841 buf_q, work_q = queue.Queue(), queue.Queue()
1842 for work in plugs.items():
1845 start_cnt = thr.active_count()
1846 for num in range(nthreads):
1847 tname = 'PlugT-{0:02}'.format(num)
1848 thread = PlugThread(tname, (buf_q, work_q, lock))
1851 rthread = RefreshThread(lock)
1854 while not buf_q.empty() or thr.active_count() != start_cnt:
1856 action, name, msg = buf_q.get(True, 0.25)
1857 buf.write(action, name, ['OK'] if not msg else msg)
1861 except KeyboardInterrupt:
1872 function! s:update_ruby()
1875 SEP = ["\r", "\n", nil]
1879 char = readchar rescue return
1880 if SEP.include? char.chr
1889 end unless defined?(PlugStream)
1892 %["#{arg.gsub('"', '\"')}"]
1897 if /mswin|mingw|bccwin/ =~ RUBY_PLATFORM
1898 pids.each { |pid| Process.kill 'INT', pid.to_i rescue nil }
1900 unless `which pgrep 2> /dev/null`.empty?
1902 until children.empty?
1903 children = children.map { |pid|
1904 `pgrep -P #{pid}`.lines.map { |l| l.chomp }
1909 pids.each { |pid| Process.kill 'TERM', pid.to_i rescue nil }
1913 def compare_git_uri a, b
1914 regex = %r{^(?:\w+://)?(?:[^@/]*@)?([^:/]*(?::[0-9]*)?)[:/](.*?)(?:\.git)?/?$}
1915 regex.match(a).to_a.drop(1) == regex.match(b).to_a.drop(1)
1922 iswin = VIM::evaluate('s:is_win').to_i == 1
1923 pull = VIM::evaluate('s:update.pull').to_i == 1
1924 base = VIM::evaluate('g:plug_home')
1925 all = VIM::evaluate('s:update.todo')
1926 limit = VIM::evaluate('get(g:, "plug_timeout", 60)')
1927 tries = VIM::evaluate('get(g:, "plug_retries", 2)') + 1
1928 nthr = VIM::evaluate('s:update.threads').to_i
1929 maxy = VIM::evaluate('winheight(".")').to_i
1930 vim7 = VIM::evaluate('v:version').to_i <= 703 && RUBY_PLATFORM =~ /darwin/
1931 cd = iswin ? 'cd /d' : 'cd'
1932 tot = VIM::evaluate('len(s:update.todo)') || 0
1934 skip = 'Already installed'
1936 take1 = proc { mtx.synchronize { running && all.shift } }
1939 $curbuf[1] = "#{pull ? 'Updating' : 'Installing'} plugins (#{cnt}/#{tot})"
1940 $curbuf[2] = '[' + bar.ljust(tot) + ']'
1941 VIM::command('normal! 2G')
1942 VIM::command('redraw')
1944 where = proc { |name| (1..($curbuf.length)).find { |l| $curbuf[l] =~ /^[-+x*] #{name}:/ } }
1945 log = proc { |name, result, type|
1947 ing = ![true, false].include?(type)
1948 bar += type ? '=' : 'x' unless ing
1950 when :install then '+' when :update then '*'
1951 when true, nil then '-' else
1952 VIM::command("call add(s:update.errors, '#{name}')")
1956 if type || type.nil?
1957 ["#{b} #{name}: #{result.lines.to_a.last || 'OK'}"]
1958 elsif result =~ /^Interrupted|^Timeout/
1959 ["#{b} #{name}: #{result}"]
1961 ["#{b} #{name}"] + result.lines.map { |l| " " << l }
1963 if lnum = where.call(name)
1965 lnum = 4 if ing && lnum > maxy
1967 result.each_with_index do |line, offset|
1968 $curbuf.append((lnum || 4) - 1 + offset, line.gsub(/\e\[./, '').chomp)
1973 bt = proc { |cmd, name, type, cleanup|
1981 Timeout::timeout(timeout) do
1982 tmp = VIM::evaluate('tempname()')
1983 system("(#{cmd}) > #{tmp}")
1984 data = File.read(tmp).chomp
1985 File.unlink tmp rescue nil
1988 fd = IO.popen(cmd).extend(PlugStream)
1990 log_prob = 1.0 / nthr
1991 while line = Timeout::timeout(timeout) { fd.get_line }
1993 log.call name, line.chomp, type if name && (first_line || rand < log_prob)
1998 [$? == 0, data.chomp]
1999 rescue Timeout::Error, Interrupt => e
2000 if fd && !fd.closed?
2004 cleanup.call if cleanup
2005 if e.is_a?(Timeout::Error) && tried < tries
2006 3.downto(1) do |countdown|
2007 s = countdown > 1 ? 's' : ''
2008 log.call name, "Timeout. Will retry in #{countdown} second#{s} ...", type
2011 log.call name, 'Retrying ...', type
2014 [false, e.is_a?(Interrupt) ? "Interrupted!" : "Timeout!"]
2017 main = Thread.current
2019 watcher = Thread.new {
2021 while VIM::evaluate('getchar(1)')
2025 require 'io/console' # >= Ruby 1.9
2026 nil until IO.console.getch == 3.chr
2030 threads.each { |t| t.raise Interrupt } unless vim7
2032 threads.each { |t| t.join rescue nil }
2035 refresh = Thread.new {
2038 break unless running
2039 VIM::command('noautocmd normal! a')
2043 } if VIM::evaluate('s:mac_gui') == 1
2045 clone_opt = VIM::evaluate('s:clone_opt').join(' ')
2046 progress = VIM::evaluate('s:progress_opt(1)')
2049 threads << Thread.new {
2050 while pair = take1.call
2052 dir, uri, tag = pair.last.values_at *%w[dir uri tag]
2053 exists = File.directory? dir
2056 chdir = "#{cd} #{iswin ? dir : esc(dir)}"
2057 ret, data = bt.call "#{chdir} && git rev-parse --abbrev-ref HEAD 2>&1 && git config -f .git/config remote.origin.url", nil, nil, nil
2058 current_uri = data.lines.to_a.last
2060 if data =~ /^Interrupted|^Timeout/
2063 [false, [data.chomp, "PlugClean required."].join($/)]
2065 elsif !compare_git_uri(current_uri, uri)
2066 [false, ["Invalid URI: #{current_uri}",
2068 "PlugClean required."].join($/)]
2071 log.call name, 'Updating ...', :update
2072 fetch_opt = (tag && File.exist?(File.join(dir, '.git/shallow'))) ? '--depth 99999999' : ''
2073 bt.call "#{chdir} && git fetch #{fetch_opt} #{progress} 2>&1", name, :update, nil
2079 d = esc dir.sub(%r{[\\/]+$}, '')
2080 log.call name, 'Installing ...', :install
2081 bt.call "git clone #{clone_opt unless tag} #{progress} #{uri} #{d} 2>&1", name, :install, proc {
2085 mtx.synchronize { VIM::command("let s:update.new['#{name}'] = 1") } if !exists && ok
2086 log.call name, result, ok
2091 threads.each { |t| t.join rescue nil }
2093 refresh.kill if refresh
2098 function! s:shellesc_cmd(arg, script)
2099 let escaped = substitute('"'.a:arg.'"', '[&|<>()@^!"]', '^&', 'g')
2100 return substitute(escaped, '%', (a:script ? '%' : '^') . '&', 'g')
2103 function! s:shellesc_ps1(arg)
2104 return "'".substitute(escape(a:arg, '\"'), "'", "''", 'g')."'"
2107 function! s:shellesc_sh(arg)
2108 return "'".substitute(a:arg, "'", "'\\\\''", 'g')."'"
2111 " Escape the shell argument based on the shell.
2112 " Vim and Neovim's shellescape() are insufficient.
2113 " 1. shellslash determines whether to use single/double quotes.
2114 " Double-quote escaping is fragile for cmd.exe.
2115 " 2. It does not work for powershell.
2116 " 3. It does not work for *sh shells if the command is executed
2117 " via cmd.exe (ie. cmd.exe /c sh -c command command_args)
2118 " 4. It does not support batchfile syntax.
2120 " Accepts an optional dictionary with the following keys:
2121 " - shell: same as Vim/Neovim 'shell' option.
2122 " If unset, fallback to 'cmd.exe' on Windows or 'sh'.
2123 " - script: If truthy and shell is cmd.exe, escape for batchfile syntax.
2124 function! plug#shellescape(arg, ...)
2125 if a:arg =~# '^[A-Za-z0-9_/:.-]\+$'
2128 let opts = a:0 > 0 && type(a:1) == s:TYPE.dict ? a:1 : {}
2129 let shell = get(opts, 'shell', s:is_win ? 'cmd.exe' : 'sh')
2130 let script = get(opts, 'script', 1)
2131 if shell =~# 'cmd\.exe'
2132 return s:shellesc_cmd(a:arg, script)
2133 elseif shell =~# 'powershell\.exe' || shell =~# 'pwsh$'
2134 return s:shellesc_ps1(a:arg)
2136 return s:shellesc_sh(a:arg)
2139 function! s:glob_dir(path)
2140 return map(filter(s:glob(a:path, '**'), 'isdirectory(v:val)'), 's:dirpath(v:val)')
2143 function! s:progress_bar(line, bar, total)
2144 call setline(a:line, '[' . s:lpad(a:bar, a:total) . ']')
2147 function! s:compare_git_uri(a, b)
2148 " See `git help clone'
2149 " https:// [user@] github.com[:port] / junegunn/vim-plug [.git]
2150 " [git@] github.com[:port] : junegunn/vim-plug [.git]
2151 " file:// / junegunn/vim-plug [/]
2152 " / junegunn/vim-plug [/]
2153 let pat = '^\%(\w\+://\)\='.'\%([^@/]*@\)\='.'\([^:/]*\%(:[0-9]*\)\=\)'.'[:/]'.'\(.\{-}\)'.'\%(\.git\)\=/\?$'
2154 let ma = matchlist(a:a, pat)
2155 let mb = matchlist(a:b, pat)
2156 return ma[1:2] ==# mb[1:2]
2159 function! s:format_message(bullet, name, message)
2161 return [printf('%s %s: %s', a:bullet, a:name, s:lastline(a:message))]
2163 let lines = map(s:lines(a:message), '" ".v:val')
2164 return extend([printf('x %s:', a:name)], lines)
2168 function! s:with_cd(cmd, dir, ...)
2169 let script = a:0 > 0 ? a:1 : 1
2170 return printf('cd%s %s && %s', s:is_win ? ' /d' : '', plug#shellescape(a:dir, {'script': script}), a:cmd)
2173 function! s:system(cmd, ...)
2176 let [sh, shellcmdflag, shrd] = s:chsh(1)
2177 if type(a:cmd) == s:TYPE.list
2178 " Neovim's system() supports list argument to bypass the shell
2179 " but it cannot set the working directory for the command.
2180 " Assume that the command does not rely on the shell.
2181 if has('nvim') && a:0 == 0
2182 return system(a:cmd)
2184 let cmd = join(map(copy(a:cmd), 'plug#shellescape(v:val, {"shell": &shell, "script": 0})'))
2185 if &shell =~# 'powershell\.exe'
2186 let cmd = '& ' . cmd
2192 let cmd = s:with_cd(cmd, a:1, type(a:cmd) != s:TYPE.list)
2194 if s:is_win && type(a:cmd) != s:TYPE.list
2195 let [batchfile, cmd] = s:batchfile(cmd)
2199 let [&shell, &shellcmdflag, &shellredir] = [sh, shellcmdflag, shrd]
2200 if s:is_win && filereadable(batchfile)
2201 call delete(batchfile)
2206 function! s:system_chomp(...)
2207 let ret = call('s:system', a:000)
2208 return v:shell_error ? '' : substitute(ret, '\n$', '', '')
2211 function! s:git_validate(spec, check_branch)
2213 if isdirectory(a:spec.dir)
2214 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))
2215 let remote = result[-1]
2217 let err = join([remote, 'PlugClean required.'], "\n")
2218 elseif !s:compare_git_uri(remote, a:spec.uri)
2219 let err = join(['Invalid URI: '.remote,
2220 \ 'Expected: '.a:spec.uri,
2221 \ 'PlugClean required.'], "\n")
2222 elseif a:check_branch && has_key(a:spec, 'commit')
2223 let result = s:lines(s:system('git rev-parse HEAD 2>&1', a:spec.dir))
2224 let sha = result[-1]
2226 let err = join(add(result, 'PlugClean required.'), "\n")
2227 elseif !s:hash_match(sha, a:spec.commit)
2228 let err = join([printf('Invalid HEAD (expected: %s, actual: %s)',
2229 \ a:spec.commit[:6], sha[:6]),
2230 \ 'PlugUpdate required.'], "\n")
2232 elseif a:check_branch
2233 let branch = result[0]
2235 if has_key(a:spec, 'tag')
2236 let tag = s:system_chomp('git describe --exact-match --tags HEAD 2>&1', a:spec.dir)
2237 if a:spec.tag !=# tag && a:spec.tag !~ '\*'
2238 let err = printf('Invalid tag: %s (expected: %s). Try PlugUpdate.',
2239 \ (empty(tag) ? 'N/A' : tag), a:spec.tag)
2242 elseif a:spec.branch !=# branch
2243 let err = printf('Invalid branch: %s (expected: %s). Try PlugUpdate.',
2244 \ branch, a:spec.branch)
2247 let [ahead, behind] = split(s:lastline(s:system([
2248 \ 'git', 'rev-list', '--count', '--left-right',
2249 \ printf('HEAD...origin/%s', a:spec.branch)
2250 \ ], a:spec.dir)), '\t')
2251 if !v:shell_error && ahead
2253 " Only mention PlugClean if diverged, otherwise it's likely to be
2254 " pushable (and probably not that messed up).
2256 \ "Diverged from origin/%s (%d commit(s) ahead and %d commit(s) behind!\n"
2257 \ .'Backup local changes and run PlugClean and PlugUpdate to reinstall it.', a:spec.branch, ahead, behind)
2259 let err = printf("Ahead of origin/%s by %d commit(s).\n"
2260 \ .'Cannot update until local changes are pushed.',
2261 \ a:spec.branch, ahead)
2267 let err = 'Not found'
2269 return [err, err =~# 'PlugClean']
2272 function! s:rm_rf(dir)
2273 if isdirectory(a:dir)
2274 call s:system(s:is_win
2275 \ ? 'rmdir /S /Q '.plug#shellescape(a:dir)
2276 \ : ['rm', '-rf', a:dir])
2280 function! s:clean(force)
2282 call append(0, 'Searching for invalid plugins in '.g:plug_home)
2285 " List of valid directories
2288 let [cnt, total] = [0, len(g:plugs)]
2289 for [name, spec] in items(g:plugs)
2290 if !s:is_managed(name)
2291 call add(dirs, spec.dir)
2293 let [err, clean] = s:git_validate(spec, 1)
2295 let errs[spec.dir] = s:lines(err)[0]
2297 call add(dirs, spec.dir)
2301 call s:progress_bar(2, repeat('=', cnt), total)
2308 let allowed[s:dirpath(s:plug_fnamemodify(dir, ':h:h'))] = 1
2309 let allowed[dir] = 1
2310 for child in s:glob_dir(dir)
2311 let allowed[child] = 1
2316 let found = sort(s:glob_dir(g:plug_home))
2318 let f = remove(found, 0)
2319 if !has_key(allowed, f) && isdirectory(f)
2321 call append(line('$'), '- ' . f)
2323 call append(line('$'), ' ' . errs[f])
2325 let found = filter(found, 'stridx(v:val, f) != 0')
2332 call append(line('$'), 'Already clean.')
2334 let s:clean_count = 0
2335 call append(3, ['Directories to delete:', ''])
2337 if a:force || s:ask_no_interrupt('Delete all directories?')
2338 call s:delete([6, line('$')], 1)
2340 call setline(4, 'Cancelled.')
2341 nnoremap <silent> <buffer> d :set opfunc=<sid>delete_op<cr>g@
2342 nmap <silent> <buffer> dd d_
2343 xnoremap <silent> <buffer> d :<c-u>call <sid>delete_op(visualmode(), 1)<cr>
2344 echo 'Delete the lines (d{motion}) to delete the corresponding directories'
2348 setlocal nomodifiable
2351 function! s:delete_op(type, ...)
2352 call s:delete(a:0 ? [line("'<"), line("'>")] : [line("'["), line("']")], 0)
2355 function! s:delete(range, force)
2356 let [l1, l2] = a:range
2359 let line = getline(l1)
2360 if line =~ '^- ' && isdirectory(line[2:])
2363 let answer = force ? 1 : s:ask('Delete '.line[2:].'?', 1)
2364 let force = force || answer > 1
2366 call s:rm_rf(line[2:])
2368 call setline(l1, '~'.line[1:])
2369 let s:clean_count += 1
2370 call setline(4, printf('Removed %d directories.', s:clean_count))
2371 setlocal nomodifiable
2378 function! s:upgrade()
2379 echo 'Downloading the latest version of vim-plug'
2381 let tmp = s:plug_tempname()
2382 let new = tmp . '/plug.vim'
2385 let out = s:system(['git', 'clone', '--depth', '1', s:plug_src, tmp])
2387 return s:err('Error upgrading vim-plug: '. out)
2390 if readfile(s:me) ==# readfile(new)
2391 echo 'vim-plug is already up-to-date'
2394 call rename(s:me, s:me . '.old')
2395 call rename(new, s:me)
2397 echo 'vim-plug has been upgraded'
2401 silent! call s:rm_rf(tmp)
2405 function! s:upgrade_specs()
2406 for spec in values(g:plugs)
2407 let spec.frozen = get(spec, 'frozen', 0)
2411 function! s:status()
2413 call append(0, 'Checking plugins')
2418 let [cnt, total] = [0, len(g:plugs)]
2419 for [name, spec] in items(g:plugs)
2420 let is_dir = isdirectory(spec.dir)
2421 if has_key(spec, 'uri')
2423 let [err, _] = s:git_validate(spec, 1)
2424 let [valid, msg] = [empty(err), empty(err) ? 'OK' : err]
2426 let [valid, msg] = [0, 'Not found. Try PlugInstall.']
2430 let [valid, msg] = [1, 'OK']
2432 let [valid, msg] = [0, 'Not found.']
2437 " `s:loaded` entry can be missing if PlugUpgraded
2438 if is_dir && get(s:loaded, name, -1) == 0
2440 let msg .= ' (not loaded)'
2442 call s:progress_bar(2, repeat('=', cnt), total)
2443 call append(3, s:format_message(valid ? '-' : 'x', name, msg))
2447 call setline(1, 'Finished. '.ecnt.' error(s).')
2449 setlocal nomodifiable
2451 echo "Press 'L' on each line to load plugin, or 'U' to update"
2452 nnoremap <silent> <buffer> L :call <SID>status_load(line('.'))<cr>
2453 xnoremap <silent> <buffer> L :call <SID>status_load(line('.'))<cr>
2457 function! s:extract_name(str, prefix, suffix)
2458 return matchstr(a:str, '^'.a:prefix.' \zs[^:]\+\ze:.*'.a:suffix.'$')
2461 function! s:status_load(lnum)
2462 let line = getline(a:lnum)
2463 let name = s:extract_name(line, '-', '(not loaded)')
2465 call plug#load(name)
2467 call setline(a:lnum, substitute(line, ' (not loaded)$', '', ''))
2468 setlocal nomodifiable
2472 function! s:status_update() range
2473 let lines = getline(a:firstline, a:lastline)
2474 let names = filter(map(lines, 's:extract_name(v:val, "[x-]", "")'), '!empty(v:val)')
2477 execute 'PlugUpdate' join(names)
2481 function! s:is_preview_window_open()
2489 function! s:find_name(lnum)
2490 for lnum in reverse(range(1, a:lnum))
2491 let line = getline(lnum)
2495 let name = s:extract_name(line, '-', '')
2503 function! s:preview_commit()
2504 if b:plug_preview < 0
2505 let b:plug_preview = !s:is_preview_window_open()
2508 let sha = matchstr(getline('.'), '^ \X*\zs[0-9a-f]\{7,9}')
2513 let name = s:find_name(line('.'))
2514 if empty(name) || !has_key(g:plugs, name) || !isdirectory(g:plugs[name].dir)
2518 if exists('g:plug_pwindow') && !s:is_preview_window_open()
2519 execute g:plug_pwindow
2525 setlocal previewwindow filetype=git buftype=nofile nobuflisted modifiable
2528 let [sh, shellcmdflag, shrd] = s:chsh(1)
2529 let cmd = 'cd '.plug#shellescape(g:plugs[name].dir).' && git show --no-color --pretty=medium '.sha
2531 let [batchfile, cmd] = s:batchfile(cmd)
2533 execute 'silent %!' cmd
2535 let [&shell, &shellcmdflag, &shellredir] = [sh, shellcmdflag, shrd]
2536 if s:is_win && filereadable(batchfile)
2537 call delete(batchfile)
2540 setlocal nomodifiable
2541 nnoremap <silent> <buffer> q :q<cr>
2545 function! s:section(flags)
2546 call search('\(^[x-] \)\@<=[^:]\+:', a:flags)
2549 function! s:format_git_log(line)
2551 let tokens = split(a:line, nr2char(1))
2553 return indent.substitute(a:line, '\s*$', '', '')
2555 let [graph, sha, refs, subject, date] = tokens
2556 let tag = matchstr(refs, 'tag: [^,)]\+')
2557 let tag = empty(tag) ? ' ' : ' ('.tag.') '
2558 return printf('%s%s%s%s%s (%s)', indent, graph, sha, tag, subject, date)
2561 function! s:append_ul(lnum, text)
2562 call append(a:lnum, ['', a:text, repeat('-', len(a:text))])
2567 call append(0, ['Collecting changes ...', ''])
2570 let total = filter(copy(g:plugs), 's:is_managed(v:key) && isdirectory(v:val.dir)')
2571 call s:progress_bar(2, bar, len(total))
2572 for origin in [1, 0]
2573 let plugs = reverse(sort(items(filter(copy(total), (origin ? '' : '!').'(has_key(v:val, "commit") || has_key(v:val, "tag"))'))))
2577 call s:append_ul(2, origin ? 'Pending updates:' : 'Last update:')
2579 let range = origin ? '..origin/'.v.branch : 'HEAD@{1}..'
2580 let cmd = ['git', 'log', '--graph', '--color=never']
2581 if s:git_version_requirement(2, 10, 0)
2582 call add(cmd, '--no-show-signature')
2584 call extend(cmd, ['--pretty=format:%x01%h%x01%d%x01%s%x01%cr', range])
2585 if has_key(v, 'rtp')
2586 call extend(cmd, ['--', v.rtp])
2588 let diff = s:system_chomp(cmd, v.dir)
2590 let ref = has_key(v, 'tag') ? (' (tag: '.v.tag.')') : has_key(v, 'commit') ? (' '.v.commit) : ''
2591 call append(5, extend(['', '- '.k.':'.ref], map(s:lines(diff), 's:format_git_log(v:val)')))
2592 let cnts[origin] += 1
2595 call s:progress_bar(2, bar, len(total))
2600 call append(5, ['', 'N/A'])
2603 call setline(1, printf('%d plugin(s) updated.', cnts[0])
2604 \ . (cnts[1] ? printf(' %d plugin(s) have pending updates.', cnts[1]) : ''))
2606 if cnts[0] || cnts[1]
2607 nnoremap <silent> <buffer> <plug>(plug-preview) :silent! call <SID>preview_commit()<cr>
2608 if empty(maparg("\<cr>", 'n'))
2609 nmap <buffer> <cr> <plug>(plug-preview)
2611 if empty(maparg('o', 'n'))
2612 nmap <buffer> o <plug>(plug-preview)
2616 nnoremap <silent> <buffer> X :call <SID>revert()<cr>
2617 echo "Press 'X' on each block to revert the update"
2620 setlocal nomodifiable
2623 function! s:revert()
2624 if search('^Pending updates', 'bnW')
2628 let name = s:find_name(line('.'))
2629 if empty(name) || !has_key(g:plugs, name) ||
2630 \ input(printf('Revert the update of %s? (y/N) ', name)) !~? '^y'
2634 call s:system('git reset --hard HEAD@{1} && git checkout '.plug#shellescape(g:plugs[name].branch).' --', g:plugs[name].dir)
2637 setlocal nomodifiable
2641 function! s:snapshot(force, ...) abort
2644 call append(0, ['" Generated by vim-plug',
2645 \ '" '.strftime("%c"),
2646 \ '" :source this file in vim to restore the snapshot',
2647 \ '" or execute: vim -S snapshot.vim',
2648 \ '', '', 'PlugUpdate!'])
2650 let anchor = line('$') - 3
2651 let names = sort(keys(filter(copy(g:plugs),
2652 \'has_key(v:val, "uri") && !has_key(v:val, "commit") && isdirectory(v:val.dir)')))
2653 for name in reverse(names)
2654 let sha = s:system_chomp(['git', 'rev-parse', '--short', 'HEAD'], g:plugs[name].dir)
2656 call append(anchor, printf("silent! let g:plugs['%s'].commit = '%s'", name, sha))
2662 let fn = s:plug_expand(a:1)
2663 if filereadable(fn) && !(a:force || s:ask(a:1.' already exists. Overwrite?'))
2666 call writefile(getline(1, '$'), fn)
2667 echo 'Saved as '.a:1
2668 silent execute 'e' s:esc(fn)
2673 function! s:split_rtp()
2674 return split(&rtp, '\\\@<!,')
2677 let s:first_rtp = s:escrtp(get(s:split_rtp(), 0, ''))
2678 let s:last_rtp = s:escrtp(get(s:split_rtp(), -1, ''))
2680 if exists('g:plugs')
2681 let g:plugs_order = get(g:, 'plugs_order', keys(g:plugs))
2682 call s:upgrade_specs()
2683 call s:define_commands()
2686 let &cpo = s:cpo_save