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 :if b:plug_preview==1<bar>pc<bar>endif<bar>bd<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:assign_name()
962 let prefix = '[Plugins]'
965 while bufexists(name)
966 let name = printf('%s (%s)', prefix, idx)
969 silent! execute 'f' fnameescape(name)
972 function! s:chsh(swap)
973 let prev = [&shell, &shellcmdflag, &shellredir]
978 if &shell =~# 'powershell\(\.exe\)\?$' || &shell =~# 'pwsh$'
979 let &shellredir = '2>&1 | Out-File -Encoding UTF8 %s'
980 elseif &shell =~# 'sh' || &shell =~# 'cmd\(\.exe\)\?$'
981 set shellredir=>%s\ 2>&1
987 function! s:bang(cmd, ...)
990 let [sh, shellcmdflag, shrd] = s:chsh(a:0)
991 " FIXME: Escaping is incomplete. We could use shellescape with eval,
992 " but it won't work on Windows.
993 let cmd = a:0 ? s:with_cd(a:cmd, a:1) : a:cmd
995 let [batchfile, cmd] = s:batchfile(cmd)
997 let g:_plug_bang = (s:is_win && has('gui_running') ? 'silent ' : '').'!'.escape(cmd, '#!%')
998 execute "normal! :execute g:_plug_bang\<cr>\<cr>"
1001 let [&shell, &shellcmdflag, &shellredir] = [sh, shellcmdflag, shrd]
1002 if s:is_win && filereadable(batchfile)
1003 call delete(batchfile)
1006 return v:shell_error ? 'Exit status: ' . v:shell_error : ''
1009 function! s:regress_bar()
1010 let bar = substitute(getline(2)[1:-2], '.*\zs=', 'x', '')
1011 call s:progress_bar(2, bar, len(bar))
1014 function! s:is_updated(dir)
1015 return !empty(s:system_chomp(['git', 'log', '--pretty=format:%h', 'HEAD...HEAD@{1}'], a:dir))
1018 function! s:do(pull, force, todo)
1019 for [name, spec] in items(a:todo)
1020 if !isdirectory(spec.dir)
1023 let installed = has_key(s:update.new, name)
1024 let updated = installed ? 0 :
1025 \ (a:pull && index(s:update.errors, name) < 0 && s:is_updated(spec.dir))
1026 if a:force || installed || updated
1027 execute 'cd' s:esc(spec.dir)
1028 call append(3, '- Post-update hook for '. name .' ... ')
1030 let type = type(spec.do)
1031 if type == s:TYPE.string
1032 if spec.do[0] == ':'
1033 if !get(s:loaded, name, 0)
1034 let s:loaded[name] = 1
1037 call s:load_plugin(spec)
1041 let error = v:exception
1043 if !s:plug_window_exists()
1045 throw 'Warning: vim-plug was terminated by the post-update hook of '.name
1048 let error = s:bang(spec.do)
1050 elseif type == s:TYPE.funcref
1052 call s:load_plugin(spec)
1053 let status = installed ? 'installed' : (updated ? 'updated' : 'unchanged')
1054 call spec.do({ 'name': name, 'status': status, 'force': a:force })
1056 let error = v:exception
1059 let error = 'Invalid hook type'
1062 call setline(4, empty(error) ? (getline(4) . 'OK')
1063 \ : ('x' . getline(4)[1:] . error))
1065 call add(s:update.errors, name)
1066 call s:regress_bar()
1073 function! s:hash_match(a, b)
1074 return stridx(a:a, a:b) == 0 || stridx(a:b, a:a) == 0
1077 function! s:checkout(spec)
1078 let sha = a:spec.commit
1079 let output = s:git_revision(a:spec.dir)
1080 if !empty(output) && !s:hash_match(sha, s:lines(output)[0])
1081 let credential_helper = s:git_version_requirement(2) ? '-c credential.helper= ' : ''
1082 let output = s:system(
1083 \ 'git '.credential_helper.'fetch --depth 999999 && git checkout '.plug#shellescape(sha).' --', a:spec.dir)
1088 function! s:finish(pull)
1089 let new_frozen = len(filter(keys(s:update.new), 'g:plugs[v:val].frozen'))
1091 let s = new_frozen > 1 ? 's' : ''
1092 call append(3, printf('- Installed %d frozen plugin%s', new_frozen, s))
1094 call append(3, '- Finishing ... ') | 4
1096 call plug#helptags()
1098 call setline(4, getline(4) . 'Done!')
1101 if !empty(s:update.errors)
1102 call add(msgs, "Press 'R' to retry.")
1104 if a:pull && len(s:update.new) < len(filter(getline(5, '$'),
1105 \ "v:val =~ '^- ' && v:val !~# 'Already up.to.date'"))
1106 call add(msgs, "Press 'D' to see the updated changes.")
1108 echo join(msgs, ' ')
1109 call s:finish_bindings()
1113 if empty(s:update.errors)
1117 call s:update_impl(s:update.pull, s:update.force,
1118 \ extend(copy(s:update.errors), [s:update.threads]))
1121 function! s:is_managed(name)
1122 return has_key(g:plugs[a:name], 'uri')
1125 function! s:names(...)
1126 return sort(filter(keys(g:plugs), 'stridx(v:val, a:1) == 0 && s:is_managed(v:val)'))
1129 function! s:check_ruby()
1130 silent! ruby require 'thread'; VIM::command("let g:plug_ruby = '#{RUBY_VERSION}'")
1131 if !exists('g:plug_ruby')
1133 return s:warn('echom', 'Warning: Ruby interface is broken')
1135 let ruby_version = split(g:plug_ruby, '\.')
1137 return s:version_requirement(ruby_version, [1, 8, 7])
1140 function! s:update_impl(pull, force, args) abort
1141 let sync = index(a:args, '--sync') >= 0 || has('vim_starting')
1142 let args = filter(copy(a:args), 'v:val != "--sync"')
1143 let threads = (len(args) > 0 && args[-1] =~ '^[1-9][0-9]*$') ?
1144 \ remove(args, -1) : get(g:, 'plug_threads', 16)
1146 let managed = filter(copy(g:plugs), 's:is_managed(v:key)')
1147 let todo = empty(args) ? filter(managed, '!v:val.frozen || !isdirectory(v:val.dir)') :
1148 \ filter(managed, 'index(args, v:key) >= 0')
1151 return s:warn('echo', 'No plugin to '. (a:pull ? 'update' : 'install'))
1154 if !s:is_win && s:git_version_requirement(2, 3)
1155 let s:git_terminal_prompt = exists('$GIT_TERMINAL_PROMPT') ? $GIT_TERMINAL_PROMPT : ''
1156 let $GIT_TERMINAL_PROMPT = 0
1157 for plug in values(todo)
1158 let plug.uri = substitute(plug.uri,
1159 \ '^https://git::@github\.com', 'https://github.com', '')
1163 if !isdirectory(g:plug_home)
1165 call mkdir(g:plug_home, 'p')
1167 return s:err(printf('Invalid plug directory: %s. '.
1168 \ 'Try to call plug#begin with a valid directory', g:plug_home))
1172 if has('nvim') && !exists('*jobwait') && threads > 1
1173 call s:warn('echom', '[vim-plug] Update Neovim for parallel installer')
1176 let use_job = s:nvim || s:vim8
1177 let python = (has('python') || has('python3')) && !use_job
1178 let ruby = has('ruby') && !use_job && (v:version >= 703 || v:version == 702 && has('patch374')) && !(s:is_win && has('gui_running')) && threads > 1 && s:check_ruby()
1181 \ 'start': reltime(),
1183 \ 'todo': copy(todo),
1188 \ 'threads': (python || ruby || use_job) ? min([len(todo), threads]) : 1,
1194 call append(0, ['', ''])
1198 let s:clone_opt = []
1199 if get(g:, 'plug_shallow', 1)
1200 call extend(s:clone_opt, ['--depth', '1'])
1201 if s:git_version_requirement(1, 7, 10)
1202 call add(s:clone_opt, '--no-single-branch')
1206 if has('win32unix') || has('wsl')
1207 call extend(s:clone_opt, ['-c', 'core.eol=lf', '-c', 'core.autocrlf=input'])
1210 let s:submodule_opt = s:git_version_requirement(2, 8) ? ' --jobs='.threads : ''
1212 " Python version requirement (>= 2.7)
1213 if python && !has('python3') && !ruby && !use_job && s:update.threads > 1
1215 silent python import platform; print platform.python_version()
1217 let python = s:version_requirement(
1218 \ map(split(split(pyv)[0], '\.'), 'str2nr(v:val)'), [2, 6])
1221 if (python || ruby) && s:update.threads > 1
1228 call s:update_ruby()
1230 call s:update_python()
1233 let lines = getline(4, '$')
1237 let name = s:extract_name(line, '.', '')
1238 if empty(name) || !has_key(printed, name)
1239 call append('$', line)
1241 let printed[name] = 1
1242 if line[0] == 'x' && index(s:update.errors, name) < 0
1243 call add(s:update.errors, name)
1250 call s:update_finish()
1254 while use_job && sync
1263 function! s:log4(name, msg)
1264 call setline(4, printf('- %s (%s)', a:msg, a:name))
1268 function! s:update_finish()
1269 if exists('s:git_terminal_prompt')
1270 let $GIT_TERMINAL_PROMPT = s:git_terminal_prompt
1273 call append(3, '- Updating ...') | 4
1274 for [name, spec] in items(filter(copy(s:update.all), 'index(s:update.errors, v:key) < 0 && (s:update.force || s:update.pull || has_key(s:update.new, v:key))'))
1275 let [pos, _] = s:logpos(name)
1279 if has_key(spec, 'commit')
1280 call s:log4(name, 'Checking out '.spec.commit)
1281 let out = s:checkout(spec)
1282 elseif has_key(spec, 'tag')
1285 let tags = s:lines(s:system('git tag --list '.plug#shellescape(tag).' --sort -version:refname 2>&1', spec.dir))
1286 if !v:shell_error && !empty(tags)
1288 call s:log4(name, printf('Latest tag for %s -> %s', spec.tag, tag))
1292 call s:log4(name, 'Checking out '.tag)
1293 let out = s:system('git checkout -q '.plug#shellescape(tag).' -- 2>&1', spec.dir)
1295 let branch = s:git_origin_branch(spec)
1296 call s:log4(name, 'Merging origin/'.s:esc(branch))
1297 let out = s:system('git checkout -q '.plug#shellescape(branch).' -- 2>&1'
1298 \. (has_key(s:update.new, name) ? '' : ('&& git merge --ff-only '.plug#shellescape('origin/'.branch).' 2>&1')), spec.dir)
1300 if !v:shell_error && filereadable(spec.dir.'/.gitmodules') &&
1301 \ (s:update.force || has_key(s:update.new, name) || s:is_updated(spec.dir))
1302 call s:log4(name, 'Updating submodules. This may take a while.')
1303 let out .= s:bang('git submodule update --init --recursive'.s:submodule_opt.' 2>&1', spec.dir)
1305 let msg = s:format_message(v:shell_error ? 'x': '-', name, out)
1307 call add(s:update.errors, name)
1308 call s:regress_bar()
1309 silent execute pos 'd _'
1310 call append(4, msg) | 4
1312 call setline(pos, msg[0])
1318 call s:do(s:update.pull, s:update.force, filter(copy(s:update.all), 'index(s:update.errors, v:key) < 0 && has_key(v:val, "do")'))
1320 call s:warn('echom', v:exception)
1321 call s:warn('echo', '')
1324 call s:finish(s:update.pull)
1325 call setline(1, 'Updated. Elapsed time: ' . split(reltimestr(reltime(s:update.start)))[0] . ' sec.')
1326 call s:switch_out('normal! gg')
1330 function! s:job_abort()
1331 if (!s:nvim && !s:vim8) || !exists('s:jobs')
1335 for [name, j] in items(s:jobs)
1337 silent! call jobstop(j.jobid)
1339 silent! call job_stop(j.jobid)
1342 call s:rm_rf(g:plugs[name].dir)
1348 function! s:last_non_empty_line(lines)
1349 let len = len(a:lines)
1350 for idx in range(len)
1351 let line = a:lines[len-idx-1]
1359 function! s:job_out_cb(self, data) abort
1361 let data = remove(self.lines, -1) . a:data
1362 let lines = map(split(data, "\n", 1), 'split(v:val, "\r", 1)[-1]')
1363 call extend(self.lines, lines)
1364 " To reduce the number of buffer updates
1365 let self.tick = get(self, 'tick', -1) + 1
1366 if !self.running || self.tick % len(s:jobs) == 0
1367 let bullet = self.running ? (self.new ? '+' : '*') : (self.error ? 'x' : '-')
1368 let result = self.error ? join(self.lines, "\n") : s:last_non_empty_line(self.lines)
1369 call s:log(bullet, self.name, result)
1373 function! s:job_exit_cb(self, data) abort
1374 let a:self.running = 0
1375 let a:self.error = a:data != 0
1376 call s:reap(a:self.name)
1380 function! s:job_cb(fn, job, ch, data)
1381 if !s:plug_window_exists() " plug window closed
1382 return s:job_abort()
1384 call call(a:fn, [a:job, a:data])
1387 function! s:nvim_cb(job_id, data, event) dict abort
1388 return (a:event == 'stdout' || a:event == 'stderr') ?
1389 \ s:job_cb('s:job_out_cb', self, 0, join(a:data, "\n")) :
1390 \ s:job_cb('s:job_exit_cb', self, 0, a:data)
1393 function! s:spawn(name, cmd, opts)
1394 let job = { 'name': a:name, 'running': 1, 'error': 0, 'lines': [''],
1395 \ 'new': get(a:opts, 'new', 0) }
1396 let s:jobs[a:name] = job
1399 if has_key(a:opts, 'dir')
1400 let job.cwd = a:opts.dir
1404 \ 'on_stdout': function('s:nvim_cb'),
1405 \ 'on_stderr': function('s:nvim_cb'),
1406 \ 'on_exit': function('s:nvim_cb'),
1408 let jid = s:plug_call('jobstart', argv, job)
1414 let job.lines = [jid < 0 ? argv[0].' is not executable' :
1415 \ 'Invalid arguments (or job table is full)']
1418 let cmd = join(map(copy(a:cmd), 'plug#shellescape(v:val, {"script": 0})'))
1419 if has_key(a:opts, 'dir')
1420 let cmd = s:with_cd(cmd, a:opts.dir, 0)
1422 let argv = s:is_win ? ['cmd', '/s', '/c', '"'.cmd.'"'] : ['sh', '-c', cmd]
1423 let jid = job_start(s:is_win ? join(argv, ' ') : argv, {
1424 \ 'out_cb': function('s:job_cb', ['s:job_out_cb', job]),
1425 \ 'err_cb': function('s:job_cb', ['s:job_out_cb', job]),
1426 \ 'exit_cb': function('s:job_cb', ['s:job_exit_cb', job]),
1427 \ 'err_mode': 'raw',
1430 if job_status(jid) == 'run'
1435 let job.lines = ['Failed to start job']
1438 let job.lines = s:lines(call('s:system', has_key(a:opts, 'dir') ? [a:cmd, a:opts.dir] : [a:cmd]))
1439 let job.error = v:shell_error != 0
1444 function! s:reap(name)
1445 let job = s:jobs[a:name]
1447 call add(s:update.errors, a:name)
1448 elseif get(job, 'new', 0)
1449 let s:update.new[a:name] = 1
1451 let s:update.bar .= job.error ? 'x' : '='
1453 let bullet = job.error ? 'x' : '-'
1454 let result = job.error ? join(job.lines, "\n") : s:last_non_empty_line(job.lines)
1455 call s:log(bullet, a:name, empty(result) ? 'OK' : result)
1458 call remove(s:jobs, a:name)
1463 let total = len(s:update.all)
1464 call setline(1, (s:update.pull ? 'Updating' : 'Installing').
1465 \ ' plugins ('.len(s:update.bar).'/'.total.')')
1466 call s:progress_bar(2, s:update.bar, total)
1471 function! s:logpos(name)
1473 for i in range(4, max > 4 ? max : 4)
1474 if getline(i) =~# '^[-+x*] '.a:name.':'
1475 for j in range(i + 1, max > 5 ? max : 5)
1476 if getline(j) !~ '^ '
1486 function! s:log(bullet, name, lines)
1488 let [b, e] = s:logpos(a:name)
1490 silent execute printf('%d,%d d _', b, e)
1491 if b > winheight('.')
1497 " FIXME For some reason, nomodifiable is set after :d in vim8
1499 call append(b - 1, s:format_message(a:bullet, a:name, a:lines))
1504 function! s:update_vim()
1512 let pull = s:update.pull
1513 let prog = s:progress_opt(s:nvim || s:vim8)
1514 while 1 " Without TCO, Vim stack is bound to explode
1515 if empty(s:update.todo)
1516 if empty(s:jobs) && !s:update.fin
1517 call s:update_finish()
1518 let s:update.fin = 1
1523 let name = keys(s:update.todo)[0]
1524 let spec = remove(s:update.todo, name)
1525 let new = empty(globpath(spec.dir, '.git', 1))
1527 call s:log(new ? '+' : '*', name, pull ? 'Updating ...' : 'Installing ...')
1530 let has_tag = has_key(spec, 'tag')
1532 let [error, _] = s:git_validate(spec, 0)
1535 let cmd = s:git_version_requirement(2) ? ['git', '-c', 'credential.helper=', 'fetch'] : ['git', 'fetch']
1536 if has_tag && !empty(globpath(spec.dir, '.git/shallow'))
1537 call extend(cmd, ['--depth', '99999999'])
1542 call s:spawn(name, cmd, { 'dir': spec.dir })
1544 let s:jobs[name] = { 'running': 0, 'lines': ['Already installed'], 'error': 0 }
1547 let s:jobs[name] = { 'running': 0, 'lines': s:lines(error), 'error': 1 }
1550 let cmd = ['git', 'clone']
1552 call extend(cmd, s:clone_opt)
1557 call s:spawn(name, extend(cmd, [spec.uri, s:trim(spec.dir)]), { 'new': 1 })
1560 if !s:jobs[name].running
1563 if len(s:jobs) >= s:update.threads
1569 function! s:update_python()
1570 let py_exe = has('python') ? 'python' : 'python3'
1571 execute py_exe "<< EOF"
1578 import Queue as queue
1585 import threading as thr
1590 G_NVIM = vim.eval("has('nvim')") == '1'
1591 G_PULL = vim.eval('s:update.pull') == '1'
1592 G_RETRIES = int(vim.eval('get(g:, "plug_retries", 2)')) + 1
1593 G_TIMEOUT = int(vim.eval('get(g:, "plug_timeout", 60)'))
1594 G_CLONE_OPT = ' '.join(vim.eval('s:clone_opt'))
1595 G_PROGRESS = vim.eval('s:progress_opt(1)')
1596 G_LOG_PROB = 1.0 / int(vim.eval('s:update.threads'))
1597 G_STOP = thr.Event()
1598 G_IS_WIN = vim.eval('s:is_win') == '1'
1600 class PlugError(Exception):
1601 def __init__(self, msg):
1603 class CmdTimedOut(PlugError):
1605 class CmdFailed(PlugError):
1607 class InvalidURI(PlugError):
1609 class Action(object):
1610 INSTALL, UPDATE, ERROR, DONE = ['+', '*', 'x', '-']
1612 class Buffer(object):
1613 def __init__(self, lock, num_plugs, is_pull):
1615 self.event = 'Updating' if is_pull else 'Installing'
1617 self.maxy = int(vim.eval('winheight(".")'))
1618 self.num_plugs = num_plugs
1620 def __where(self, name):
1621 """ Find first line with name in current buffer. Return line num. """
1622 found, lnum = False, 0
1623 matcher = re.compile('^[-+x*] {0}:'.format(name))
1624 for line in vim.current.buffer:
1625 if matcher.search(line) is not None:
1635 curbuf = vim.current.buffer
1636 curbuf[0] = self.event + ' plugins ({0}/{1})'.format(len(self.bar), self.num_plugs)
1638 num_spaces = self.num_plugs - len(self.bar)
1639 curbuf[1] = '[{0}{1}]'.format(self.bar, num_spaces * ' ')
1642 vim.command('normal! 2G')
1643 vim.command('redraw')
1645 def write(self, action, name, lines):
1646 first, rest = lines[0], lines[1:]
1647 msg = ['{0} {1}{2}{3}'.format(action, name, ': ' if first else '', first)]
1648 msg.extend([' ' + line for line in rest])
1651 if action == Action.ERROR:
1653 vim.command("call add(s:update.errors, '{0}')".format(name))
1654 elif action == Action.DONE:
1657 curbuf = vim.current.buffer
1658 lnum = self.__where(name)
1659 if lnum != -1: # Found matching line num
1661 if lnum > self.maxy and action in set([Action.INSTALL, Action.UPDATE]):
1665 curbuf.append(msg, lnum)
1671 class Command(object):
1672 CD = 'cd /d' if G_IS_WIN else 'cd'
1674 def __init__(self, cmd, cmd_dir=None, timeout=60, cb=None, clean=None):
1677 self.cmd = '{0} {1} && {2}'.format(Command.CD, cmd_dir, self.cmd)
1678 self.timeout = timeout
1679 self.callback = cb if cb else (lambda msg: None)
1680 self.clean = clean if clean else (lambda: None)
1685 """ Returns true only if command still running. """
1686 return self.proc and self.proc.poll() is None
1688 def execute(self, ntries=3):
1689 """ Execute the command with ntries if CmdTimedOut.
1690 Returns the output of the command if no Exception.
1692 attempt, finished, limit = 0, False, self.timeout
1697 result = self.try_command()
1701 if attempt != ntries:
1703 self.timeout += limit
1707 def notify_retry(self):
1708 """ Retry required for command, notify user. """
1709 for count in range(3, 0, -1):
1711 raise KeyboardInterrupt
1712 msg = 'Timeout. Will retry in {0} second{1} ...'.format(
1713 count, 's' if count != 1 else '')
1714 self.callback([msg])
1716 self.callback(['Retrying ...'])
1718 def try_command(self):
1719 """ Execute a cmd & poll for callback. Returns list of output.
1720 Raises CmdFailed -> return code for Popen isn't 0
1721 Raises CmdTimedOut -> command exceeded timeout without new output
1726 tfile = tempfile.NamedTemporaryFile(mode='w+b')
1727 preexec_fn = not G_IS_WIN and os.setsid or None
1728 self.proc = subprocess.Popen(self.cmd, stdout=tfile,
1729 stderr=subprocess.STDOUT,
1730 stdin=subprocess.PIPE, shell=True,
1731 preexec_fn=preexec_fn)
1732 thrd = thr.Thread(target=(lambda proc: proc.wait()), args=(self.proc,))
1735 thread_not_started = True
1736 while thread_not_started:
1739 thread_not_started = False
1740 except RuntimeError:
1745 raise KeyboardInterrupt
1747 if first_line or random.random() < G_LOG_PROB:
1749 line = '' if G_IS_WIN else nonblock_read(tfile.name)
1751 self.callback([line])
1753 time_diff = time.time() - os.path.getmtime(tfile.name)
1754 if time_diff > self.timeout:
1755 raise CmdTimedOut(['Timeout!'])
1760 result = [line.decode('utf-8', 'replace').rstrip() for line in tfile]
1762 if self.proc.returncode != 0:
1763 raise CmdFailed([''] + result)
1770 def terminate(self):
1771 """ Terminate process and cleanup. """
1774 os.kill(self.proc.pid, signal.SIGINT)
1776 os.killpg(self.proc.pid, signal.SIGTERM)
1779 class Plugin(object):
1780 def __init__(self, name, args, buf_q, lock):
1785 self.tag = args.get('tag', 0)
1789 if os.path.exists(self.args['dir']):
1794 thread_vim_command("let s:update.new['{0}'] = 1".format(self.name))
1795 except PlugError as exc:
1796 self.write(Action.ERROR, self.name, exc.msg)
1797 except KeyboardInterrupt:
1799 self.write(Action.ERROR, self.name, ['Interrupted!'])
1801 # Any exception except those above print stack trace
1802 msg = 'Trace:\n{0}'.format(traceback.format_exc().rstrip())
1803 self.write(Action.ERROR, self.name, msg.split('\n'))
1807 target = self.args['dir']
1808 if target[-1] == '\\':
1809 target = target[0:-1]
1814 shutil.rmtree(target)
1819 self.write(Action.INSTALL, self.name, ['Installing ...'])
1820 callback = functools.partial(self.write, Action.INSTALL, self.name)
1821 cmd = 'git clone {0} {1} {2} {3} 2>&1'.format(
1822 '' if self.tag else G_CLONE_OPT, G_PROGRESS, self.args['uri'],
1824 com = Command(cmd, None, G_TIMEOUT, callback, clean(target))
1825 result = com.execute(G_RETRIES)
1826 self.write(Action.DONE, self.name, result[-1:])
1829 cmd = 'git rev-parse --abbrev-ref HEAD 2>&1 && git config -f .git/config remote.origin.url'
1830 command = Command(cmd, self.args['dir'], G_TIMEOUT,)
1831 result = command.execute(G_RETRIES)
1835 actual_uri = self.repo_uri()
1836 expect_uri = self.args['uri']
1837 regex = re.compile(r'^(?:\w+://)?(?:[^@/]*@)?([^:/]*(?::[0-9]*)?)[:/](.*?)(?:\.git)?/?$')
1838 ma = regex.match(actual_uri)
1839 mb = regex.match(expect_uri)
1840 if ma is None or mb is None or ma.groups() != mb.groups():
1842 'Invalid URI: {0}'.format(actual_uri),
1843 'Expected {0}'.format(expect_uri),
1844 'PlugClean required.']
1845 raise InvalidURI(msg)
1848 self.write(Action.UPDATE, self.name, ['Updating ...'])
1849 callback = functools.partial(self.write, Action.UPDATE, self.name)
1850 fetch_opt = '--depth 99999999' if self.tag and os.path.isfile(os.path.join(self.args['dir'], '.git/shallow')) else ''
1851 cmd = 'git fetch {0} {1} 2>&1'.format(fetch_opt, G_PROGRESS)
1852 com = Command(cmd, self.args['dir'], G_TIMEOUT, callback)
1853 result = com.execute(G_RETRIES)
1854 self.write(Action.DONE, self.name, result[-1:])
1856 self.write(Action.DONE, self.name, ['Already installed'])
1858 def write(self, action, name, msg):
1859 self.buf_q.put((action, name, msg))
1861 class PlugThread(thr.Thread):
1862 def __init__(self, tname, args):
1863 super(PlugThread, self).__init__()
1868 thr.current_thread().name = self.tname
1869 buf_q, work_q, lock = self.args
1872 while not G_STOP.is_set():
1873 name, args = work_q.get_nowait()
1874 plug = Plugin(name, args, buf_q, lock)
1880 class RefreshThread(thr.Thread):
1881 def __init__(self, lock):
1882 super(RefreshThread, self).__init__()
1889 thread_vim_command('noautocmd normal! a')
1893 self.running = False
1896 def thread_vim_command(cmd):
1897 vim.session.threadsafe_call(lambda: vim.command(cmd))
1899 def thread_vim_command(cmd):
1903 return '"' + name.replace('"', '\"') + '"'
1905 def nonblock_read(fname):
1906 """ Read a file with nonblock flag. Return the last line. """
1907 fread = os.open(fname, os.O_RDONLY | os.O_NONBLOCK)
1908 buf = os.read(fread, 100000).decode('utf-8', 'replace')
1911 line = buf.rstrip('\r\n')
1912 left = max(line.rfind('\r'), line.rfind('\n'))
1920 thr.current_thread().name = 'main'
1921 nthreads = int(vim.eval('s:update.threads'))
1922 plugs = vim.eval('s:update.todo')
1923 mac_gui = vim.eval('s:mac_gui') == '1'
1926 buf = Buffer(lock, len(plugs), G_PULL)
1927 buf_q, work_q = queue.Queue(), queue.Queue()
1928 for work in plugs.items():
1931 start_cnt = thr.active_count()
1932 for num in range(nthreads):
1933 tname = 'PlugT-{0:02}'.format(num)
1934 thread = PlugThread(tname, (buf_q, work_q, lock))
1937 rthread = RefreshThread(lock)
1940 while not buf_q.empty() or thr.active_count() != start_cnt:
1942 action, name, msg = buf_q.get(True, 0.25)
1943 buf.write(action, name, ['OK'] if not msg else msg)
1947 except KeyboardInterrupt:
1958 function! s:update_ruby()
1961 SEP = ["\r", "\n", nil]
1965 char = readchar rescue return
1966 if SEP.include? char.chr
1975 end unless defined?(PlugStream)
1978 %["#{arg.gsub('"', '\"')}"]
1983 if /mswin|mingw|bccwin/ =~ RUBY_PLATFORM
1984 pids.each { |pid| Process.kill 'INT', pid.to_i rescue nil }
1986 unless `which pgrep 2> /dev/null`.empty?
1988 until children.empty?
1989 children = children.map { |pid|
1990 `pgrep -P #{pid}`.lines.map { |l| l.chomp }
1995 pids.each { |pid| Process.kill 'TERM', pid.to_i rescue nil }
1999 def compare_git_uri a, b
2000 regex = %r{^(?:\w+://)?(?:[^@/]*@)?([^:/]*(?::[0-9]*)?)[:/](.*?)(?:\.git)?/?$}
2001 regex.match(a).to_a.drop(1) == regex.match(b).to_a.drop(1)
2008 iswin = VIM::evaluate('s:is_win').to_i == 1
2009 pull = VIM::evaluate('s:update.pull').to_i == 1
2010 base = VIM::evaluate('g:plug_home')
2011 all = VIM::evaluate('s:update.todo')
2012 limit = VIM::evaluate('get(g:, "plug_timeout", 60)')
2013 tries = VIM::evaluate('get(g:, "plug_retries", 2)') + 1
2014 nthr = VIM::evaluate('s:update.threads').to_i
2015 maxy = VIM::evaluate('winheight(".")').to_i
2016 vim7 = VIM::evaluate('v:version').to_i <= 703 && RUBY_PLATFORM =~ /darwin/
2017 cd = iswin ? 'cd /d' : 'cd'
2018 tot = VIM::evaluate('len(s:update.todo)') || 0
2020 skip = 'Already installed'
2022 take1 = proc { mtx.synchronize { running && all.shift } }
2025 $curbuf[1] = "#{pull ? 'Updating' : 'Installing'} plugins (#{cnt}/#{tot})"
2026 $curbuf[2] = '[' + bar.ljust(tot) + ']'
2027 VIM::command('normal! 2G')
2028 VIM::command('redraw')
2030 where = proc { |name| (1..($curbuf.length)).find { |l| $curbuf[l] =~ /^[-+x*] #{name}:/ } }
2031 log = proc { |name, result, type|
2033 ing = ![true, false].include?(type)
2034 bar += type ? '=' : 'x' unless ing
2036 when :install then '+' when :update then '*'
2037 when true, nil then '-' else
2038 VIM::command("call add(s:update.errors, '#{name}')")
2042 if type || type.nil?
2043 ["#{b} #{name}: #{result.lines.to_a.last || 'OK'}"]
2044 elsif result =~ /^Interrupted|^Timeout/
2045 ["#{b} #{name}: #{result}"]
2047 ["#{b} #{name}"] + result.lines.map { |l| " " << l }
2049 if lnum = where.call(name)
2051 lnum = 4 if ing && lnum > maxy
2053 result.each_with_index do |line, offset|
2054 $curbuf.append((lnum || 4) - 1 + offset, line.gsub(/\e\[./, '').chomp)
2059 bt = proc { |cmd, name, type, cleanup|
2067 Timeout::timeout(timeout) do
2068 tmp = VIM::evaluate('tempname()')
2069 system("(#{cmd}) > #{tmp}")
2070 data = File.read(tmp).chomp
2071 File.unlink tmp rescue nil
2074 fd = IO.popen(cmd).extend(PlugStream)
2076 log_prob = 1.0 / nthr
2077 while line = Timeout::timeout(timeout) { fd.get_line }
2079 log.call name, line.chomp, type if name && (first_line || rand < log_prob)
2084 [$? == 0, data.chomp]
2085 rescue Timeout::Error, Interrupt => e
2086 if fd && !fd.closed?
2090 cleanup.call if cleanup
2091 if e.is_a?(Timeout::Error) && tried < tries
2092 3.downto(1) do |countdown|
2093 s = countdown > 1 ? 's' : ''
2094 log.call name, "Timeout. Will retry in #{countdown} second#{s} ...", type
2097 log.call name, 'Retrying ...', type
2100 [false, e.is_a?(Interrupt) ? "Interrupted!" : "Timeout!"]
2103 main = Thread.current
2105 watcher = Thread.new {
2107 while VIM::evaluate('getchar(1)')
2111 require 'io/console' # >= Ruby 1.9
2112 nil until IO.console.getch == 3.chr
2116 threads.each { |t| t.raise Interrupt } unless vim7
2118 threads.each { |t| t.join rescue nil }
2121 refresh = Thread.new {
2124 break unless running
2125 VIM::command('noautocmd normal! a')
2129 } if VIM::evaluate('s:mac_gui') == 1
2131 clone_opt = VIM::evaluate('s:clone_opt').join(' ')
2132 progress = VIM::evaluate('s:progress_opt(1)')
2135 threads << Thread.new {
2136 while pair = take1.call
2138 dir, uri, tag = pair.last.values_at *%w[dir uri tag]
2139 exists = File.directory? dir
2142 chdir = "#{cd} #{iswin ? dir : esc(dir)}"
2143 ret, data = bt.call "#{chdir} && git rev-parse --abbrev-ref HEAD 2>&1 && git config -f .git/config remote.origin.url", nil, nil, nil
2144 current_uri = data.lines.to_a.last
2146 if data =~ /^Interrupted|^Timeout/
2149 [false, [data.chomp, "PlugClean required."].join($/)]
2151 elsif !compare_git_uri(current_uri, uri)
2152 [false, ["Invalid URI: #{current_uri}",
2154 "PlugClean required."].join($/)]
2157 log.call name, 'Updating ...', :update
2158 fetch_opt = (tag && File.exist?(File.join(dir, '.git/shallow'))) ? '--depth 99999999' : ''
2159 bt.call "#{chdir} && git fetch #{fetch_opt} #{progress} 2>&1", name, :update, nil
2165 d = esc dir.sub(%r{[\\/]+$}, '')
2166 log.call name, 'Installing ...', :install
2167 bt.call "git clone #{clone_opt unless tag} #{progress} #{uri} #{d} 2>&1", name, :install, proc {
2171 mtx.synchronize { VIM::command("let s:update.new['#{name}'] = 1") } if !exists && ok
2172 log.call name, result, ok
2177 threads.each { |t| t.join rescue nil }
2179 refresh.kill if refresh
2184 function! s:shellesc_cmd(arg, script)
2185 let escaped = substitute('"'.a:arg.'"', '[&|<>()@^!"]', '^&', 'g')
2186 return substitute(escaped, '%', (a:script ? '%' : '^') . '&', 'g')
2189 function! s:shellesc_ps1(arg)
2190 return "'".substitute(escape(a:arg, '\"'), "'", "''", 'g')."'"
2193 function! s:shellesc_sh(arg)
2194 return "'".substitute(a:arg, "'", "'\\\\''", 'g')."'"
2197 " Escape the shell argument based on the shell.
2198 " Vim and Neovim's shellescape() are insufficient.
2199 " 1. shellslash determines whether to use single/double quotes.
2200 " Double-quote escaping is fragile for cmd.exe.
2201 " 2. It does not work for powershell.
2202 " 3. It does not work for *sh shells if the command is executed
2203 " via cmd.exe (ie. cmd.exe /c sh -c command command_args)
2204 " 4. It does not support batchfile syntax.
2206 " Accepts an optional dictionary with the following keys:
2207 " - shell: same as Vim/Neovim 'shell' option.
2208 " If unset, fallback to 'cmd.exe' on Windows or 'sh'.
2209 " - script: If truthy and shell is cmd.exe, escape for batchfile syntax.
2210 function! plug#shellescape(arg, ...)
2211 if a:arg =~# '^[A-Za-z0-9_/:.-]\+$'
2214 let opts = a:0 > 0 && type(a:1) == s:TYPE.dict ? a:1 : {}
2215 let shell = get(opts, 'shell', s:is_win ? 'cmd.exe' : 'sh')
2216 let script = get(opts, 'script', 1)
2217 if shell =~# 'cmd\(\.exe\)\?$'
2218 return s:shellesc_cmd(a:arg, script)
2219 elseif shell =~# 'powershell\(\.exe\)\?$' || shell =~# 'pwsh$'
2220 return s:shellesc_ps1(a:arg)
2222 return s:shellesc_sh(a:arg)
2225 function! s:glob_dir(path)
2226 return map(filter(s:glob(a:path, '**'), 'isdirectory(v:val)'), 's:dirpath(v:val)')
2229 function! s:progress_bar(line, bar, total)
2230 call setline(a:line, '[' . s:lpad(a:bar, a:total) . ']')
2233 function! s:compare_git_uri(a, b)
2234 " See `git help clone'
2235 " https:// [user@] github.com[:port] / junegunn/vim-plug [.git]
2236 " [git@] github.com[:port] : junegunn/vim-plug [.git]
2237 " file:// / junegunn/vim-plug [/]
2238 " / junegunn/vim-plug [/]
2239 let pat = '^\%(\w\+://\)\='.'\%([^@/]*@\)\='.'\([^:/]*\%(:[0-9]*\)\=\)'.'[:/]'.'\(.\{-}\)'.'\%(\.git\)\=/\?$'
2240 let ma = matchlist(a:a, pat)
2241 let mb = matchlist(a:b, pat)
2242 return ma[1:2] ==# mb[1:2]
2245 function! s:format_message(bullet, name, message)
2247 return [printf('%s %s: %s', a:bullet, a:name, s:lastline(a:message))]
2249 let lines = map(s:lines(a:message), '" ".v:val')
2250 return extend([printf('x %s:', a:name)], lines)
2254 function! s:with_cd(cmd, dir, ...)
2255 let script = a:0 > 0 ? a:1 : 1
2256 return printf('cd%s %s && %s', s:is_win ? ' /d' : '', plug#shellescape(a:dir, {'script': script}), a:cmd)
2259 function! s:system(cmd, ...)
2262 let [sh, shellcmdflag, shrd] = s:chsh(1)
2263 if type(a:cmd) == s:TYPE.list
2264 " Neovim's system() supports list argument to bypass the shell
2265 " but it cannot set the working directory for the command.
2266 " Assume that the command does not rely on the shell.
2267 if has('nvim') && a:0 == 0
2268 return system(a:cmd)
2270 let cmd = join(map(copy(a:cmd), 'plug#shellescape(v:val, {"shell": &shell, "script": 0})'))
2271 if &shell =~# 'powershell\(\.exe\)\?$'
2272 let cmd = '& ' . cmd
2278 let cmd = s:with_cd(cmd, a:1, type(a:cmd) != s:TYPE.list)
2280 if s:is_win && type(a:cmd) != s:TYPE.list
2281 let [batchfile, cmd] = s:batchfile(cmd)
2285 let [&shell, &shellcmdflag, &shellredir] = [sh, shellcmdflag, shrd]
2286 if s:is_win && filereadable(batchfile)
2287 call delete(batchfile)
2292 function! s:system_chomp(...)
2293 let ret = call('s:system', a:000)
2294 return v:shell_error ? '' : substitute(ret, '\n$', '', '')
2297 function! s:git_validate(spec, check_branch)
2299 if isdirectory(a:spec.dir)
2300 let result = [s:git_local_branch(a:spec.dir), s:git_origin_url(a:spec.dir)]
2301 let remote = result[-1]
2303 let err = join([remote, 'PlugClean required.'], "\n")
2304 elseif !s:compare_git_uri(remote, a:spec.uri)
2305 let err = join(['Invalid URI: '.remote,
2306 \ 'Expected: '.a:spec.uri,
2307 \ 'PlugClean required.'], "\n")
2308 elseif a:check_branch && has_key(a:spec, 'commit')
2309 let sha = s:git_revision(a:spec.dir)
2311 let err = join(add(result, 'PlugClean required.'), "\n")
2312 elseif !s:hash_match(sha, a:spec.commit)
2313 let err = join([printf('Invalid HEAD (expected: %s, actual: %s)',
2314 \ a:spec.commit[:6], sha[:6]),
2315 \ 'PlugUpdate required.'], "\n")
2317 elseif a:check_branch
2318 let current_branch = result[0]
2320 let origin_branch = s:git_origin_branch(a:spec)
2321 if has_key(a:spec, 'tag')
2322 let tag = s:system_chomp('git describe --exact-match --tags HEAD 2>&1', a:spec.dir)
2323 if a:spec.tag !=# tag && a:spec.tag !~ '\*'
2324 let err = printf('Invalid tag: %s (expected: %s). Try PlugUpdate.',
2325 \ (empty(tag) ? 'N/A' : tag), a:spec.tag)
2328 elseif origin_branch !=# current_branch
2329 let err = printf('Invalid branch: %s (expected: %s). Try PlugUpdate.',
2330 \ current_branch, origin_branch)
2333 let [ahead, behind] = split(s:lastline(s:system([
2334 \ 'git', 'rev-list', '--count', '--left-right',
2335 \ printf('HEAD...origin/%s', origin_branch)
2336 \ ], a:spec.dir)), '\t')
2337 if !v:shell_error && ahead
2339 " Only mention PlugClean if diverged, otherwise it's likely to be
2340 " pushable (and probably not that messed up).
2342 \ "Diverged from origin/%s (%d commit(s) ahead and %d commit(s) behind!\n"
2343 \ .'Backup local changes and run PlugClean and PlugUpdate to reinstall it.', origin_branch, ahead, behind)
2345 let err = printf("Ahead of origin/%s by %d commit(s).\n"
2346 \ .'Cannot update until local changes are pushed.',
2347 \ origin_branch, ahead)
2353 let err = 'Not found'
2355 return [err, err =~# 'PlugClean']
2358 function! s:rm_rf(dir)
2359 if isdirectory(a:dir)
2360 return s:system(s:is_win
2361 \ ? 'rmdir /S /Q '.plug#shellescape(a:dir)
2362 \ : ['rm', '-rf', a:dir])
2366 function! s:clean(force)
2368 call append(0, 'Searching for invalid plugins in '.g:plug_home)
2371 " List of valid directories
2374 let [cnt, total] = [0, len(g:plugs)]
2375 for [name, spec] in items(g:plugs)
2376 if !s:is_managed(name)
2377 call add(dirs, spec.dir)
2379 let [err, clean] = s:git_validate(spec, 1)
2381 let errs[spec.dir] = s:lines(err)[0]
2383 call add(dirs, spec.dir)
2387 call s:progress_bar(2, repeat('=', cnt), total)
2394 let allowed[s:dirpath(s:plug_fnamemodify(dir, ':h:h'))] = 1
2395 let allowed[dir] = 1
2396 for child in s:glob_dir(dir)
2397 let allowed[child] = 1
2402 let found = sort(s:glob_dir(g:plug_home))
2404 let f = remove(found, 0)
2405 if !has_key(allowed, f) && isdirectory(f)
2407 call append(line('$'), '- ' . f)
2409 call append(line('$'), ' ' . errs[f])
2411 let found = filter(found, 'stridx(v:val, f) != 0')
2418 call append(line('$'), 'Already clean.')
2420 let s:clean_count = 0
2421 call append(3, ['Directories to delete:', ''])
2423 if a:force || s:ask_no_interrupt('Delete all directories?')
2424 call s:delete([6, line('$')], 1)
2426 call setline(4, 'Cancelled.')
2427 nnoremap <silent> <buffer> d :set opfunc=<sid>delete_op<cr>g@
2428 nmap <silent> <buffer> dd d_
2429 xnoremap <silent> <buffer> d :<c-u>call <sid>delete_op(visualmode(), 1)<cr>
2430 echo 'Delete the lines (d{motion}) to delete the corresponding directories'
2434 setlocal nomodifiable
2437 function! s:delete_op(type, ...)
2438 call s:delete(a:0 ? [line("'<"), line("'>")] : [line("'["), line("']")], 0)
2441 function! s:delete(range, force)
2442 let [l1, l2] = a:range
2446 let line = getline(l1)
2447 if line =~ '^- ' && isdirectory(line[2:])
2450 let answer = force ? 1 : s:ask('Delete '.line[2:].'?', 1)
2451 let force = force || answer > 1
2453 let err = s:rm_rf(line[2:])
2456 call setline(l1, '~'.line[1:])
2457 let s:clean_count += 1
2460 call append(l1 - 1, s:format_message('x', line[1:], err))
2461 let l2 += len(s:lines(err))
2464 let msg = printf('Removed %d directories.', s:clean_count)
2466 let msg .= printf(' Failed to remove %d directories.', err_count)
2468 call setline(4, msg)
2469 setlocal nomodifiable
2476 function! s:upgrade()
2477 echo 'Downloading the latest version of vim-plug'
2479 let tmp = s:plug_tempname()
2480 let new = tmp . '/plug.vim'
2483 let out = s:system(['git', 'clone', '--depth', '1', s:plug_src, tmp])
2485 return s:err('Error upgrading vim-plug: '. out)
2488 if readfile(s:me) ==# readfile(new)
2489 echo 'vim-plug is already up-to-date'
2492 call rename(s:me, s:me . '.old')
2493 call rename(new, s:me)
2495 echo 'vim-plug has been upgraded'
2499 silent! call s:rm_rf(tmp)
2503 function! s:upgrade_specs()
2504 for spec in values(g:plugs)
2505 let spec.frozen = get(spec, 'frozen', 0)
2509 function! s:status()
2511 call append(0, 'Checking plugins')
2516 let [cnt, total] = [0, len(g:plugs)]
2517 for [name, spec] in items(g:plugs)
2518 let is_dir = isdirectory(spec.dir)
2519 if has_key(spec, 'uri')
2521 let [err, _] = s:git_validate(spec, 1)
2522 let [valid, msg] = [empty(err), empty(err) ? 'OK' : err]
2524 let [valid, msg] = [0, 'Not found. Try PlugInstall.']
2528 let [valid, msg] = [1, 'OK']
2530 let [valid, msg] = [0, 'Not found.']
2535 " `s:loaded` entry can be missing if PlugUpgraded
2536 if is_dir && get(s:loaded, name, -1) == 0
2538 let msg .= ' (not loaded)'
2540 call s:progress_bar(2, repeat('=', cnt), total)
2541 call append(3, s:format_message(valid ? '-' : 'x', name, msg))
2545 call setline(1, 'Finished. '.ecnt.' error(s).')
2547 setlocal nomodifiable
2549 echo "Press 'L' on each line to load plugin, or 'U' to update"
2550 nnoremap <silent> <buffer> L :call <SID>status_load(line('.'))<cr>
2551 xnoremap <silent> <buffer> L :call <SID>status_load(line('.'))<cr>
2555 function! s:extract_name(str, prefix, suffix)
2556 return matchstr(a:str, '^'.a:prefix.' \zs[^:]\+\ze:.*'.a:suffix.'$')
2559 function! s:status_load(lnum)
2560 let line = getline(a:lnum)
2561 let name = s:extract_name(line, '-', '(not loaded)')
2563 call plug#load(name)
2565 call setline(a:lnum, substitute(line, ' (not loaded)$', '', ''))
2566 setlocal nomodifiable
2570 function! s:status_update() range
2571 let lines = getline(a:firstline, a:lastline)
2572 let names = filter(map(lines, 's:extract_name(v:val, "[x-]", "")'), '!empty(v:val)')
2575 execute 'PlugUpdate' join(names)
2579 function! s:is_preview_window_open()
2587 function! s:find_name(lnum)
2588 for lnum in reverse(range(1, a:lnum))
2589 let line = getline(lnum)
2593 let name = s:extract_name(line, '-', '')
2601 function! s:preview_commit()
2602 if b:plug_preview < 0
2603 let b:plug_preview = !s:is_preview_window_open()
2606 let sha = matchstr(getline('.'), '^ \X*\zs[0-9a-f]\{7,9}')
2611 let name = s:find_name(line('.'))
2612 if empty(name) || !has_key(g:plugs, name) || !isdirectory(g:plugs[name].dir)
2616 if exists('g:plug_pwindow') && !s:is_preview_window_open()
2617 execute g:plug_pwindow
2623 setlocal previewwindow filetype=git buftype=nofile nobuflisted modifiable
2626 let [sh, shellcmdflag, shrd] = s:chsh(1)
2627 let cmd = 'cd '.plug#shellescape(g:plugs[name].dir).' && git show --no-color --pretty=medium '.sha
2629 let [batchfile, cmd] = s:batchfile(cmd)
2631 execute 'silent %!' cmd
2633 let [&shell, &shellcmdflag, &shellredir] = [sh, shellcmdflag, shrd]
2634 if s:is_win && filereadable(batchfile)
2635 call delete(batchfile)
2638 setlocal nomodifiable
2639 nnoremap <silent> <buffer> q :q<cr>
2643 function! s:section(flags)
2644 call search('\(^[x-] \)\@<=[^:]\+:', a:flags)
2647 function! s:format_git_log(line)
2649 let tokens = split(a:line, nr2char(1))
2651 return indent.substitute(a:line, '\s*$', '', '')
2653 let [graph, sha, refs, subject, date] = tokens
2654 let tag = matchstr(refs, 'tag: [^,)]\+')
2655 let tag = empty(tag) ? ' ' : ' ('.tag.') '
2656 return printf('%s%s%s%s%s (%s)', indent, graph, sha, tag, subject, date)
2659 function! s:append_ul(lnum, text)
2660 call append(a:lnum, ['', a:text, repeat('-', len(a:text))])
2665 call append(0, ['Collecting changes ...', ''])
2668 let total = filter(copy(g:plugs), 's:is_managed(v:key) && isdirectory(v:val.dir)')
2669 call s:progress_bar(2, bar, len(total))
2670 for origin in [1, 0]
2671 let plugs = reverse(sort(items(filter(copy(total), (origin ? '' : '!').'(has_key(v:val, "commit") || has_key(v:val, "tag"))'))))
2675 call s:append_ul(2, origin ? 'Pending updates:' : 'Last update:')
2677 let branch = s:git_origin_branch(v)
2679 let range = origin ? '..origin/'.branch : 'HEAD@{1}..'
2680 let cmd = ['git', 'log', '--graph', '--color=never']
2681 if s:git_version_requirement(2, 10, 0)
2682 call add(cmd, '--no-show-signature')
2684 call extend(cmd, ['--pretty=format:%x01%h%x01%d%x01%s%x01%cr', range])
2685 if has_key(v, 'rtp')
2686 call extend(cmd, ['--', v.rtp])
2688 let diff = s:system_chomp(cmd, v.dir)
2690 let ref = has_key(v, 'tag') ? (' (tag: '.v.tag.')') : has_key(v, 'commit') ? (' '.v.commit) : ''
2691 call append(5, extend(['', '- '.k.':'.ref], map(s:lines(diff), 's:format_git_log(v:val)')))
2692 let cnts[origin] += 1
2696 call s:progress_bar(2, bar, len(total))
2701 call append(5, ['', 'N/A'])
2704 call setline(1, printf('%d plugin(s) updated.', cnts[0])
2705 \ . (cnts[1] ? printf(' %d plugin(s) have pending updates.', cnts[1]) : ''))
2707 if cnts[0] || cnts[1]
2708 nnoremap <silent> <buffer> <plug>(plug-preview) :silent! call <SID>preview_commit()<cr>
2709 if empty(maparg("\<cr>", 'n'))
2710 nmap <buffer> <cr> <plug>(plug-preview)
2712 if empty(maparg('o', 'n'))
2713 nmap <buffer> o <plug>(plug-preview)
2717 nnoremap <silent> <buffer> X :call <SID>revert()<cr>
2718 echo "Press 'X' on each block to revert the update"
2721 setlocal nomodifiable
2724 function! s:revert()
2725 if search('^Pending updates', 'bnW')
2729 let name = s:find_name(line('.'))
2730 if empty(name) || !has_key(g:plugs, name) ||
2731 \ input(printf('Revert the update of %s? (y/N) ', name)) !~? '^y'
2735 call s:system('git reset --hard HEAD@{1} && git checkout '.plug#shellescape(g:plugs[name].branch).' --', g:plugs[name].dir)
2738 setlocal nomodifiable
2742 function! s:snapshot(force, ...) abort
2745 call append(0, ['" Generated by vim-plug',
2746 \ '" '.strftime("%c"),
2747 \ '" :source this file in vim to restore the snapshot',
2748 \ '" or execute: vim -S snapshot.vim',
2749 \ '', '', 'PlugUpdate!'])
2751 let anchor = line('$') - 3
2752 let names = sort(keys(filter(copy(g:plugs),
2753 \'has_key(v:val, "uri") && !has_key(v:val, "commit") && isdirectory(v:val.dir)')))
2754 for name in reverse(names)
2755 let sha = s:git_revision(g:plugs[name].dir)
2757 call append(anchor, printf("silent! let g:plugs['%s'].commit = '%s'", name, sha))
2763 let fn = s:plug_expand(a:1)
2764 if filereadable(fn) && !(a:force || s:ask(a:1.' already exists. Overwrite?'))
2767 call writefile(getline(1, '$'), fn)
2768 echo 'Saved as '.a:1
2769 silent execute 'e' s:esc(fn)
2774 function! s:split_rtp()
2775 return split(&rtp, '\\\@<!,')
2778 let s:first_rtp = s:escrtp(get(s:split_rtp(), 0, ''))
2779 let s:last_rtp = s:escrtp(get(s:split_rtp(), -1, ''))
2781 if exists('g:plugs')
2782 let g:plugs_order = get(g:, 'plugs_order', keys(g:plugs))
2783 call s:upgrade_specs()
2784 call s:define_commands()
2787 let &cpo = s:cpo_save