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-default 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': '', 'frozen': 0 }
111 \ 'string': type(''),
114 \ 'funcref': type(function('call'))
116 let s:loaded = get(s:, 'loaded', {})
117 let s:triggers = get(s:, 'triggers', {})
119 function! s:isabsolute(dir) abort
120 return a:dir =~# '^/' || (has('win32') && a:dir =~? '^\%(\\\|[A-Z]:\)')
123 function! s:git_dir(dir) abort
124 let gitdir = s:trim(a:dir) . '/.git'
125 if isdirectory(gitdir)
128 if !filereadable(gitdir)
131 let gitdir = matchstr(get(readfile(gitdir), 0, ''), '^gitdir: \zs.*')
132 if len(gitdir) && !s:isabsolute(gitdir)
133 let gitdir = a:dir . '/' . gitdir
135 return isdirectory(gitdir) ? gitdir : ''
138 function! s:git_origin_url(dir) abort
139 let gitdir = s:git_dir(a:dir)
140 let config = gitdir . '/config'
141 if empty(gitdir) || !filereadable(config)
144 return matchstr(join(readfile(config)), '\[remote "origin"\].\{-}url\s*=\s*\zs\S*\ze')
147 function! s:git_revision(dir) abort
148 let gitdir = s:git_dir(a:dir)
149 let head = gitdir . '/HEAD'
150 if empty(gitdir) || !filereadable(head)
154 let line = get(readfile(head), 0, '')
155 let ref = matchstr(line, '^ref: \zs.*')
160 if filereadable(gitdir . '/' . ref)
161 return get(readfile(gitdir . '/' . ref), 0, '')
164 if filereadable(gitdir . '/packed-refs')
165 for line in readfile(gitdir . '/packed-refs')
166 if line =~# ' ' . ref
167 return matchstr(line, '^[0-9a-f]*')
175 function! s:git_local_branch(dir) abort
176 let gitdir = s:git_dir(a:dir)
177 let head = gitdir . '/HEAD'
178 if empty(gitdir) || !filereadable(head)
181 let branch = matchstr(get(readfile(head), 0, ''), '^ref: refs/heads/\zs.*')
182 return len(branch) ? branch : 'HEAD'
185 function! s:git_origin_branch(spec)
186 if len(a:spec.branch)
190 " The file may not be present if this is a local repository
191 let gitdir = s:git_dir(a:spec.dir)
192 let origin_head = gitdir.'/refs/remotes/origin/HEAD'
193 if len(gitdir) && filereadable(origin_head)
194 return matchstr(get(readfile(origin_head), 0, ''),
195 \ '^ref: refs/remotes/origin/\zs.*')
198 " The command may not return the name of a branch in detached HEAD state
199 let result = s:lines(s:system('git symbolic-ref --short HEAD', a:spec.dir))
200 return v:shell_error ? '' : result[-1]
204 function! s:plug_call(fn, ...)
205 let shellslash = &shellslash
208 return call(a:fn, a:000)
210 let &shellslash = shellslash
214 function! s:plug_call(fn, ...)
215 return call(a:fn, a:000)
219 function! s:plug_getcwd()
220 return s:plug_call('getcwd')
223 function! s:plug_fnamemodify(fname, mods)
224 return s:plug_call('fnamemodify', a:fname, a:mods)
227 function! s:plug_expand(fmt)
228 return s:plug_call('expand', a:fmt, 1)
231 function! s:plug_tempname()
232 return s:plug_call('tempname')
235 function! plug#begin(...)
237 let s:plug_home_org = a:1
238 let home = s:path(s:plug_fnamemodify(s:plug_expand(a:1), ':p'))
239 elseif exists('g:plug_home')
240 let home = s:path(g:plug_home)
242 let home = s:path(split(&rtp, ',')[0]) . '/plugged'
244 return s:err('Unable to determine plug home. Try calling plug#begin() with a path argument.')
246 if s:plug_fnamemodify(home, ':t') ==# 'plugin' && s:plug_fnamemodify(home, ':h') ==# s:first_rtp
247 return s:err('Invalid plug home. '.home.' is a standard Vim runtime path and is not allowed.')
250 let g:plug_home = home
252 let g:plugs_order = []
255 call s:define_commands()
259 function! s:define_commands()
260 command! -nargs=+ -bar Plug call plug#(<args>)
261 if !executable('git')
262 return s:err('`git` executable not found. Most commands will not be available. To suppress this message, prepend `silent!` to `call plug#begin(...)`.')
266 \ && (&shell =~# 'cmd\(\.exe\)\?$' || &shell =~# 'powershell\(\.exe\)\?$')
267 return s:err('vim-plug does not support shell, ' . &shell . ', when shellslash is set.')
270 \ && (has('win32') || has('win32unix'))
271 \ && !has('multi_byte')
272 return s:err('Vim needs +multi_byte feature on Windows to run shell commands. Enable +iconv for best results.')
274 command! -nargs=* -bar -bang -complete=customlist,s:names PlugInstall call s:install(<bang>0, [<f-args>])
275 command! -nargs=* -bar -bang -complete=customlist,s:names PlugUpdate call s:update(<bang>0, [<f-args>])
276 command! -nargs=0 -bar -bang PlugClean call s:clean(<bang>0)
277 command! -nargs=0 -bar PlugUpgrade if s:upgrade() | execute 'source' s:esc(s:me) | endif
278 command! -nargs=0 -bar PlugStatus call s:status()
279 command! -nargs=0 -bar PlugDiff call s:diff()
280 command! -nargs=? -bar -bang -complete=file PlugSnapshot call s:snapshot(<bang>0, <f-args>)
284 return type(a:v) == s:TYPE.list ? a:v : [a:v]
288 return type(a:v) == s:TYPE.string ? a:v : join(a:v, "\n") . "\n"
291 function! s:glob(from, pattern)
292 return s:lines(globpath(a:from, a:pattern))
295 function! s:source(from, ...)
298 for vim in s:glob(a:from, pattern)
299 execute 'source' s:esc(vim)
306 function! s:assoc(dict, key, val)
307 let a:dict[a:key] = add(get(a:dict, a:key, []), a:val)
310 function! s:ask(message, ...)
313 let answer = input(a:message.(a:0 ? ' (y/N/a) ' : ' (y/N) '))
317 return (a:0 && answer =~? '^a') ? 2 : (answer =~? '^y') ? 1 : 0
320 function! s:ask_no_interrupt(...)
322 return call('s:ask', a:000)
328 function! s:lazy(plug, opt)
329 return has_key(a:plug, a:opt) &&
330 \ (empty(s:to_a(a:plug[a:opt])) ||
331 \ !isdirectory(a:plug.dir) ||
332 \ len(s:glob(s:rtp(a:plug), 'plugin')) ||
333 \ len(s:glob(s:rtp(a:plug), 'after/plugin')))
337 if !exists('g:plugs')
338 return s:err('plug#end() called without calling plug#begin() first')
341 if exists('#PlugLOD')
347 let lod = { 'ft': {}, 'map': {}, 'cmd': {} }
349 if exists('g:did_load_filetypes')
352 for name in g:plugs_order
353 if !has_key(g:plugs, name)
356 let plug = g:plugs[name]
357 if get(s:loaded, name, 0) || !s:lazy(plug, 'on') && !s:lazy(plug, 'for')
358 let s:loaded[name] = 1
362 if has_key(plug, 'on')
363 let s:triggers[name] = { 'map': [], 'cmd': [] }
364 for cmd in s:to_a(plug.on)
365 if cmd =~? '^<Plug>.\+'
366 if empty(mapcheck(cmd)) && empty(mapcheck(cmd, 'i'))
367 call s:assoc(lod.map, cmd, name)
369 call add(s:triggers[name].map, cmd)
370 elseif cmd =~# '^[A-Z]'
371 let cmd = substitute(cmd, '!*$', '', '')
372 if exists(':'.cmd) != 2
373 call s:assoc(lod.cmd, cmd, name)
375 call add(s:triggers[name].cmd, cmd)
377 call s:err('Invalid `on` option: '.cmd.
378 \ '. Should start with an uppercase letter or `<Plug>`.')
383 if has_key(plug, 'for')
384 let types = s:to_a(plug.for)
386 augroup filetypedetect
387 call s:source(s:rtp(plug), 'ftdetect/**/*.vim', 'after/ftdetect/**/*.vim')
391 call s:assoc(lod.ft, type, name)
396 for [cmd, names] in items(lod.cmd)
398 \ 'command! -nargs=* -range -bang -complete=file %s call s:lod_cmd(%s, "<bang>", <line1>, <line2>, <q-args>, %s)',
399 \ cmd, string(cmd), string(names))
402 for [map, names] in items(lod.map)
403 for [mode, map_prefix, key_prefix] in
404 \ [['i', '<C-O>', ''], ['n', '', ''], ['v', '', 'gv'], ['o', '', '']]
406 \ '%snoremap <silent> %s %s:<C-U>call <SID>lod_map(%s, %s, %s, "%s")<CR>',
407 \ mode, map, map_prefix, string(map), string(names), mode != 'i', key_prefix)
411 for [ft, names] in items(lod.ft)
413 execute printf('autocmd FileType %s call <SID>lod_ft(%s, %s)',
414 \ ft, string(ft), string(names))
419 filetype plugin indent on
420 if has('vim_starting')
421 if has('syntax') && !exists('g:syntax_on')
425 call s:reload_plugins()
429 function! s:loaded_names()
430 return filter(copy(g:plugs_order), 'get(s:loaded, v:val, 0)')
433 function! s:load_plugin(spec)
434 call s:source(s:rtp(a:spec), 'plugin/**/*.vim', 'after/plugin/**/*.vim')
437 function! s:reload_plugins()
438 for name in s:loaded_names()
439 call s:load_plugin(g:plugs[name])
443 function! s:trim(str)
444 return substitute(a:str, '[\/]\+$', '', '')
447 function! s:version_requirement(val, min)
448 for idx in range(0, len(a:min) - 1)
449 let v = get(a:val, idx, 0)
450 if v < a:min[idx] | return 0
451 elseif v > a:min[idx] | return 1
457 function! s:git_version_requirement(...)
458 if !exists('s:git_version')
459 let s:git_version = map(split(split(s:system(['git', '--version']))[2], '\.'), 'str2nr(v:val)')
461 return s:version_requirement(s:git_version, a:000)
464 function! s:progress_opt(base)
465 return a:base && !s:is_win &&
466 \ s:git_version_requirement(1, 7, 1) ? '--progress' : ''
469 function! s:rtp(spec)
470 return s:path(a:spec.dir . get(a:spec, 'rtp', ''))
474 function! s:path(path)
475 return s:trim(substitute(a:path, '/', '\', 'g'))
478 function! s:dirpath(path)
479 return s:path(a:path) . '\'
482 function! s:is_local_plug(repo)
483 return a:repo =~? '^[a-z]:\|^[%~]'
487 function! s:wrap_cmds(cmds)
490 \ 'setlocal enabledelayedexpansion']
491 \ + (type(a:cmds) == type([]) ? a:cmds : [a:cmds])
494 if !exists('s:codepage')
495 let s:codepage = libcallnr('kernel32.dll', 'GetACP', 0)
497 return map(cmds, printf('iconv(v:val."\r", "%s", "cp%d")', &encoding, s:codepage))
499 return map(cmds, 'v:val."\r"')
502 function! s:batchfile(cmd)
503 let batchfile = s:plug_tempname().'.bat'
504 call writefile(s:wrap_cmds(a:cmd), batchfile)
505 let cmd = plug#shellescape(batchfile, {'shell': &shell, 'script': 0})
506 if &shell =~# 'powershell\(\.exe\)\?$'
509 return [batchfile, cmd]
512 function! s:path(path)
513 return s:trim(a:path)
516 function! s:dirpath(path)
517 return substitute(a:path, '[/\\]*$', '/', '')
520 function! s:is_local_plug(repo)
521 return a:repo[0] =~ '[/$~]'
527 echom '[vim-plug] '.a:msg
531 function! s:warn(cmd, msg)
533 execute a:cmd 'a:msg'
537 function! s:esc(path)
538 return escape(a:path, ' ')
541 function! s:escrtp(path)
542 return escape(a:path, ' ,')
545 function! s:remove_rtp()
546 for name in s:loaded_names()
547 let rtp = s:rtp(g:plugs[name])
548 execute 'set rtp-='.s:escrtp(rtp)
549 let after = globpath(rtp, 'after')
550 if isdirectory(after)
551 execute 'set rtp-='.s:escrtp(after)
556 function! s:reorg_rtp()
557 if !empty(s:first_rtp)
558 execute 'set rtp-='.s:first_rtp
559 execute 'set rtp-='.s:last_rtp
562 " &rtp is modified from outside
563 if exists('s:prtp') && s:prtp !=# &rtp
568 let s:middle = get(s:, 'middle', &rtp)
569 let rtps = map(s:loaded_names(), 's:rtp(g:plugs[v:val])')
570 let afters = filter(map(copy(rtps), 'globpath(v:val, "after")'), '!empty(v:val)')
571 let rtp = join(map(rtps, 'escape(v:val, ",")'), ',')
573 \ . join(map(afters, 'escape(v:val, ",")'), ',')
574 let &rtp = substitute(substitute(rtp, ',,*', ',', 'g'), '^,\|,$', '', 'g')
577 if !empty(s:first_rtp)
578 execute 'set rtp^='.s:first_rtp
579 execute 'set rtp+='.s:last_rtp
583 function! s:doautocmd(...)
584 if exists('#'.join(a:000, '#'))
585 execute 'doautocmd' ((v:version > 703 || has('patch442')) ? '<nomodeline>' : '') join(a:000)
589 function! s:dobufread(names)
591 let path = s:rtp(g:plugs[name])
592 for dir in ['ftdetect', 'ftplugin', 'after/ftdetect', 'after/ftplugin']
593 if len(finddir(dir, path))
594 if exists('#BufRead')
603 function! plug#load(...)
605 return s:err('Argument missing: plugin name(s) required')
607 if !exists('g:plugs')
608 return s:err('plug#begin was not called')
610 let names = a:0 == 1 && type(a:1) == s:TYPE.list ? a:1 : a:000
611 let unknowns = filter(copy(names), '!has_key(g:plugs, v:val)')
613 let s = len(unknowns) > 1 ? 's' : ''
614 return s:err(printf('Unknown plugin%s: %s', s, join(unknowns, ', ')))
616 let unloaded = filter(copy(names), '!get(s:loaded, v:val, 0)')
619 call s:lod([name], ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin'])
621 call s:dobufread(unloaded)
627 function! s:remove_triggers(name)
628 if !has_key(s:triggers, a:name)
631 for cmd in s:triggers[a:name].cmd
632 execute 'silent! delc' cmd
634 for map in s:triggers[a:name].map
635 execute 'silent! unmap' map
636 execute 'silent! iunmap' map
638 call remove(s:triggers, a:name)
641 function! s:lod(names, types, ...)
643 call s:remove_triggers(name)
644 let s:loaded[name] = 1
649 let rtp = s:rtp(g:plugs[name])
651 call s:source(rtp, dir.'/**/*.vim')
654 if !s:source(rtp, a:1) && !empty(s:glob(rtp, a:2))
655 execute 'runtime' a:1
657 call s:source(rtp, a:2)
659 call s:doautocmd('User', name)
663 function! s:lod_ft(pat, names)
664 let syn = 'syntax/'.a:pat.'.vim'
665 call s:lod(a:names, ['plugin', 'after/plugin'], syn, 'after/'.syn)
666 execute 'autocmd! PlugLOD FileType' a:pat
667 call s:doautocmd('filetypeplugin', 'FileType')
668 call s:doautocmd('filetypeindent', 'FileType')
671 function! s:lod_cmd(cmd, bang, l1, l2, args, names)
672 call s:lod(a:names, ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin'])
673 call s:dobufread(a:names)
674 execute printf('%s%s%s %s', (a:l1 == a:l2 ? '' : (a:l1.','.a:l2)), a:cmd, a:bang, a:args)
677 function! s:lod_map(map, names, with_prefix, prefix)
678 call s:lod(a:names, ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin'])
679 call s:dobufread(a:names)
686 let extra .= nr2char(c)
690 let prefix = v:count ? v:count : ''
691 let prefix .= '"'.v:register.a:prefix
694 let prefix = "\<esc>" . prefix
696 let prefix .= v:operator
698 call feedkeys(prefix, 'n')
700 call feedkeys(substitute(a:map, '^<Plug>', "\<Plug>", '') . extra)
703 function! plug#(repo, ...)
705 return s:err('Invalid number of arguments (1..2)')
709 let repo = s:trim(a:repo)
710 let opts = a:0 == 1 ? s:parse_options(a:1) : s:base_spec
711 let name = get(opts, 'as', s:plug_fnamemodify(repo, ':t:s?\.git$??'))
712 let spec = extend(s:infer_properties(name, repo), opts)
713 if !has_key(g:plugs, name)
714 call add(g:plugs_order, name)
716 let g:plugs[name] = spec
717 let s:loaded[name] = get(s:loaded, name, 0)
719 return s:err(repo . ' ' . v:exception)
723 function! s:parse_options(arg)
724 let opts = copy(s:base_spec)
725 let type = type(a:arg)
726 let opt_errfmt = 'Invalid argument for "%s" option of :Plug (expected: %s)'
727 if type == s:TYPE.string
729 throw printf(opt_errfmt, 'tag', 'string')
732 elseif type == s:TYPE.dict
733 for opt in ['branch', 'tag', 'commit', 'rtp', 'dir', 'as']
734 if has_key(a:arg, opt)
735 \ && (type(a:arg[opt]) != s:TYPE.string || empty(a:arg[opt]))
736 throw printf(opt_errfmt, opt, 'string')
739 for opt in ['on', 'for']
740 if has_key(a:arg, opt)
741 \ && type(a:arg[opt]) != s:TYPE.list
742 \ && (type(a:arg[opt]) != s:TYPE.string || empty(a:arg[opt]))
743 throw printf(opt_errfmt, opt, 'string or list')
746 if has_key(a:arg, 'do')
747 \ && type(a:arg.do) != s:TYPE.funcref
748 \ && (type(a:arg.do) != s:TYPE.string || empty(a:arg.do))
749 throw printf(opt_errfmt, 'do', 'string or funcref')
751 call extend(opts, a:arg)
752 if has_key(opts, 'dir')
753 let opts.dir = s:dirpath(s:plug_expand(opts.dir))
756 throw 'Invalid argument type (expected: string or dictionary)'
761 function! s:infer_properties(name, repo)
763 if s:is_local_plug(repo)
764 return { 'dir': s:dirpath(s:plug_expand(repo)) }
770 throw printf('Invalid argument: %s (implicit `vim-scripts'' expansion is deprecated)', repo)
772 let fmt = get(g:, 'plug_url_format', 'https://git::@github.com/%s.git')
773 let uri = printf(fmt, repo)
775 return { 'dir': s:dirpath(g:plug_home.'/'.a:name), 'uri': uri }
779 function! s:install(force, names)
780 call s:update_impl(0, a:force, a:names)
783 function! s:update(force, names)
784 call s:update_impl(1, a:force, a:names)
787 function! plug#helptags()
788 if !exists('g:plugs')
789 return s:err('plug#begin was not called')
791 for spec in values(g:plugs)
792 let docd = join([s:rtp(spec), 'doc'], '/')
794 silent! execute 'helptags' s:esc(docd)
802 syntax region plug1 start=/\%1l/ end=/\%2l/ contains=plugNumber
803 syntax region plug2 start=/\%2l/ end=/\%3l/ contains=plugBracket,plugX
804 syn match plugNumber /[0-9]\+[0-9.]*/ contained
805 syn match plugBracket /[[\]]/ contained
806 syn match plugX /x/ contained
807 syn match plugDash /^-\{1}\ /
808 syn match plugPlus /^+/
809 syn match plugStar /^*/
810 syn match plugMessage /\(^- \)\@<=.*/
811 syn match plugName /\(^- \)\@<=[^ ]*:/
812 syn match plugSha /\%(: \)\@<=[0-9a-f]\{4,}$/
813 syn match plugTag /(tag: [^)]\+)/
814 syn match plugInstall /\(^+ \)\@<=[^:]*/
815 syn match plugUpdate /\(^* \)\@<=[^:]*/
816 syn match plugCommit /^ \X*[0-9a-f]\{7,9} .*/ contains=plugRelDate,plugEdge,plugTag
817 syn match plugEdge /^ \X\+$/
818 syn match plugEdge /^ \X*/ contained nextgroup=plugSha
819 syn match plugSha /[0-9a-f]\{7,9}/ contained
820 syn match plugRelDate /([^)]*)$/ contained
821 syn match plugNotLoaded /(not loaded)$/
822 syn match plugError /^x.*/
823 syn region plugDeleted start=/^\~ .*/ end=/^\ze\S/
824 syn match plugH2 /^.*:\n-\+$/
825 syn match plugH2 /^-\{2,}/
826 syn keyword Function PlugInstall PlugStatus PlugUpdate PlugClean
827 hi def link plug1 Title
828 hi def link plug2 Repeat
829 hi def link plugH2 Type
830 hi def link plugX Exception
831 hi def link plugBracket Structure
832 hi def link plugNumber Number
834 hi def link plugDash Special
835 hi def link plugPlus Constant
836 hi def link plugStar Boolean
838 hi def link plugMessage Function
839 hi def link plugName Label
840 hi def link plugInstall Function
841 hi def link plugUpdate Type
843 hi def link plugError Error
844 hi def link plugDeleted Ignore
845 hi def link plugRelDate Comment
846 hi def link plugEdge PreProc
847 hi def link plugSha Identifier
848 hi def link plugTag Constant
850 hi def link plugNotLoaded Comment
853 function! s:lpad(str, len)
854 return a:str . repeat(' ', a:len - len(a:str))
857 function! s:lines(msg)
858 return split(a:msg, "[\r\n]")
861 function! s:lastline(msg)
862 return get(s:lines(a:msg), -1, '')
865 function! s:new_window()
866 execute get(g:, 'plug_window', 'vertical topleft new')
869 function! s:plug_window_exists()
870 let buflist = tabpagebuflist(s:plug_tab)
871 return !empty(buflist) && index(buflist, s:plug_buf) >= 0
874 function! s:switch_in()
875 if !s:plug_window_exists()
879 if winbufnr(0) != s:plug_buf
880 let s:pos = [tabpagenr(), winnr(), winsaveview()]
881 execute 'normal!' s:plug_tab.'gt'
882 let winnr = bufwinnr(s:plug_buf)
883 execute winnr.'wincmd w'
884 call add(s:pos, winsaveview())
886 let s:pos = [winsaveview()]
893 function! s:switch_out(...)
894 call winrestview(s:pos[-1])
895 setlocal nomodifiable
901 execute 'normal!' s:pos[0].'gt'
902 execute s:pos[1] 'wincmd w'
903 call winrestview(s:pos[2])
907 function! s:finish_bindings()
908 nnoremap <silent> <buffer> R :call <SID>retry()<cr>
909 nnoremap <silent> <buffer> D :PlugDiff<cr>
910 nnoremap <silent> <buffer> S :PlugStatus<cr>
911 nnoremap <silent> <buffer> U :call <SID>status_update()<cr>
912 xnoremap <silent> <buffer> U :call <SID>status_update()<cr>
913 nnoremap <silent> <buffer> ]] :silent! call <SID>section('')<cr>
914 nnoremap <silent> <buffer> [[ :silent! call <SID>section('b')<cr>
917 function! s:prepare(...)
918 if empty(s:plug_getcwd())
919 throw 'Invalid current working directory. Cannot proceed.'
922 for evar in ['$GIT_DIR', '$GIT_WORK_TREE']
924 throw evar.' detected. Cannot proceed.'
930 if b:plug_preview == 1
938 nnoremap <silent> <buffer> q :call <SID>close_pane()<cr>
940 call s:finish_bindings()
942 let b:plug_preview = -1
943 let s:plug_tab = tabpagenr()
944 let s:plug_buf = winbufnr(0)
947 for k in ['<cr>', 'L', 'o', 'X', 'd', 'dd']
948 execute 'silent! unmap <buffer>' k
950 setlocal buftype=nofile bufhidden=wipe nobuflisted nolist noswapfile nowrap cursorline modifiable nospell
951 if exists('+colorcolumn')
952 setlocal colorcolumn=
955 if exists('g:syntax_on')
960 function! s:close_pane()
961 if b:plug_preview == 1
963 let b:plug_preview = -1
969 function! s:assign_name()
971 let prefix = '[Plugins]'
974 while bufexists(name)
975 let name = printf('%s (%s)', prefix, idx)
978 silent! execute 'f' fnameescape(name)
981 function! s:chsh(swap)
982 let prev = [&shell, &shellcmdflag, &shellredir]
987 if &shell =~# 'powershell\(\.exe\)\?$' || &shell =~# 'pwsh$'
988 let &shellredir = '2>&1 | Out-File -Encoding UTF8 %s'
989 elseif &shell =~# 'sh' || &shell =~# 'cmd\(\.exe\)\?$'
990 set shellredir=>%s\ 2>&1
996 function! s:bang(cmd, ...)
999 let [sh, shellcmdflag, shrd] = s:chsh(a:0)
1000 " FIXME: Escaping is incomplete. We could use shellescape with eval,
1001 " but it won't work on Windows.
1002 let cmd = a:0 ? s:with_cd(a:cmd, a:1) : a:cmd
1004 let [batchfile, cmd] = s:batchfile(cmd)
1006 let g:_plug_bang = (s:is_win && has('gui_running') ? 'silent ' : '').'!'.escape(cmd, '#!%')
1007 execute "normal! :execute g:_plug_bang\<cr>\<cr>"
1010 let [&shell, &shellcmdflag, &shellredir] = [sh, shellcmdflag, shrd]
1011 if s:is_win && filereadable(batchfile)
1012 call delete(batchfile)
1015 return v:shell_error ? 'Exit status: ' . v:shell_error : ''
1018 function! s:regress_bar()
1019 let bar = substitute(getline(2)[1:-2], '.*\zs=', 'x', '')
1020 call s:progress_bar(2, bar, len(bar))
1023 function! s:is_updated(dir)
1024 return !empty(s:system_chomp(['git', 'log', '--pretty=format:%h', 'HEAD...HEAD@{1}'], a:dir))
1027 function! s:do(pull, force, todo)
1028 for [name, spec] in items(a:todo)
1029 if !isdirectory(spec.dir)
1032 let installed = has_key(s:update.new, name)
1033 let updated = installed ? 0 :
1034 \ (a:pull && index(s:update.errors, name) < 0 && s:is_updated(spec.dir))
1035 if a:force || installed || updated
1036 execute 'cd' s:esc(spec.dir)
1037 call append(3, '- Post-update hook for '. name .' ... ')
1039 let type = type(spec.do)
1040 if type == s:TYPE.string
1041 if spec.do[0] == ':'
1042 if !get(s:loaded, name, 0)
1043 let s:loaded[name] = 1
1046 call s:load_plugin(spec)
1050 let error = v:exception
1052 if !s:plug_window_exists()
1054 throw 'Warning: vim-plug was terminated by the post-update hook of '.name
1057 let error = s:bang(spec.do)
1059 elseif type == s:TYPE.funcref
1061 call s:load_plugin(spec)
1062 let status = installed ? 'installed' : (updated ? 'updated' : 'unchanged')
1063 call spec.do({ 'name': name, 'status': status, 'force': a:force })
1065 let error = v:exception
1068 let error = 'Invalid hook type'
1071 call setline(4, empty(error) ? (getline(4) . 'OK')
1072 \ : ('x' . getline(4)[1:] . error))
1074 call add(s:update.errors, name)
1075 call s:regress_bar()
1082 function! s:hash_match(a, b)
1083 return stridx(a:a, a:b) == 0 || stridx(a:b, a:a) == 0
1086 function! s:checkout(spec)
1087 let sha = a:spec.commit
1088 let output = s:git_revision(a:spec.dir)
1089 if !empty(output) && !s:hash_match(sha, s:lines(output)[0])
1090 let credential_helper = s:git_version_requirement(2) ? '-c credential.helper= ' : ''
1091 let output = s:system(
1092 \ 'git '.credential_helper.'fetch --depth 999999 && git checkout '.plug#shellescape(sha).' --', a:spec.dir)
1097 function! s:finish(pull)
1098 let new_frozen = len(filter(keys(s:update.new), 'g:plugs[v:val].frozen'))
1100 let s = new_frozen > 1 ? 's' : ''
1101 call append(3, printf('- Installed %d frozen plugin%s', new_frozen, s))
1103 call append(3, '- Finishing ... ') | 4
1105 call plug#helptags()
1107 call setline(4, getline(4) . 'Done!')
1110 if !empty(s:update.errors)
1111 call add(msgs, "Press 'R' to retry.")
1113 if a:pull && len(s:update.new) < len(filter(getline(5, '$'),
1114 \ "v:val =~ '^- ' && v:val !~# 'Already up.to.date'"))
1115 call add(msgs, "Press 'D' to see the updated changes.")
1117 echo join(msgs, ' ')
1118 call s:finish_bindings()
1122 if empty(s:update.errors)
1126 call s:update_impl(s:update.pull, s:update.force,
1127 \ extend(copy(s:update.errors), [s:update.threads]))
1130 function! s:is_managed(name)
1131 return has_key(g:plugs[a:name], 'uri')
1134 function! s:names(...)
1135 return sort(filter(keys(g:plugs), 'stridx(v:val, a:1) == 0 && s:is_managed(v:val)'))
1138 function! s:check_ruby()
1139 silent! ruby require 'thread'; VIM::command("let g:plug_ruby = '#{RUBY_VERSION}'")
1140 if !exists('g:plug_ruby')
1142 return s:warn('echom', 'Warning: Ruby interface is broken')
1144 let ruby_version = split(g:plug_ruby, '\.')
1146 return s:version_requirement(ruby_version, [1, 8, 7])
1149 function! s:update_impl(pull, force, args) abort
1150 let sync = index(a:args, '--sync') >= 0 || has('vim_starting')
1151 let args = filter(copy(a:args), 'v:val != "--sync"')
1152 let threads = (len(args) > 0 && args[-1] =~ '^[1-9][0-9]*$') ?
1153 \ remove(args, -1) : get(g:, 'plug_threads', 16)
1155 let managed = filter(copy(g:plugs), 's:is_managed(v:key)')
1156 let todo = empty(args) ? filter(managed, '!v:val.frozen || !isdirectory(v:val.dir)') :
1157 \ filter(managed, 'index(args, v:key) >= 0')
1160 return s:warn('echo', 'No plugin to '. (a:pull ? 'update' : 'install'))
1163 if !s:is_win && s:git_version_requirement(2, 3)
1164 let s:git_terminal_prompt = exists('$GIT_TERMINAL_PROMPT') ? $GIT_TERMINAL_PROMPT : ''
1165 let $GIT_TERMINAL_PROMPT = 0
1166 for plug in values(todo)
1167 let plug.uri = substitute(plug.uri,
1168 \ '^https://git::@github\.com', 'https://github.com', '')
1172 if !isdirectory(g:plug_home)
1174 call mkdir(g:plug_home, 'p')
1176 return s:err(printf('Invalid plug directory: %s. '.
1177 \ 'Try to call plug#begin with a valid directory', g:plug_home))
1181 if has('nvim') && !exists('*jobwait') && threads > 1
1182 call s:warn('echom', '[vim-plug] Update Neovim for parallel installer')
1185 let use_job = s:nvim || s:vim8
1186 let python = (has('python') || has('python3')) && !use_job
1187 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()
1190 \ 'start': reltime(),
1192 \ 'todo': copy(todo),
1197 \ 'threads': (python || ruby || use_job) ? min([len(todo), threads]) : 1,
1203 call append(0, ['', ''])
1207 let s:clone_opt = []
1208 if get(g:, 'plug_shallow', 1)
1209 call extend(s:clone_opt, ['--depth', '1'])
1210 if s:git_version_requirement(1, 7, 10)
1211 call add(s:clone_opt, '--no-single-branch')
1215 if has('win32unix') || has('wsl')
1216 call extend(s:clone_opt, ['-c', 'core.eol=lf', '-c', 'core.autocrlf=input'])
1219 let s:submodule_opt = s:git_version_requirement(2, 8) ? ' --jobs='.threads : ''
1221 " Python version requirement (>= 2.7)
1222 if python && !has('python3') && !ruby && !use_job && s:update.threads > 1
1224 silent python import platform; print platform.python_version()
1226 let python = s:version_requirement(
1227 \ map(split(split(pyv)[0], '\.'), 'str2nr(v:val)'), [2, 6])
1230 if (python || ruby) && s:update.threads > 1
1237 call s:update_ruby()
1239 call s:update_python()
1242 let lines = getline(4, '$')
1246 let name = s:extract_name(line, '.', '')
1247 if empty(name) || !has_key(printed, name)
1248 call append('$', line)
1250 let printed[name] = 1
1251 if line[0] == 'x' && index(s:update.errors, name) < 0
1252 call add(s:update.errors, name)
1259 call s:update_finish()
1263 while use_job && sync
1272 function! s:log4(name, msg)
1273 call setline(4, printf('- %s (%s)', a:msg, a:name))
1277 function! s:update_finish()
1278 if exists('s:git_terminal_prompt')
1279 let $GIT_TERMINAL_PROMPT = s:git_terminal_prompt
1282 call append(3, '- Updating ...') | 4
1283 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))'))
1284 let [pos, _] = s:logpos(name)
1288 if has_key(spec, 'commit')
1289 call s:log4(name, 'Checking out '.spec.commit)
1290 let out = s:checkout(spec)
1291 elseif has_key(spec, 'tag')
1294 let tags = s:lines(s:system('git tag --list '.plug#shellescape(tag).' --sort -version:refname 2>&1', spec.dir))
1295 if !v:shell_error && !empty(tags)
1297 call s:log4(name, printf('Latest tag for %s -> %s', spec.tag, tag))
1301 call s:log4(name, 'Checking out '.tag)
1302 let out = s:system('git checkout -q '.plug#shellescape(tag).' -- 2>&1', spec.dir)
1304 let branch = s:git_origin_branch(spec)
1305 call s:log4(name, 'Merging origin/'.s:esc(branch))
1306 let out = s:system('git checkout -q '.plug#shellescape(branch).' -- 2>&1'
1307 \. (has_key(s:update.new, name) ? '' : ('&& git merge --ff-only '.plug#shellescape('origin/'.branch).' 2>&1')), spec.dir)
1309 if !v:shell_error && filereadable(spec.dir.'/.gitmodules') &&
1310 \ (s:update.force || has_key(s:update.new, name) || s:is_updated(spec.dir))
1311 call s:log4(name, 'Updating submodules. This may take a while.')
1312 let out .= s:bang('git submodule update --init --recursive'.s:submodule_opt.' 2>&1', spec.dir)
1314 let msg = s:format_message(v:shell_error ? 'x': '-', name, out)
1316 call add(s:update.errors, name)
1317 call s:regress_bar()
1318 silent execute pos 'd _'
1319 call append(4, msg) | 4
1321 call setline(pos, msg[0])
1327 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")'))
1329 call s:warn('echom', v:exception)
1330 call s:warn('echo', '')
1333 call s:finish(s:update.pull)
1334 call setline(1, 'Updated. Elapsed time: ' . split(reltimestr(reltime(s:update.start)))[0] . ' sec.')
1335 call s:switch_out('normal! gg')
1339 function! s:job_abort()
1340 if (!s:nvim && !s:vim8) || !exists('s:jobs')
1344 for [name, j] in items(s:jobs)
1346 silent! call jobstop(j.jobid)
1348 silent! call job_stop(j.jobid)
1351 call s:rm_rf(g:plugs[name].dir)
1357 function! s:last_non_empty_line(lines)
1358 let len = len(a:lines)
1359 for idx in range(len)
1360 let line = a:lines[len-idx-1]
1368 function! s:job_out_cb(self, data) abort
1370 let data = remove(self.lines, -1) . a:data
1371 let lines = map(split(data, "\n", 1), 'split(v:val, "\r", 1)[-1]')
1372 call extend(self.lines, lines)
1373 " To reduce the number of buffer updates
1374 let self.tick = get(self, 'tick', -1) + 1
1375 if !self.running || self.tick % len(s:jobs) == 0
1376 let bullet = self.running ? (self.new ? '+' : '*') : (self.error ? 'x' : '-')
1377 let result = self.error ? join(self.lines, "\n") : s:last_non_empty_line(self.lines)
1378 call s:log(bullet, self.name, result)
1382 function! s:job_exit_cb(self, data) abort
1383 let a:self.running = 0
1384 let a:self.error = a:data != 0
1385 call s:reap(a:self.name)
1389 function! s:job_cb(fn, job, ch, data)
1390 if !s:plug_window_exists() " plug window closed
1391 return s:job_abort()
1393 call call(a:fn, [a:job, a:data])
1396 function! s:nvim_cb(job_id, data, event) dict abort
1397 return (a:event == 'stdout' || a:event == 'stderr') ?
1398 \ s:job_cb('s:job_out_cb', self, 0, join(a:data, "\n")) :
1399 \ s:job_cb('s:job_exit_cb', self, 0, a:data)
1402 function! s:spawn(name, cmd, opts)
1403 let job = { 'name': a:name, 'running': 1, 'error': 0, 'lines': [''],
1404 \ 'new': get(a:opts, 'new', 0) }
1405 let s:jobs[a:name] = job
1408 if has_key(a:opts, 'dir')
1409 let job.cwd = a:opts.dir
1413 \ 'on_stdout': function('s:nvim_cb'),
1414 \ 'on_stderr': function('s:nvim_cb'),
1415 \ 'on_exit': function('s:nvim_cb'),
1417 let jid = s:plug_call('jobstart', argv, job)
1423 let job.lines = [jid < 0 ? argv[0].' is not executable' :
1424 \ 'Invalid arguments (or job table is full)']
1427 let cmd = join(map(copy(a:cmd), 'plug#shellescape(v:val, {"script": 0})'))
1428 if has_key(a:opts, 'dir')
1429 let cmd = s:with_cd(cmd, a:opts.dir, 0)
1431 let argv = s:is_win ? ['cmd', '/s', '/c', '"'.cmd.'"'] : ['sh', '-c', cmd]
1432 let jid = job_start(s:is_win ? join(argv, ' ') : argv, {
1433 \ 'out_cb': function('s:job_cb', ['s:job_out_cb', job]),
1434 \ 'err_cb': function('s:job_cb', ['s:job_out_cb', job]),
1435 \ 'exit_cb': function('s:job_cb', ['s:job_exit_cb', job]),
1436 \ 'err_mode': 'raw',
1439 if job_status(jid) == 'run'
1444 let job.lines = ['Failed to start job']
1447 let job.lines = s:lines(call('s:system', has_key(a:opts, 'dir') ? [a:cmd, a:opts.dir] : [a:cmd]))
1448 let job.error = v:shell_error != 0
1453 function! s:reap(name)
1454 let job = s:jobs[a:name]
1456 call add(s:update.errors, a:name)
1457 elseif get(job, 'new', 0)
1458 let s:update.new[a:name] = 1
1460 let s:update.bar .= job.error ? 'x' : '='
1462 let bullet = job.error ? 'x' : '-'
1463 let result = job.error ? join(job.lines, "\n") : s:last_non_empty_line(job.lines)
1464 call s:log(bullet, a:name, empty(result) ? 'OK' : result)
1467 call remove(s:jobs, a:name)
1472 let total = len(s:update.all)
1473 call setline(1, (s:update.pull ? 'Updating' : 'Installing').
1474 \ ' plugins ('.len(s:update.bar).'/'.total.')')
1475 call s:progress_bar(2, s:update.bar, total)
1480 function! s:logpos(name)
1482 for i in range(4, max > 4 ? max : 4)
1483 if getline(i) =~# '^[-+x*] '.a:name.':'
1484 for j in range(i + 1, max > 5 ? max : 5)
1485 if getline(j) !~ '^ '
1495 function! s:log(bullet, name, lines)
1497 let [b, e] = s:logpos(a:name)
1499 silent execute printf('%d,%d d _', b, e)
1500 if b > winheight('.')
1506 " FIXME For some reason, nomodifiable is set after :d in vim8
1508 call append(b - 1, s:format_message(a:bullet, a:name, a:lines))
1513 function! s:update_vim()
1521 let pull = s:update.pull
1522 let prog = s:progress_opt(s:nvim || s:vim8)
1523 while 1 " Without TCO, Vim stack is bound to explode
1524 if empty(s:update.todo)
1525 if empty(s:jobs) && !s:update.fin
1526 call s:update_finish()
1527 let s:update.fin = 1
1532 let name = keys(s:update.todo)[0]
1533 let spec = remove(s:update.todo, name)
1534 let new = empty(globpath(spec.dir, '.git', 1))
1536 call s:log(new ? '+' : '*', name, pull ? 'Updating ...' : 'Installing ...')
1539 let has_tag = has_key(spec, 'tag')
1541 let [error, _] = s:git_validate(spec, 0)
1544 let cmd = s:git_version_requirement(2) ? ['git', '-c', 'credential.helper=', 'fetch'] : ['git', 'fetch']
1545 if has_tag && !empty(globpath(spec.dir, '.git/shallow'))
1546 call extend(cmd, ['--depth', '99999999'])
1551 call s:spawn(name, cmd, { 'dir': spec.dir })
1553 let s:jobs[name] = { 'running': 0, 'lines': ['Already installed'], 'error': 0 }
1556 let s:jobs[name] = { 'running': 0, 'lines': s:lines(error), 'error': 1 }
1559 let cmd = ['git', 'clone']
1561 call extend(cmd, s:clone_opt)
1566 call s:spawn(name, extend(cmd, [spec.uri, s:trim(spec.dir)]), { 'new': 1 })
1569 if !s:jobs[name].running
1572 if len(s:jobs) >= s:update.threads
1578 function! s:update_python()
1579 let py_exe = has('python') ? 'python' : 'python3'
1580 execute py_exe "<< EOF"
1587 import Queue as queue
1594 import threading as thr
1599 G_NVIM = vim.eval("has('nvim')") == '1'
1600 G_PULL = vim.eval('s:update.pull') == '1'
1601 G_RETRIES = int(vim.eval('get(g:, "plug_retries", 2)')) + 1
1602 G_TIMEOUT = int(vim.eval('get(g:, "plug_timeout", 60)'))
1603 G_CLONE_OPT = ' '.join(vim.eval('s:clone_opt'))
1604 G_PROGRESS = vim.eval('s:progress_opt(1)')
1605 G_LOG_PROB = 1.0 / int(vim.eval('s:update.threads'))
1606 G_STOP = thr.Event()
1607 G_IS_WIN = vim.eval('s:is_win') == '1'
1609 class PlugError(Exception):
1610 def __init__(self, msg):
1612 class CmdTimedOut(PlugError):
1614 class CmdFailed(PlugError):
1616 class InvalidURI(PlugError):
1618 class Action(object):
1619 INSTALL, UPDATE, ERROR, DONE = ['+', '*', 'x', '-']
1621 class Buffer(object):
1622 def __init__(self, lock, num_plugs, is_pull):
1624 self.event = 'Updating' if is_pull else 'Installing'
1626 self.maxy = int(vim.eval('winheight(".")'))
1627 self.num_plugs = num_plugs
1629 def __where(self, name):
1630 """ Find first line with name in current buffer. Return line num. """
1631 found, lnum = False, 0
1632 matcher = re.compile('^[-+x*] {0}:'.format(name))
1633 for line in vim.current.buffer:
1634 if matcher.search(line) is not None:
1644 curbuf = vim.current.buffer
1645 curbuf[0] = self.event + ' plugins ({0}/{1})'.format(len(self.bar), self.num_plugs)
1647 num_spaces = self.num_plugs - len(self.bar)
1648 curbuf[1] = '[{0}{1}]'.format(self.bar, num_spaces * ' ')
1651 vim.command('normal! 2G')
1652 vim.command('redraw')
1654 def write(self, action, name, lines):
1655 first, rest = lines[0], lines[1:]
1656 msg = ['{0} {1}{2}{3}'.format(action, name, ': ' if first else '', first)]
1657 msg.extend([' ' + line for line in rest])
1660 if action == Action.ERROR:
1662 vim.command("call add(s:update.errors, '{0}')".format(name))
1663 elif action == Action.DONE:
1666 curbuf = vim.current.buffer
1667 lnum = self.__where(name)
1668 if lnum != -1: # Found matching line num
1670 if lnum > self.maxy and action in set([Action.INSTALL, Action.UPDATE]):
1674 curbuf.append(msg, lnum)
1680 class Command(object):
1681 CD = 'cd /d' if G_IS_WIN else 'cd'
1683 def __init__(self, cmd, cmd_dir=None, timeout=60, cb=None, clean=None):
1686 self.cmd = '{0} {1} && {2}'.format(Command.CD, cmd_dir, self.cmd)
1687 self.timeout = timeout
1688 self.callback = cb if cb else (lambda msg: None)
1689 self.clean = clean if clean else (lambda: None)
1694 """ Returns true only if command still running. """
1695 return self.proc and self.proc.poll() is None
1697 def execute(self, ntries=3):
1698 """ Execute the command with ntries if CmdTimedOut.
1699 Returns the output of the command if no Exception.
1701 attempt, finished, limit = 0, False, self.timeout
1706 result = self.try_command()
1710 if attempt != ntries:
1712 self.timeout += limit
1716 def notify_retry(self):
1717 """ Retry required for command, notify user. """
1718 for count in range(3, 0, -1):
1720 raise KeyboardInterrupt
1721 msg = 'Timeout. Will retry in {0} second{1} ...'.format(
1722 count, 's' if count != 1 else '')
1723 self.callback([msg])
1725 self.callback(['Retrying ...'])
1727 def try_command(self):
1728 """ Execute a cmd & poll for callback. Returns list of output.
1729 Raises CmdFailed -> return code for Popen isn't 0
1730 Raises CmdTimedOut -> command exceeded timeout without new output
1735 tfile = tempfile.NamedTemporaryFile(mode='w+b')
1736 preexec_fn = not G_IS_WIN and os.setsid or None
1737 self.proc = subprocess.Popen(self.cmd, stdout=tfile,
1738 stderr=subprocess.STDOUT,
1739 stdin=subprocess.PIPE, shell=True,
1740 preexec_fn=preexec_fn)
1741 thrd = thr.Thread(target=(lambda proc: proc.wait()), args=(self.proc,))
1744 thread_not_started = True
1745 while thread_not_started:
1748 thread_not_started = False
1749 except RuntimeError:
1754 raise KeyboardInterrupt
1756 if first_line or random.random() < G_LOG_PROB:
1758 line = '' if G_IS_WIN else nonblock_read(tfile.name)
1760 self.callback([line])
1762 time_diff = time.time() - os.path.getmtime(tfile.name)
1763 if time_diff > self.timeout:
1764 raise CmdTimedOut(['Timeout!'])
1769 result = [line.decode('utf-8', 'replace').rstrip() for line in tfile]
1771 if self.proc.returncode != 0:
1772 raise CmdFailed([''] + result)
1779 def terminate(self):
1780 """ Terminate process and cleanup. """
1783 os.kill(self.proc.pid, signal.SIGINT)
1785 os.killpg(self.proc.pid, signal.SIGTERM)
1788 class Plugin(object):
1789 def __init__(self, name, args, buf_q, lock):
1794 self.tag = args.get('tag', 0)
1798 if os.path.exists(self.args['dir']):
1803 thread_vim_command("let s:update.new['{0}'] = 1".format(self.name))
1804 except PlugError as exc:
1805 self.write(Action.ERROR, self.name, exc.msg)
1806 except KeyboardInterrupt:
1808 self.write(Action.ERROR, self.name, ['Interrupted!'])
1810 # Any exception except those above print stack trace
1811 msg = 'Trace:\n{0}'.format(traceback.format_exc().rstrip())
1812 self.write(Action.ERROR, self.name, msg.split('\n'))
1816 target = self.args['dir']
1817 if target[-1] == '\\':
1818 target = target[0:-1]
1823 shutil.rmtree(target)
1828 self.write(Action.INSTALL, self.name, ['Installing ...'])
1829 callback = functools.partial(self.write, Action.INSTALL, self.name)
1830 cmd = 'git clone {0} {1} {2} {3} 2>&1'.format(
1831 '' if self.tag else G_CLONE_OPT, G_PROGRESS, self.args['uri'],
1833 com = Command(cmd, None, G_TIMEOUT, callback, clean(target))
1834 result = com.execute(G_RETRIES)
1835 self.write(Action.DONE, self.name, result[-1:])
1838 cmd = 'git rev-parse --abbrev-ref HEAD 2>&1 && git config -f .git/config remote.origin.url'
1839 command = Command(cmd, self.args['dir'], G_TIMEOUT,)
1840 result = command.execute(G_RETRIES)
1844 actual_uri = self.repo_uri()
1845 expect_uri = self.args['uri']
1846 regex = re.compile(r'^(?:\w+://)?(?:[^@/]*@)?([^:/]*(?::[0-9]*)?)[:/](.*?)(?:\.git)?/?$')
1847 ma = regex.match(actual_uri)
1848 mb = regex.match(expect_uri)
1849 if ma is None or mb is None or ma.groups() != mb.groups():
1851 'Invalid URI: {0}'.format(actual_uri),
1852 'Expected {0}'.format(expect_uri),
1853 'PlugClean required.']
1854 raise InvalidURI(msg)
1857 self.write(Action.UPDATE, self.name, ['Updating ...'])
1858 callback = functools.partial(self.write, Action.UPDATE, self.name)
1859 fetch_opt = '--depth 99999999' if self.tag and os.path.isfile(os.path.join(self.args['dir'], '.git/shallow')) else ''
1860 cmd = 'git fetch {0} {1} 2>&1'.format(fetch_opt, G_PROGRESS)
1861 com = Command(cmd, self.args['dir'], G_TIMEOUT, callback)
1862 result = com.execute(G_RETRIES)
1863 self.write(Action.DONE, self.name, result[-1:])
1865 self.write(Action.DONE, self.name, ['Already installed'])
1867 def write(self, action, name, msg):
1868 self.buf_q.put((action, name, msg))
1870 class PlugThread(thr.Thread):
1871 def __init__(self, tname, args):
1872 super(PlugThread, self).__init__()
1877 thr.current_thread().name = self.tname
1878 buf_q, work_q, lock = self.args
1881 while not G_STOP.is_set():
1882 name, args = work_q.get_nowait()
1883 plug = Plugin(name, args, buf_q, lock)
1889 class RefreshThread(thr.Thread):
1890 def __init__(self, lock):
1891 super(RefreshThread, self).__init__()
1898 thread_vim_command('noautocmd normal! a')
1902 self.running = False
1905 def thread_vim_command(cmd):
1906 vim.session.threadsafe_call(lambda: vim.command(cmd))
1908 def thread_vim_command(cmd):
1912 return '"' + name.replace('"', '\"') + '"'
1914 def nonblock_read(fname):
1915 """ Read a file with nonblock flag. Return the last line. """
1916 fread = os.open(fname, os.O_RDONLY | os.O_NONBLOCK)
1917 buf = os.read(fread, 100000).decode('utf-8', 'replace')
1920 line = buf.rstrip('\r\n')
1921 left = max(line.rfind('\r'), line.rfind('\n'))
1929 thr.current_thread().name = 'main'
1930 nthreads = int(vim.eval('s:update.threads'))
1931 plugs = vim.eval('s:update.todo')
1932 mac_gui = vim.eval('s:mac_gui') == '1'
1935 buf = Buffer(lock, len(plugs), G_PULL)
1936 buf_q, work_q = queue.Queue(), queue.Queue()
1937 for work in plugs.items():
1940 start_cnt = thr.active_count()
1941 for num in range(nthreads):
1942 tname = 'PlugT-{0:02}'.format(num)
1943 thread = PlugThread(tname, (buf_q, work_q, lock))
1946 rthread = RefreshThread(lock)
1949 while not buf_q.empty() or thr.active_count() != start_cnt:
1951 action, name, msg = buf_q.get(True, 0.25)
1952 buf.write(action, name, ['OK'] if not msg else msg)
1956 except KeyboardInterrupt:
1967 function! s:update_ruby()
1970 SEP = ["\r", "\n", nil]
1974 char = readchar rescue return
1975 if SEP.include? char.chr
1984 end unless defined?(PlugStream)
1987 %["#{arg.gsub('"', '\"')}"]
1992 if /mswin|mingw|bccwin/ =~ RUBY_PLATFORM
1993 pids.each { |pid| Process.kill 'INT', pid.to_i rescue nil }
1995 unless `which pgrep 2> /dev/null`.empty?
1997 until children.empty?
1998 children = children.map { |pid|
1999 `pgrep -P #{pid}`.lines.map { |l| l.chomp }
2004 pids.each { |pid| Process.kill 'TERM', pid.to_i rescue nil }
2008 def compare_git_uri a, b
2009 regex = %r{^(?:\w+://)?(?:[^@/]*@)?([^:/]*(?::[0-9]*)?)[:/](.*?)(?:\.git)?/?$}
2010 regex.match(a).to_a.drop(1) == regex.match(b).to_a.drop(1)
2017 iswin = VIM::evaluate('s:is_win').to_i == 1
2018 pull = VIM::evaluate('s:update.pull').to_i == 1
2019 base = VIM::evaluate('g:plug_home')
2020 all = VIM::evaluate('s:update.todo')
2021 limit = VIM::evaluate('get(g:, "plug_timeout", 60)')
2022 tries = VIM::evaluate('get(g:, "plug_retries", 2)') + 1
2023 nthr = VIM::evaluate('s:update.threads').to_i
2024 maxy = VIM::evaluate('winheight(".")').to_i
2025 vim7 = VIM::evaluate('v:version').to_i <= 703 && RUBY_PLATFORM =~ /darwin/
2026 cd = iswin ? 'cd /d' : 'cd'
2027 tot = VIM::evaluate('len(s:update.todo)') || 0
2029 skip = 'Already installed'
2031 take1 = proc { mtx.synchronize { running && all.shift } }
2034 $curbuf[1] = "#{pull ? 'Updating' : 'Installing'} plugins (#{cnt}/#{tot})"
2035 $curbuf[2] = '[' + bar.ljust(tot) + ']'
2036 VIM::command('normal! 2G')
2037 VIM::command('redraw')
2039 where = proc { |name| (1..($curbuf.length)).find { |l| $curbuf[l] =~ /^[-+x*] #{name}:/ } }
2040 log = proc { |name, result, type|
2042 ing = ![true, false].include?(type)
2043 bar += type ? '=' : 'x' unless ing
2045 when :install then '+' when :update then '*'
2046 when true, nil then '-' else
2047 VIM::command("call add(s:update.errors, '#{name}')")
2051 if type || type.nil?
2052 ["#{b} #{name}: #{result.lines.to_a.last || 'OK'}"]
2053 elsif result =~ /^Interrupted|^Timeout/
2054 ["#{b} #{name}: #{result}"]
2056 ["#{b} #{name}"] + result.lines.map { |l| " " << l }
2058 if lnum = where.call(name)
2060 lnum = 4 if ing && lnum > maxy
2062 result.each_with_index do |line, offset|
2063 $curbuf.append((lnum || 4) - 1 + offset, line.gsub(/\e\[./, '').chomp)
2068 bt = proc { |cmd, name, type, cleanup|
2076 Timeout::timeout(timeout) do
2077 tmp = VIM::evaluate('tempname()')
2078 system("(#{cmd}) > #{tmp}")
2079 data = File.read(tmp).chomp
2080 File.unlink tmp rescue nil
2083 fd = IO.popen(cmd).extend(PlugStream)
2085 log_prob = 1.0 / nthr
2086 while line = Timeout::timeout(timeout) { fd.get_line }
2088 log.call name, line.chomp, type if name && (first_line || rand < log_prob)
2093 [$? == 0, data.chomp]
2094 rescue Timeout::Error, Interrupt => e
2095 if fd && !fd.closed?
2099 cleanup.call if cleanup
2100 if e.is_a?(Timeout::Error) && tried < tries
2101 3.downto(1) do |countdown|
2102 s = countdown > 1 ? 's' : ''
2103 log.call name, "Timeout. Will retry in #{countdown} second#{s} ...", type
2106 log.call name, 'Retrying ...', type
2109 [false, e.is_a?(Interrupt) ? "Interrupted!" : "Timeout!"]
2112 main = Thread.current
2114 watcher = Thread.new {
2116 while VIM::evaluate('getchar(1)')
2120 require 'io/console' # >= Ruby 1.9
2121 nil until IO.console.getch == 3.chr
2125 threads.each { |t| t.raise Interrupt } unless vim7
2127 threads.each { |t| t.join rescue nil }
2130 refresh = Thread.new {
2133 break unless running
2134 VIM::command('noautocmd normal! a')
2138 } if VIM::evaluate('s:mac_gui') == 1
2140 clone_opt = VIM::evaluate('s:clone_opt').join(' ')
2141 progress = VIM::evaluate('s:progress_opt(1)')
2144 threads << Thread.new {
2145 while pair = take1.call
2147 dir, uri, tag = pair.last.values_at *%w[dir uri tag]
2148 exists = File.directory? dir
2151 chdir = "#{cd} #{iswin ? dir : esc(dir)}"
2152 ret, data = bt.call "#{chdir} && git rev-parse --abbrev-ref HEAD 2>&1 && git config -f .git/config remote.origin.url", nil, nil, nil
2153 current_uri = data.lines.to_a.last
2155 if data =~ /^Interrupted|^Timeout/
2158 [false, [data.chomp, "PlugClean required."].join($/)]
2160 elsif !compare_git_uri(current_uri, uri)
2161 [false, ["Invalid URI: #{current_uri}",
2163 "PlugClean required."].join($/)]
2166 log.call name, 'Updating ...', :update
2167 fetch_opt = (tag && File.exist?(File.join(dir, '.git/shallow'))) ? '--depth 99999999' : ''
2168 bt.call "#{chdir} && git fetch #{fetch_opt} #{progress} 2>&1", name, :update, nil
2174 d = esc dir.sub(%r{[\\/]+$}, '')
2175 log.call name, 'Installing ...', :install
2176 bt.call "git clone #{clone_opt unless tag} #{progress} #{uri} #{d} 2>&1", name, :install, proc {
2180 mtx.synchronize { VIM::command("let s:update.new['#{name}'] = 1") } if !exists && ok
2181 log.call name, result, ok
2186 threads.each { |t| t.join rescue nil }
2188 refresh.kill if refresh
2193 function! s:shellesc_cmd(arg, script)
2194 let escaped = substitute('"'.a:arg.'"', '[&|<>()@^!"]', '^&', 'g')
2195 return substitute(escaped, '%', (a:script ? '%' : '^') . '&', 'g')
2198 function! s:shellesc_ps1(arg)
2199 return "'".substitute(escape(a:arg, '\"'), "'", "''", 'g')."'"
2202 function! s:shellesc_sh(arg)
2203 return "'".substitute(a:arg, "'", "'\\\\''", 'g')."'"
2206 " Escape the shell argument based on the shell.
2207 " Vim and Neovim's shellescape() are insufficient.
2208 " 1. shellslash determines whether to use single/double quotes.
2209 " Double-quote escaping is fragile for cmd.exe.
2210 " 2. It does not work for powershell.
2211 " 3. It does not work for *sh shells if the command is executed
2212 " via cmd.exe (ie. cmd.exe /c sh -c command command_args)
2213 " 4. It does not support batchfile syntax.
2215 " Accepts an optional dictionary with the following keys:
2216 " - shell: same as Vim/Neovim 'shell' option.
2217 " If unset, fallback to 'cmd.exe' on Windows or 'sh'.
2218 " - script: If truthy and shell is cmd.exe, escape for batchfile syntax.
2219 function! plug#shellescape(arg, ...)
2220 if a:arg =~# '^[A-Za-z0-9_/:.-]\+$'
2223 let opts = a:0 > 0 && type(a:1) == s:TYPE.dict ? a:1 : {}
2224 let shell = get(opts, 'shell', s:is_win ? 'cmd.exe' : 'sh')
2225 let script = get(opts, 'script', 1)
2226 if shell =~# 'cmd\(\.exe\)\?$'
2227 return s:shellesc_cmd(a:arg, script)
2228 elseif shell =~# 'powershell\(\.exe\)\?$' || shell =~# 'pwsh$'
2229 return s:shellesc_ps1(a:arg)
2231 return s:shellesc_sh(a:arg)
2234 function! s:glob_dir(path)
2235 return map(filter(s:glob(a:path, '**'), 'isdirectory(v:val)'), 's:dirpath(v:val)')
2238 function! s:progress_bar(line, bar, total)
2239 call setline(a:line, '[' . s:lpad(a:bar, a:total) . ']')
2242 function! s:compare_git_uri(a, b)
2243 " See `git help clone'
2244 " https:// [user@] github.com[:port] / junegunn/vim-plug [.git]
2245 " [git@] github.com[:port] : junegunn/vim-plug [.git]
2246 " file:// / junegunn/vim-plug [/]
2247 " / junegunn/vim-plug [/]
2248 let pat = '^\%(\w\+://\)\='.'\%([^@/]*@\)\='.'\([^:/]*\%(:[0-9]*\)\=\)'.'[:/]'.'\(.\{-}\)'.'\%(\.git\)\=/\?$'
2249 let ma = matchlist(a:a, pat)
2250 let mb = matchlist(a:b, pat)
2251 return ma[1:2] ==# mb[1:2]
2254 function! s:format_message(bullet, name, message)
2256 return [printf('%s %s: %s', a:bullet, a:name, s:lastline(a:message))]
2258 let lines = map(s:lines(a:message), '" ".v:val')
2259 return extend([printf('x %s:', a:name)], lines)
2263 function! s:with_cd(cmd, dir, ...)
2264 let script = a:0 > 0 ? a:1 : 1
2265 return printf('cd%s %s && %s', s:is_win ? ' /d' : '', plug#shellescape(a:dir, {'script': script}), a:cmd)
2268 function! s:system(cmd, ...)
2271 let [sh, shellcmdflag, shrd] = s:chsh(1)
2272 if type(a:cmd) == s:TYPE.list
2273 " Neovim's system() supports list argument to bypass the shell
2274 " but it cannot set the working directory for the command.
2275 " Assume that the command does not rely on the shell.
2276 if has('nvim') && a:0 == 0
2277 return system(a:cmd)
2279 let cmd = join(map(copy(a:cmd), 'plug#shellescape(v:val, {"shell": &shell, "script": 0})'))
2280 if &shell =~# 'powershell\(\.exe\)\?$'
2281 let cmd = '& ' . cmd
2287 let cmd = s:with_cd(cmd, a:1, type(a:cmd) != s:TYPE.list)
2289 if s:is_win && type(a:cmd) != s:TYPE.list
2290 let [batchfile, cmd] = s:batchfile(cmd)
2294 let [&shell, &shellcmdflag, &shellredir] = [sh, shellcmdflag, shrd]
2295 if s:is_win && filereadable(batchfile)
2296 call delete(batchfile)
2301 function! s:system_chomp(...)
2302 let ret = call('s:system', a:000)
2303 return v:shell_error ? '' : substitute(ret, '\n$', '', '')
2306 function! s:git_validate(spec, check_branch)
2308 if isdirectory(a:spec.dir)
2309 let result = [s:git_local_branch(a:spec.dir), s:git_origin_url(a:spec.dir)]
2310 let remote = result[-1]
2312 let err = join([remote, 'PlugClean required.'], "\n")
2313 elseif !s:compare_git_uri(remote, a:spec.uri)
2314 let err = join(['Invalid URI: '.remote,
2315 \ 'Expected: '.a:spec.uri,
2316 \ 'PlugClean required.'], "\n")
2317 elseif a:check_branch && has_key(a:spec, 'commit')
2318 let sha = s:git_revision(a:spec.dir)
2320 let err = join(add(result, 'PlugClean required.'), "\n")
2321 elseif !s:hash_match(sha, a:spec.commit)
2322 let err = join([printf('Invalid HEAD (expected: %s, actual: %s)',
2323 \ a:spec.commit[:6], sha[:6]),
2324 \ 'PlugUpdate required.'], "\n")
2326 elseif a:check_branch
2327 let current_branch = result[0]
2329 let origin_branch = s:git_origin_branch(a:spec)
2330 if has_key(a:spec, 'tag')
2331 let tag = s:system_chomp('git describe --exact-match --tags HEAD 2>&1', a:spec.dir)
2332 if a:spec.tag !=# tag && a:spec.tag !~ '\*'
2333 let err = printf('Invalid tag: %s (expected: %s). Try PlugUpdate.',
2334 \ (empty(tag) ? 'N/A' : tag), a:spec.tag)
2337 elseif origin_branch !=# current_branch
2338 let err = printf('Invalid branch: %s (expected: %s). Try PlugUpdate.',
2339 \ current_branch, origin_branch)
2342 let [ahead, behind] = split(s:lastline(s:system([
2343 \ 'git', 'rev-list', '--count', '--left-right',
2344 \ printf('HEAD...origin/%s', origin_branch)
2345 \ ], a:spec.dir)), '\t')
2346 if !v:shell_error && ahead
2348 " Only mention PlugClean if diverged, otherwise it's likely to be
2349 " pushable (and probably not that messed up).
2351 \ "Diverged from origin/%s (%d commit(s) ahead and %d commit(s) behind!\n"
2352 \ .'Backup local changes and run PlugClean and PlugUpdate to reinstall it.', origin_branch, ahead, behind)
2354 let err = printf("Ahead of origin/%s by %d commit(s).\n"
2355 \ .'Cannot update until local changes are pushed.',
2356 \ origin_branch, ahead)
2362 let err = 'Not found'
2364 return [err, err =~# 'PlugClean']
2367 function! s:rm_rf(dir)
2368 if isdirectory(a:dir)
2369 return s:system(s:is_win
2370 \ ? 'rmdir /S /Q '.plug#shellescape(a:dir)
2371 \ : ['rm', '-rf', a:dir])
2375 function! s:clean(force)
2377 call append(0, 'Searching for invalid plugins in '.g:plug_home)
2380 " List of valid directories
2383 let [cnt, total] = [0, len(g:plugs)]
2384 for [name, spec] in items(g:plugs)
2385 if !s:is_managed(name)
2386 call add(dirs, spec.dir)
2388 let [err, clean] = s:git_validate(spec, 1)
2390 let errs[spec.dir] = s:lines(err)[0]
2392 call add(dirs, spec.dir)
2396 call s:progress_bar(2, repeat('=', cnt), total)
2403 let allowed[s:dirpath(s:plug_fnamemodify(dir, ':h:h'))] = 1
2404 let allowed[dir] = 1
2405 for child in s:glob_dir(dir)
2406 let allowed[child] = 1
2411 let found = sort(s:glob_dir(g:plug_home))
2413 let f = remove(found, 0)
2414 if !has_key(allowed, f) && isdirectory(f)
2416 call append(line('$'), '- ' . f)
2418 call append(line('$'), ' ' . errs[f])
2420 let found = filter(found, 'stridx(v:val, f) != 0')
2427 call append(line('$'), 'Already clean.')
2429 let s:clean_count = 0
2430 call append(3, ['Directories to delete:', ''])
2432 if a:force || s:ask_no_interrupt('Delete all directories?')
2433 call s:delete([6, line('$')], 1)
2435 call setline(4, 'Cancelled.')
2436 nnoremap <silent> <buffer> d :set opfunc=<sid>delete_op<cr>g@
2437 nmap <silent> <buffer> dd d_
2438 xnoremap <silent> <buffer> d :<c-u>call <sid>delete_op(visualmode(), 1)<cr>
2439 echo 'Delete the lines (d{motion}) to delete the corresponding directories'
2443 setlocal nomodifiable
2446 function! s:delete_op(type, ...)
2447 call s:delete(a:0 ? [line("'<"), line("'>")] : [line("'["), line("']")], 0)
2450 function! s:delete(range, force)
2451 let [l1, l2] = a:range
2455 let line = getline(l1)
2456 if line =~ '^- ' && isdirectory(line[2:])
2459 let answer = force ? 1 : s:ask('Delete '.line[2:].'?', 1)
2460 let force = force || answer > 1
2462 let err = s:rm_rf(line[2:])
2465 call setline(l1, '~'.line[1:])
2466 let s:clean_count += 1
2469 call append(l1 - 1, s:format_message('x', line[1:], err))
2470 let l2 += len(s:lines(err))
2473 let msg = printf('Removed %d directories.', s:clean_count)
2475 let msg .= printf(' Failed to remove %d directories.', err_count)
2477 call setline(4, msg)
2478 setlocal nomodifiable
2485 function! s:upgrade()
2486 echo 'Downloading the latest version of vim-plug'
2488 let tmp = s:plug_tempname()
2489 let new = tmp . '/plug.vim'
2492 let out = s:system(['git', 'clone', '--depth', '1', s:plug_src, tmp])
2494 return s:err('Error upgrading vim-plug: '. out)
2497 if readfile(s:me) ==# readfile(new)
2498 echo 'vim-plug is already up-to-date'
2501 call rename(s:me, s:me . '.old')
2502 call rename(new, s:me)
2504 echo 'vim-plug has been upgraded'
2508 silent! call s:rm_rf(tmp)
2512 function! s:upgrade_specs()
2513 for spec in values(g:plugs)
2514 let spec.frozen = get(spec, 'frozen', 0)
2518 function! s:status()
2520 call append(0, 'Checking plugins')
2525 let [cnt, total] = [0, len(g:plugs)]
2526 for [name, spec] in items(g:plugs)
2527 let is_dir = isdirectory(spec.dir)
2528 if has_key(spec, 'uri')
2530 let [err, _] = s:git_validate(spec, 1)
2531 let [valid, msg] = [empty(err), empty(err) ? 'OK' : err]
2533 let [valid, msg] = [0, 'Not found. Try PlugInstall.']
2537 let [valid, msg] = [1, 'OK']
2539 let [valid, msg] = [0, 'Not found.']
2544 " `s:loaded` entry can be missing if PlugUpgraded
2545 if is_dir && get(s:loaded, name, -1) == 0
2547 let msg .= ' (not loaded)'
2549 call s:progress_bar(2, repeat('=', cnt), total)
2550 call append(3, s:format_message(valid ? '-' : 'x', name, msg))
2554 call setline(1, 'Finished. '.ecnt.' error(s).')
2556 setlocal nomodifiable
2558 echo "Press 'L' on each line to load plugin, or 'U' to update"
2559 nnoremap <silent> <buffer> L :call <SID>status_load(line('.'))<cr>
2560 xnoremap <silent> <buffer> L :call <SID>status_load(line('.'))<cr>
2564 function! s:extract_name(str, prefix, suffix)
2565 return matchstr(a:str, '^'.a:prefix.' \zs[^:]\+\ze:.*'.a:suffix.'$')
2568 function! s:status_load(lnum)
2569 let line = getline(a:lnum)
2570 let name = s:extract_name(line, '-', '(not loaded)')
2572 call plug#load(name)
2574 call setline(a:lnum, substitute(line, ' (not loaded)$', '', ''))
2575 setlocal nomodifiable
2579 function! s:status_update() range
2580 let lines = getline(a:firstline, a:lastline)
2581 let names = filter(map(lines, 's:extract_name(v:val, "[x-]", "")'), '!empty(v:val)')
2584 execute 'PlugUpdate' join(names)
2588 function! s:is_preview_window_open()
2596 function! s:find_name(lnum)
2597 for lnum in reverse(range(1, a:lnum))
2598 let line = getline(lnum)
2602 let name = s:extract_name(line, '-', '')
2610 function! s:preview_commit()
2611 if b:plug_preview < 0
2612 let b:plug_preview = !s:is_preview_window_open()
2615 let sha = matchstr(getline('.'), '^ \X*\zs[0-9a-f]\{7,9}')
2620 let name = s:find_name(line('.'))
2621 if empty(name) || !has_key(g:plugs, name) || !isdirectory(g:plugs[name].dir)
2625 if exists('g:plug_pwindow') && !s:is_preview_window_open()
2626 execute g:plug_pwindow
2632 setlocal previewwindow filetype=git buftype=nofile nobuflisted modifiable
2635 let [sh, shellcmdflag, shrd] = s:chsh(1)
2636 let cmd = 'cd '.plug#shellescape(g:plugs[name].dir).' && git show --no-color --pretty=medium '.sha
2638 let [batchfile, cmd] = s:batchfile(cmd)
2640 execute 'silent %!' cmd
2642 let [&shell, &shellcmdflag, &shellredir] = [sh, shellcmdflag, shrd]
2643 if s:is_win && filereadable(batchfile)
2644 call delete(batchfile)
2647 setlocal nomodifiable
2648 nnoremap <silent> <buffer> q :q<cr>
2652 function! s:section(flags)
2653 call search('\(^[x-] \)\@<=[^:]\+:', a:flags)
2656 function! s:format_git_log(line)
2658 let tokens = split(a:line, nr2char(1))
2660 return indent.substitute(a:line, '\s*$', '', '')
2662 let [graph, sha, refs, subject, date] = tokens
2663 let tag = matchstr(refs, 'tag: [^,)]\+')
2664 let tag = empty(tag) ? ' ' : ' ('.tag.') '
2665 return printf('%s%s%s%s%s (%s)', indent, graph, sha, tag, subject, date)
2668 function! s:append_ul(lnum, text)
2669 call append(a:lnum, ['', a:text, repeat('-', len(a:text))])
2674 call append(0, ['Collecting changes ...', ''])
2677 let total = filter(copy(g:plugs), 's:is_managed(v:key) && isdirectory(v:val.dir)')
2678 call s:progress_bar(2, bar, len(total))
2679 for origin in [1, 0]
2680 let plugs = reverse(sort(items(filter(copy(total), (origin ? '' : '!').'(has_key(v:val, "commit") || has_key(v:val, "tag"))'))))
2684 call s:append_ul(2, origin ? 'Pending updates:' : 'Last update:')
2686 let branch = s:git_origin_branch(v)
2688 let range = origin ? '..origin/'.branch : 'HEAD@{1}..'
2689 let cmd = ['git', 'log', '--graph', '--color=never']
2690 if s:git_version_requirement(2, 10, 0)
2691 call add(cmd, '--no-show-signature')
2693 call extend(cmd, ['--pretty=format:%x01%h%x01%d%x01%s%x01%cr', range])
2694 if has_key(v, 'rtp')
2695 call extend(cmd, ['--', v.rtp])
2697 let diff = s:system_chomp(cmd, v.dir)
2699 let ref = has_key(v, 'tag') ? (' (tag: '.v.tag.')') : has_key(v, 'commit') ? (' '.v.commit) : ''
2700 call append(5, extend(['', '- '.k.':'.ref], map(s:lines(diff), 's:format_git_log(v:val)')))
2701 let cnts[origin] += 1
2705 call s:progress_bar(2, bar, len(total))
2710 call append(5, ['', 'N/A'])
2713 call setline(1, printf('%d plugin(s) updated.', cnts[0])
2714 \ . (cnts[1] ? printf(' %d plugin(s) have pending updates.', cnts[1]) : ''))
2716 if cnts[0] || cnts[1]
2717 nnoremap <silent> <buffer> <plug>(plug-preview) :silent! call <SID>preview_commit()<cr>
2718 if empty(maparg("\<cr>", 'n'))
2719 nmap <buffer> <cr> <plug>(plug-preview)
2721 if empty(maparg('o', 'n'))
2722 nmap <buffer> o <plug>(plug-preview)
2726 nnoremap <silent> <buffer> X :call <SID>revert()<cr>
2727 echo "Press 'X' on each block to revert the update"
2730 setlocal nomodifiable
2733 function! s:revert()
2734 if search('^Pending updates', 'bnW')
2738 let name = s:find_name(line('.'))
2739 if empty(name) || !has_key(g:plugs, name) ||
2740 \ input(printf('Revert the update of %s? (y/N) ', name)) !~? '^y'
2744 call s:system('git reset --hard HEAD@{1} && git checkout '.plug#shellescape(g:plugs[name].branch).' --', g:plugs[name].dir)
2747 setlocal nomodifiable
2751 function! s:snapshot(force, ...) abort
2754 call append(0, ['" Generated by vim-plug',
2755 \ '" '.strftime("%c"),
2756 \ '" :source this file in vim to restore the snapshot',
2757 \ '" or execute: vim -S snapshot.vim',
2758 \ '', '', 'PlugUpdate!'])
2760 let anchor = line('$') - 3
2761 let names = sort(keys(filter(copy(g:plugs),
2762 \'has_key(v:val, "uri") && !has_key(v:val, "commit") && isdirectory(v:val.dir)')))
2763 for name in reverse(names)
2764 let sha = s:git_revision(g:plugs[name].dir)
2766 call append(anchor, printf("silent! let g:plugs['%s'].commit = '%s'", name, sha))
2772 let fn = s:plug_expand(a:1)
2773 if filereadable(fn) && !(a:force || s:ask(a:1.' already exists. Overwrite?'))
2776 call writefile(getline(1, '$'), fn)
2777 echo 'Saved as '.a:1
2778 silent execute 'e' s:esc(fn)
2783 function! s:split_rtp()
2784 return split(&rtp, '\\\@<!,')
2787 let s:first_rtp = s:escrtp(get(s:split_rtp(), 0, ''))
2788 let s:last_rtp = s:escrtp(get(s:split_rtp(), -1, ''))
2790 if exists('g:plugs')
2791 let g:plugs_order = get(g:, 'plugs_order', keys(g:plugs))
2792 call s:upgrade_specs()
2793 call s:define_commands()
2796 let &cpo = s:cpo_save