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:is_powershell(shell)
120 return a:shell =~# 'powershell\(\.exe\)\?$' || a:shell =~# 'pwsh\(\.exe\)\?$'
123 function! s:isabsolute(dir) abort
124 return a:dir =~# '^/' || (has('win32') && a:dir =~? '^\%(\\\|[A-Z]:\)')
127 function! s:git_dir(dir) abort
128 let gitdir = s:trim(a:dir) . '/.git'
129 if isdirectory(gitdir)
132 if !filereadable(gitdir)
135 let gitdir = matchstr(get(readfile(gitdir), 0, ''), '^gitdir: \zs.*')
136 if len(gitdir) && !s:isabsolute(gitdir)
137 let gitdir = a:dir . '/' . gitdir
139 return isdirectory(gitdir) ? gitdir : ''
142 function! s:git_origin_url(dir) abort
143 let gitdir = s:git_dir(a:dir)
144 let config = gitdir . '/config'
145 if empty(gitdir) || !filereadable(config)
148 return matchstr(join(readfile(config)), '\[remote "origin"\].\{-}url\s*=\s*\zs\S*\ze')
151 function! s:git_revision(dir) abort
152 let gitdir = s:git_dir(a:dir)
153 let head = gitdir . '/HEAD'
154 if empty(gitdir) || !filereadable(head)
158 let line = get(readfile(head), 0, '')
159 let ref = matchstr(line, '^ref: \zs.*')
164 if filereadable(gitdir . '/' . ref)
165 return get(readfile(gitdir . '/' . ref), 0, '')
168 if filereadable(gitdir . '/packed-refs')
169 for line in readfile(gitdir . '/packed-refs')
170 if line =~# ' ' . ref
171 return matchstr(line, '^[0-9a-f]*')
179 function! s:git_local_branch(dir) abort
180 let gitdir = s:git_dir(a:dir)
181 let head = gitdir . '/HEAD'
182 if empty(gitdir) || !filereadable(head)
185 let branch = matchstr(get(readfile(head), 0, ''), '^ref: refs/heads/\zs.*')
186 return len(branch) ? branch : 'HEAD'
189 function! s:git_origin_branch(spec)
190 if len(a:spec.branch)
194 " The file may not be present if this is a local repository
195 let gitdir = s:git_dir(a:spec.dir)
196 let origin_head = gitdir.'/refs/remotes/origin/HEAD'
197 if len(gitdir) && filereadable(origin_head)
198 return matchstr(get(readfile(origin_head), 0, ''),
199 \ '^ref: refs/remotes/origin/\zs.*')
202 " The command may not return the name of a branch in detached HEAD state
203 let result = s:lines(s:system('git symbolic-ref --short HEAD', a:spec.dir))
204 return v:shell_error ? '' : result[-1]
208 function! s:plug_call(fn, ...)
209 let shellslash = &shellslash
212 return call(a:fn, a:000)
214 let &shellslash = shellslash
218 function! s:plug_call(fn, ...)
219 return call(a:fn, a:000)
223 function! s:plug_getcwd()
224 return s:plug_call('getcwd')
227 function! s:plug_fnamemodify(fname, mods)
228 return s:plug_call('fnamemodify', a:fname, a:mods)
231 function! s:plug_expand(fmt)
232 return s:plug_call('expand', a:fmt, 1)
235 function! s:plug_tempname()
236 return s:plug_call('tempname')
239 function! plug#begin(...)
241 let s:plug_home_org = a:1
242 let home = s:path(s:plug_fnamemodify(s:plug_expand(a:1), ':p'))
243 elseif exists('g:plug_home')
244 let home = s:path(g:plug_home)
246 let home = s:path(split(&rtp, ',')[0]) . '/plugged'
248 return s:err('Unable to determine plug home. Try calling plug#begin() with a path argument.')
250 if s:plug_fnamemodify(home, ':t') ==# 'plugin' && s:plug_fnamemodify(home, ':h') ==# s:first_rtp
251 return s:err('Invalid plug home. '.home.' is a standard Vim runtime path and is not allowed.')
254 let g:plug_home = home
256 let g:plugs_order = []
259 call s:define_commands()
263 function! s:define_commands()
264 command! -nargs=+ -bar Plug call plug#(<args>)
265 if !executable('git')
266 return s:err('`git` executable not found. Most commands will not be available. To suppress this message, prepend `silent!` to `call plug#begin(...)`.')
270 \ && (&shell =~# 'cmd\(\.exe\)\?$' || s:is_powershell(&shell))
271 return s:err('vim-plug does not support shell, ' . &shell . ', when shellslash is set.')
274 \ && (has('win32') || has('win32unix'))
275 \ && !has('multi_byte')
276 return s:err('Vim needs +multi_byte feature on Windows to run shell commands. Enable +iconv for best results.')
278 command! -nargs=* -bar -bang -complete=customlist,s:names PlugInstall call s:install(<bang>0, [<f-args>])
279 command! -nargs=* -bar -bang -complete=customlist,s:names PlugUpdate call s:update(<bang>0, [<f-args>])
280 command! -nargs=0 -bar -bang PlugClean call s:clean(<bang>0)
281 command! -nargs=0 -bar PlugUpgrade if s:upgrade() | execute 'source' s:esc(s:me) | endif
282 command! -nargs=0 -bar PlugStatus call s:status()
283 command! -nargs=0 -bar PlugDiff call s:diff()
284 command! -nargs=? -bar -bang -complete=file PlugSnapshot call s:snapshot(<bang>0, <f-args>)
288 return type(a:v) == s:TYPE.list ? a:v : [a:v]
292 return type(a:v) == s:TYPE.string ? a:v : join(a:v, "\n") . "\n"
295 function! s:glob(from, pattern)
296 return s:lines(globpath(a:from, a:pattern))
299 function! s:source(from, ...)
302 for vim in s:glob(a:from, pattern)
303 execute 'source' s:esc(vim)
310 function! s:assoc(dict, key, val)
311 let a:dict[a:key] = add(get(a:dict, a:key, []), a:val)
314 function! s:ask(message, ...)
317 let answer = input(a:message.(a:0 ? ' (y/N/a) ' : ' (y/N) '))
321 return (a:0 && answer =~? '^a') ? 2 : (answer =~? '^y') ? 1 : 0
324 function! s:ask_no_interrupt(...)
326 return call('s:ask', a:000)
332 function! s:lazy(plug, opt)
333 return has_key(a:plug, a:opt) &&
334 \ (empty(s:to_a(a:plug[a:opt])) ||
335 \ !isdirectory(a:plug.dir) ||
336 \ len(s:glob(s:rtp(a:plug), 'plugin')) ||
337 \ len(s:glob(s:rtp(a:plug), 'after/plugin')))
341 if !exists('g:plugs')
342 return s:err('plug#end() called without calling plug#begin() first')
345 if exists('#PlugLOD')
351 let lod = { 'ft': {}, 'map': {}, 'cmd': {} }
353 if exists('g:did_load_filetypes')
356 for name in g:plugs_order
357 if !has_key(g:plugs, name)
360 let plug = g:plugs[name]
361 if get(s:loaded, name, 0) || !s:lazy(plug, 'on') && !s:lazy(plug, 'for')
362 let s:loaded[name] = 1
366 if has_key(plug, 'on')
367 let s:triggers[name] = { 'map': [], 'cmd': [] }
368 for cmd in s:to_a(plug.on)
369 if cmd =~? '^<Plug>.\+'
370 if empty(mapcheck(cmd)) && empty(mapcheck(cmd, 'i'))
371 call s:assoc(lod.map, cmd, name)
373 call add(s:triggers[name].map, cmd)
374 elseif cmd =~# '^[A-Z]'
375 let cmd = substitute(cmd, '!*$', '', '')
376 if exists(':'.cmd) != 2
377 call s:assoc(lod.cmd, cmd, name)
379 call add(s:triggers[name].cmd, cmd)
381 call s:err('Invalid `on` option: '.cmd.
382 \ '. Should start with an uppercase letter or `<Plug>`.')
387 if has_key(plug, 'for')
388 let types = s:to_a(plug.for)
390 augroup filetypedetect
391 call s:source(s:rtp(plug), 'ftdetect/**/*.vim', 'after/ftdetect/**/*.vim')
395 call s:assoc(lod.ft, type, name)
400 for [cmd, names] in items(lod.cmd)
402 \ 'command! -nargs=* -range -bang -complete=file %s call s:lod_cmd(%s, "<bang>", <line1>, <line2>, <q-args>, %s)',
403 \ cmd, string(cmd), string(names))
406 for [map, names] in items(lod.map)
407 for [mode, map_prefix, key_prefix] in
408 \ [['i', '<C-O>', ''], ['n', '', ''], ['v', '', 'gv'], ['o', '', '']]
410 \ '%snoremap <silent> %s %s:<C-U>call <SID>lod_map(%s, %s, %s, "%s")<CR>',
411 \ mode, map, map_prefix, string(map), string(names), mode != 'i', key_prefix)
415 for [ft, names] in items(lod.ft)
417 execute printf('autocmd FileType %s call <SID>lod_ft(%s, %s)',
418 \ ft, string(ft), string(names))
423 filetype plugin indent on
424 if has('vim_starting')
425 if has('syntax') && !exists('g:syntax_on')
429 call s:reload_plugins()
433 function! s:loaded_names()
434 return filter(copy(g:plugs_order), 'get(s:loaded, v:val, 0)')
437 function! s:load_plugin(spec)
438 call s:source(s:rtp(a:spec), 'plugin/**/*.vim', 'after/plugin/**/*.vim')
441 function! s:reload_plugins()
442 for name in s:loaded_names()
443 call s:load_plugin(g:plugs[name])
447 function! s:trim(str)
448 return substitute(a:str, '[\/]\+$', '', '')
451 function! s:version_requirement(val, min)
452 for idx in range(0, len(a:min) - 1)
453 let v = get(a:val, idx, 0)
454 if v < a:min[idx] | return 0
455 elseif v > a:min[idx] | return 1
461 function! s:git_version_requirement(...)
462 if !exists('s:git_version')
463 let s:git_version = map(split(split(s:system(['git', '--version']))[2], '\.'), 'str2nr(v:val)')
465 return s:version_requirement(s:git_version, a:000)
468 function! s:progress_opt(base)
469 return a:base && !s:is_win &&
470 \ s:git_version_requirement(1, 7, 1) ? '--progress' : ''
473 function! s:rtp(spec)
474 return s:path(a:spec.dir . get(a:spec, 'rtp', ''))
478 function! s:path(path)
479 return s:trim(substitute(a:path, '/', '\', 'g'))
482 function! s:dirpath(path)
483 return s:path(a:path) . '\'
486 function! s:is_local_plug(repo)
487 return a:repo =~? '^[a-z]:\|^[%~]'
491 function! s:wrap_cmds(cmds)
494 \ 'setlocal enabledelayedexpansion']
495 \ + (type(a:cmds) == type([]) ? a:cmds : [a:cmds])
498 if !exists('s:codepage')
499 let s:codepage = libcallnr('kernel32.dll', 'GetACP', 0)
501 return map(cmds, printf('iconv(v:val."\r", "%s", "cp%d")', &encoding, s:codepage))
503 return map(cmds, 'v:val."\r"')
506 function! s:batchfile(cmd)
507 let batchfile = s:plug_tempname().'.bat'
508 call writefile(s:wrap_cmds(a:cmd), batchfile)
509 let cmd = plug#shellescape(batchfile, {'shell': &shell, 'script': 0})
510 if s:is_powershell(&shell)
513 return [batchfile, cmd]
516 function! s:path(path)
517 return s:trim(a:path)
520 function! s:dirpath(path)
521 return substitute(a:path, '[/\\]*$', '/', '')
524 function! s:is_local_plug(repo)
525 return a:repo[0] =~ '[/$~]'
531 echom '[vim-plug] '.a:msg
535 function! s:warn(cmd, msg)
537 execute a:cmd 'a:msg'
541 function! s:esc(path)
542 return escape(a:path, ' ')
545 function! s:escrtp(path)
546 return escape(a:path, ' ,')
549 function! s:remove_rtp()
550 for name in s:loaded_names()
551 let rtp = s:rtp(g:plugs[name])
552 execute 'set rtp-='.s:escrtp(rtp)
553 let after = globpath(rtp, 'after')
554 if isdirectory(after)
555 execute 'set rtp-='.s:escrtp(after)
560 function! s:reorg_rtp()
561 if !empty(s:first_rtp)
562 execute 'set rtp-='.s:first_rtp
563 execute 'set rtp-='.s:last_rtp
566 " &rtp is modified from outside
567 if exists('s:prtp') && s:prtp !=# &rtp
572 let s:middle = get(s:, 'middle', &rtp)
573 let rtps = map(s:loaded_names(), 's:rtp(g:plugs[v:val])')
574 let afters = filter(map(copy(rtps), 'globpath(v:val, "after")'), '!empty(v:val)')
575 let rtp = join(map(rtps, 'escape(v:val, ",")'), ',')
577 \ . join(map(afters, 'escape(v:val, ",")'), ',')
578 let &rtp = substitute(substitute(rtp, ',,*', ',', 'g'), '^,\|,$', '', 'g')
581 if !empty(s:first_rtp)
582 execute 'set rtp^='.s:first_rtp
583 execute 'set rtp+='.s:last_rtp
587 function! s:doautocmd(...)
588 if exists('#'.join(a:000, '#'))
589 execute 'doautocmd' ((v:version > 703 || has('patch442')) ? '<nomodeline>' : '') join(a:000)
593 function! s:dobufread(names)
595 let path = s:rtp(g:plugs[name])
596 for dir in ['ftdetect', 'ftplugin', 'after/ftdetect', 'after/ftplugin']
597 if len(finddir(dir, path))
598 if exists('#BufRead')
607 function! plug#load(...)
609 return s:err('Argument missing: plugin name(s) required')
611 if !exists('g:plugs')
612 return s:err('plug#begin was not called')
614 let names = a:0 == 1 && type(a:1) == s:TYPE.list ? a:1 : a:000
615 let unknowns = filter(copy(names), '!has_key(g:plugs, v:val)')
617 let s = len(unknowns) > 1 ? 's' : ''
618 return s:err(printf('Unknown plugin%s: %s', s, join(unknowns, ', ')))
620 let unloaded = filter(copy(names), '!get(s:loaded, v:val, 0)')
623 call s:lod([name], ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin'])
625 call s:dobufread(unloaded)
631 function! s:remove_triggers(name)
632 if !has_key(s:triggers, a:name)
635 for cmd in s:triggers[a:name].cmd
636 execute 'silent! delc' cmd
638 for map in s:triggers[a:name].map
639 execute 'silent! unmap' map
640 execute 'silent! iunmap' map
642 call remove(s:triggers, a:name)
645 function! s:lod(names, types, ...)
647 call s:remove_triggers(name)
648 let s:loaded[name] = 1
653 let rtp = s:rtp(g:plugs[name])
655 call s:source(rtp, dir.'/**/*.vim')
658 if !s:source(rtp, a:1) && !empty(s:glob(rtp, a:2))
659 execute 'runtime' a:1
661 call s:source(rtp, a:2)
663 call s:doautocmd('User', name)
667 function! s:lod_ft(pat, names)
668 let syn = 'syntax/'.a:pat.'.vim'
669 call s:lod(a:names, ['plugin', 'after/plugin'], syn, 'after/'.syn)
670 execute 'autocmd! PlugLOD FileType' a:pat
671 call s:doautocmd('filetypeplugin', 'FileType')
672 call s:doautocmd('filetypeindent', 'FileType')
675 function! s:lod_cmd(cmd, bang, l1, l2, args, names)
676 call s:lod(a:names, ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin'])
677 call s:dobufread(a:names)
678 execute printf('%s%s%s %s', (a:l1 == a:l2 ? '' : (a:l1.','.a:l2)), a:cmd, a:bang, a:args)
681 function! s:lod_map(map, names, with_prefix, prefix)
682 call s:lod(a:names, ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin'])
683 call s:dobufread(a:names)
690 let extra .= nr2char(c)
694 let prefix = v:count ? v:count : ''
695 let prefix .= '"'.v:register.a:prefix
698 let prefix = "\<esc>" . prefix
700 let prefix .= v:operator
702 call feedkeys(prefix, 'n')
704 call feedkeys(substitute(a:map, '^<Plug>', "\<Plug>", '') . extra)
707 function! plug#(repo, ...)
709 return s:err('Invalid number of arguments (1..2)')
713 let repo = s:trim(a:repo)
714 let opts = a:0 == 1 ? s:parse_options(a:1) : s:base_spec
715 let name = get(opts, 'as', s:plug_fnamemodify(repo, ':t:s?\.git$??'))
716 let spec = extend(s:infer_properties(name, repo), opts)
717 if !has_key(g:plugs, name)
718 call add(g:plugs_order, name)
720 let g:plugs[name] = spec
721 let s:loaded[name] = get(s:loaded, name, 0)
723 return s:err(repo . ' ' . v:exception)
727 function! s:parse_options(arg)
728 let opts = copy(s:base_spec)
729 let type = type(a:arg)
730 let opt_errfmt = 'Invalid argument for "%s" option of :Plug (expected: %s)'
731 if type == s:TYPE.string
733 throw printf(opt_errfmt, 'tag', 'string')
736 elseif type == s:TYPE.dict
737 for opt in ['branch', 'tag', 'commit', 'rtp', 'dir', 'as']
738 if has_key(a:arg, opt)
739 \ && (type(a:arg[opt]) != s:TYPE.string || empty(a:arg[opt]))
740 throw printf(opt_errfmt, opt, 'string')
743 for opt in ['on', 'for']
744 if has_key(a:arg, opt)
745 \ && type(a:arg[opt]) != s:TYPE.list
746 \ && (type(a:arg[opt]) != s:TYPE.string || empty(a:arg[opt]))
747 throw printf(opt_errfmt, opt, 'string or list')
750 if has_key(a:arg, 'do')
751 \ && type(a:arg.do) != s:TYPE.funcref
752 \ && (type(a:arg.do) != s:TYPE.string || empty(a:arg.do))
753 throw printf(opt_errfmt, 'do', 'string or funcref')
755 call extend(opts, a:arg)
756 if has_key(opts, 'dir')
757 let opts.dir = s:dirpath(s:plug_expand(opts.dir))
760 throw 'Invalid argument type (expected: string or dictionary)'
765 function! s:infer_properties(name, repo)
767 if s:is_local_plug(repo)
768 return { 'dir': s:dirpath(s:plug_expand(repo)) }
774 throw printf('Invalid argument: %s (implicit `vim-scripts'' expansion is deprecated)', repo)
776 let fmt = get(g:, 'plug_url_format', 'https://git::@github.com/%s.git')
777 let uri = printf(fmt, repo)
779 return { 'dir': s:dirpath(g:plug_home.'/'.a:name), 'uri': uri }
783 function! s:install(force, names)
784 call s:update_impl(0, a:force, a:names)
787 function! s:update(force, names)
788 call s:update_impl(1, a:force, a:names)
791 function! plug#helptags()
792 if !exists('g:plugs')
793 return s:err('plug#begin was not called')
795 for spec in values(g:plugs)
796 let docd = join([s:rtp(spec), 'doc'], '/')
798 silent! execute 'helptags' s:esc(docd)
806 syntax region plug1 start=/\%1l/ end=/\%2l/ contains=plugNumber
807 syntax region plug2 start=/\%2l/ end=/\%3l/ contains=plugBracket,plugX
808 syn match plugNumber /[0-9]\+[0-9.]*/ contained
809 syn match plugBracket /[[\]]/ contained
810 syn match plugX /x/ contained
811 syn match plugDash /^-\{1}\ /
812 syn match plugPlus /^+/
813 syn match plugStar /^*/
814 syn match plugMessage /\(^- \)\@<=.*/
815 syn match plugName /\(^- \)\@<=[^ ]*:/
816 syn match plugSha /\%(: \)\@<=[0-9a-f]\{4,}$/
817 syn match plugTag /(tag: [^)]\+)/
818 syn match plugInstall /\(^+ \)\@<=[^:]*/
819 syn match plugUpdate /\(^* \)\@<=[^:]*/
820 syn match plugCommit /^ \X*[0-9a-f]\{7,9} .*/ contains=plugRelDate,plugEdge,plugTag
821 syn match plugEdge /^ \X\+$/
822 syn match plugEdge /^ \X*/ contained nextgroup=plugSha
823 syn match plugSha /[0-9a-f]\{7,9}/ contained
824 syn match plugRelDate /([^)]*)$/ contained
825 syn match plugNotLoaded /(not loaded)$/
826 syn match plugError /^x.*/
827 syn region plugDeleted start=/^\~ .*/ end=/^\ze\S/
828 syn match plugH2 /^.*:\n-\+$/
829 syn match plugH2 /^-\{2,}/
830 syn keyword Function PlugInstall PlugStatus PlugUpdate PlugClean
831 hi def link plug1 Title
832 hi def link plug2 Repeat
833 hi def link plugH2 Type
834 hi def link plugX Exception
835 hi def link plugBracket Structure
836 hi def link plugNumber Number
838 hi def link plugDash Special
839 hi def link plugPlus Constant
840 hi def link plugStar Boolean
842 hi def link plugMessage Function
843 hi def link plugName Label
844 hi def link plugInstall Function
845 hi def link plugUpdate Type
847 hi def link plugError Error
848 hi def link plugDeleted Ignore
849 hi def link plugRelDate Comment
850 hi def link plugEdge PreProc
851 hi def link plugSha Identifier
852 hi def link plugTag Constant
854 hi def link plugNotLoaded Comment
857 function! s:lpad(str, len)
858 return a:str . repeat(' ', a:len - len(a:str))
861 function! s:lines(msg)
862 return split(a:msg, "[\r\n]")
865 function! s:lastline(msg)
866 return get(s:lines(a:msg), -1, '')
869 function! s:new_window()
870 execute get(g:, 'plug_window', 'vertical topleft new')
873 function! s:plug_window_exists()
874 let buflist = tabpagebuflist(s:plug_tab)
875 return !empty(buflist) && index(buflist, s:plug_buf) >= 0
878 function! s:switch_in()
879 if !s:plug_window_exists()
883 if winbufnr(0) != s:plug_buf
884 let s:pos = [tabpagenr(), winnr(), winsaveview()]
885 execute 'normal!' s:plug_tab.'gt'
886 let winnr = bufwinnr(s:plug_buf)
887 execute winnr.'wincmd w'
888 call add(s:pos, winsaveview())
890 let s:pos = [winsaveview()]
897 function! s:switch_out(...)
898 call winrestview(s:pos[-1])
899 setlocal nomodifiable
905 execute 'normal!' s:pos[0].'gt'
906 execute s:pos[1] 'wincmd w'
907 call winrestview(s:pos[2])
911 function! s:finish_bindings()
912 nnoremap <silent> <buffer> R :call <SID>retry()<cr>
913 nnoremap <silent> <buffer> D :PlugDiff<cr>
914 nnoremap <silent> <buffer> S :PlugStatus<cr>
915 nnoremap <silent> <buffer> U :call <SID>status_update()<cr>
916 xnoremap <silent> <buffer> U :call <SID>status_update()<cr>
917 nnoremap <silent> <buffer> ]] :silent! call <SID>section('')<cr>
918 nnoremap <silent> <buffer> [[ :silent! call <SID>section('b')<cr>
921 function! s:prepare(...)
922 if empty(s:plug_getcwd())
923 throw 'Invalid current working directory. Cannot proceed.'
926 for evar in ['$GIT_DIR', '$GIT_WORK_TREE']
928 throw evar.' detected. Cannot proceed.'
934 if b:plug_preview == 1
942 nnoremap <silent> <buffer> q :call <SID>close_pane()<cr>
944 call s:finish_bindings()
946 let b:plug_preview = -1
947 let s:plug_tab = tabpagenr()
948 let s:plug_buf = winbufnr(0)
951 for k in ['<cr>', 'L', 'o', 'X', 'd', 'dd']
952 execute 'silent! unmap <buffer>' k
954 setlocal buftype=nofile bufhidden=wipe nobuflisted nolist noswapfile nowrap cursorline modifiable nospell
955 if exists('+colorcolumn')
956 setlocal colorcolumn=
959 if exists('g:syntax_on')
964 function! s:close_pane()
965 if b:plug_preview == 1
967 let b:plug_preview = -1
973 function! s:assign_name()
975 let prefix = '[Plugins]'
978 while bufexists(name)
979 let name = printf('%s (%s)', prefix, idx)
982 silent! execute 'f' fnameescape(name)
985 function! s:chsh(swap)
986 let prev = [&shell, &shellcmdflag, &shellredir]
991 if s:is_powershell(&shell)
992 let &shellredir = '2>&1 | Out-File -Encoding UTF8 %s'
993 elseif &shell =~# 'sh' || &shell =~# 'cmd\(\.exe\)\?$'
994 set shellredir=>%s\ 2>&1
1000 function! s:bang(cmd, ...)
1003 let [sh, shellcmdflag, shrd] = s:chsh(a:0)
1004 " FIXME: Escaping is incomplete. We could use shellescape with eval,
1005 " but it won't work on Windows.
1006 let cmd = a:0 ? s:with_cd(a:cmd, a:1) : a:cmd
1008 let [batchfile, cmd] = s:batchfile(cmd)
1010 let g:_plug_bang = (s:is_win && has('gui_running') ? 'silent ' : '').'!'.escape(cmd, '#!%')
1011 execute "normal! :execute g:_plug_bang\<cr>\<cr>"
1014 let [&shell, &shellcmdflag, &shellredir] = [sh, shellcmdflag, shrd]
1015 if s:is_win && filereadable(batchfile)
1016 call delete(batchfile)
1019 return v:shell_error ? 'Exit status: ' . v:shell_error : ''
1022 function! s:regress_bar()
1023 let bar = substitute(getline(2)[1:-2], '.*\zs=', 'x', '')
1024 call s:progress_bar(2, bar, len(bar))
1027 function! s:is_updated(dir)
1028 return !empty(s:system_chomp(['git', 'log', '--pretty=format:%h', 'HEAD...HEAD@{1}'], a:dir))
1031 function! s:do(pull, force, todo)
1032 for [name, spec] in items(a:todo)
1033 if !isdirectory(spec.dir)
1036 let installed = has_key(s:update.new, name)
1037 let updated = installed ? 0 :
1038 \ (a:pull && index(s:update.errors, name) < 0 && s:is_updated(spec.dir))
1039 if a:force || installed || updated
1040 execute 'cd' s:esc(spec.dir)
1041 call append(3, '- Post-update hook for '. name .' ... ')
1043 let type = type(spec.do)
1044 if type == s:TYPE.string
1045 if spec.do[0] == ':'
1046 if !get(s:loaded, name, 0)
1047 let s:loaded[name] = 1
1050 call s:load_plugin(spec)
1054 let error = v:exception
1056 if !s:plug_window_exists()
1058 throw 'Warning: vim-plug was terminated by the post-update hook of '.name
1061 let error = s:bang(spec.do)
1063 elseif type == s:TYPE.funcref
1065 call s:load_plugin(spec)
1066 let status = installed ? 'installed' : (updated ? 'updated' : 'unchanged')
1067 call spec.do({ 'name': name, 'status': status, 'force': a:force })
1069 let error = v:exception
1072 let error = 'Invalid hook type'
1075 call setline(4, empty(error) ? (getline(4) . 'OK')
1076 \ : ('x' . getline(4)[1:] . error))
1078 call add(s:update.errors, name)
1079 call s:regress_bar()
1086 function! s:hash_match(a, b)
1087 return stridx(a:a, a:b) == 0 || stridx(a:b, a:a) == 0
1090 function! s:checkout(spec)
1091 let sha = a:spec.commit
1092 let output = s:git_revision(a:spec.dir)
1093 if !empty(output) && !s:hash_match(sha, s:lines(output)[0])
1094 let credential_helper = s:git_version_requirement(2) ? '-c credential.helper= ' : ''
1095 let output = s:system(
1096 \ 'git '.credential_helper.'fetch --depth 999999 && git checkout '.plug#shellescape(sha).' --', a:spec.dir)
1101 function! s:finish(pull)
1102 let new_frozen = len(filter(keys(s:update.new), 'g:plugs[v:val].frozen'))
1104 let s = new_frozen > 1 ? 's' : ''
1105 call append(3, printf('- Installed %d frozen plugin%s', new_frozen, s))
1107 call append(3, '- Finishing ... ') | 4
1109 call plug#helptags()
1111 call setline(4, getline(4) . 'Done!')
1114 if !empty(s:update.errors)
1115 call add(msgs, "Press 'R' to retry.")
1117 if a:pull && len(s:update.new) < len(filter(getline(5, '$'),
1118 \ "v:val =~ '^- ' && v:val !~# 'Already up.to.date'"))
1119 call add(msgs, "Press 'D' to see the updated changes.")
1121 echo join(msgs, ' ')
1122 call s:finish_bindings()
1126 if empty(s:update.errors)
1130 call s:update_impl(s:update.pull, s:update.force,
1131 \ extend(copy(s:update.errors), [s:update.threads]))
1134 function! s:is_managed(name)
1135 return has_key(g:plugs[a:name], 'uri')
1138 function! s:names(...)
1139 return sort(filter(keys(g:plugs), 'stridx(v:val, a:1) == 0 && s:is_managed(v:val)'))
1142 function! s:check_ruby()
1143 silent! ruby require 'thread'; VIM::command("let g:plug_ruby = '#{RUBY_VERSION}'")
1144 if !exists('g:plug_ruby')
1146 return s:warn('echom', 'Warning: Ruby interface is broken')
1148 let ruby_version = split(g:plug_ruby, '\.')
1150 return s:version_requirement(ruby_version, [1, 8, 7])
1153 function! s:update_impl(pull, force, args) abort
1154 let sync = index(a:args, '--sync') >= 0 || has('vim_starting')
1155 let args = filter(copy(a:args), 'v:val != "--sync"')
1156 let threads = (len(args) > 0 && args[-1] =~ '^[1-9][0-9]*$') ?
1157 \ remove(args, -1) : get(g:, 'plug_threads', 16)
1159 let managed = filter(copy(g:plugs), 's:is_managed(v:key)')
1160 let todo = empty(args) ? filter(managed, '!v:val.frozen || !isdirectory(v:val.dir)') :
1161 \ filter(managed, 'index(args, v:key) >= 0')
1164 return s:warn('echo', 'No plugin to '. (a:pull ? 'update' : 'install'))
1167 if !s:is_win && s:git_version_requirement(2, 3)
1168 let s:git_terminal_prompt = exists('$GIT_TERMINAL_PROMPT') ? $GIT_TERMINAL_PROMPT : ''
1169 let $GIT_TERMINAL_PROMPT = 0
1170 for plug in values(todo)
1171 let plug.uri = substitute(plug.uri,
1172 \ '^https://git::@github\.com', 'https://github.com', '')
1176 if !isdirectory(g:plug_home)
1178 call mkdir(g:plug_home, 'p')
1180 return s:err(printf('Invalid plug directory: %s. '.
1181 \ 'Try to call plug#begin with a valid directory', g:plug_home))
1185 if has('nvim') && !exists('*jobwait') && threads > 1
1186 call s:warn('echom', '[vim-plug] Update Neovim for parallel installer')
1189 let use_job = s:nvim || s:vim8
1190 let python = (has('python') || has('python3')) && !use_job
1191 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()
1194 \ 'start': reltime(),
1196 \ 'todo': copy(todo),
1201 \ 'threads': (python || ruby || use_job) ? min([len(todo), threads]) : 1,
1207 call append(0, ['', ''])
1211 let s:clone_opt = []
1212 if get(g:, 'plug_shallow', 1)
1213 call extend(s:clone_opt, ['--depth', '1'])
1214 if s:git_version_requirement(1, 7, 10)
1215 call add(s:clone_opt, '--no-single-branch')
1219 if has('win32unix') || has('wsl')
1220 call extend(s:clone_opt, ['-c', 'core.eol=lf', '-c', 'core.autocrlf=input'])
1223 let s:submodule_opt = s:git_version_requirement(2, 8) ? ' --jobs='.threads : ''
1225 " Python version requirement (>= 2.7)
1226 if python && !has('python3') && !ruby && !use_job && s:update.threads > 1
1228 silent python import platform; print platform.python_version()
1230 let python = s:version_requirement(
1231 \ map(split(split(pyv)[0], '\.'), 'str2nr(v:val)'), [2, 6])
1234 if (python || ruby) && s:update.threads > 1
1241 call s:update_ruby()
1243 call s:update_python()
1246 let lines = getline(4, '$')
1250 let name = s:extract_name(line, '.', '')
1251 if empty(name) || !has_key(printed, name)
1252 call append('$', line)
1254 let printed[name] = 1
1255 if line[0] == 'x' && index(s:update.errors, name) < 0
1256 call add(s:update.errors, name)
1263 call s:update_finish()
1267 while use_job && sync
1276 function! s:log4(name, msg)
1277 call setline(4, printf('- %s (%s)', a:msg, a:name))
1281 function! s:update_finish()
1282 if exists('s:git_terminal_prompt')
1283 let $GIT_TERMINAL_PROMPT = s:git_terminal_prompt
1286 call append(3, '- Updating ...') | 4
1287 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))'))
1288 let [pos, _] = s:logpos(name)
1292 if has_key(spec, 'commit')
1293 call s:log4(name, 'Checking out '.spec.commit)
1294 let out = s:checkout(spec)
1295 elseif has_key(spec, 'tag')
1298 let tags = s:lines(s:system('git tag --list '.plug#shellescape(tag).' --sort -version:refname 2>&1', spec.dir))
1299 if !v:shell_error && !empty(tags)
1301 call s:log4(name, printf('Latest tag for %s -> %s', spec.tag, tag))
1305 call s:log4(name, 'Checking out '.tag)
1306 let out = s:system('git checkout -q '.plug#shellescape(tag).' -- 2>&1', spec.dir)
1308 let branch = s:git_origin_branch(spec)
1309 call s:log4(name, 'Merging origin/'.s:esc(branch))
1310 let out = s:system('git checkout -q '.plug#shellescape(branch).' -- 2>&1'
1311 \. (has_key(s:update.new, name) ? '' : ('&& git merge --ff-only '.plug#shellescape('origin/'.branch).' 2>&1')), spec.dir)
1313 if !v:shell_error && filereadable(spec.dir.'/.gitmodules') &&
1314 \ (s:update.force || has_key(s:update.new, name) || s:is_updated(spec.dir))
1315 call s:log4(name, 'Updating submodules. This may take a while.')
1316 let out .= s:bang('git submodule update --init --recursive'.s:submodule_opt.' 2>&1', spec.dir)
1318 let msg = s:format_message(v:shell_error ? 'x': '-', name, out)
1320 call add(s:update.errors, name)
1321 call s:regress_bar()
1322 silent execute pos 'd _'
1323 call append(4, msg) | 4
1325 call setline(pos, msg[0])
1331 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")'))
1333 call s:warn('echom', v:exception)
1334 call s:warn('echo', '')
1337 call s:finish(s:update.pull)
1338 call setline(1, 'Updated. Elapsed time: ' . split(reltimestr(reltime(s:update.start)))[0] . ' sec.')
1339 call s:switch_out('normal! gg')
1343 function! s:job_abort()
1344 if (!s:nvim && !s:vim8) || !exists('s:jobs')
1348 for [name, j] in items(s:jobs)
1350 silent! call jobstop(j.jobid)
1352 silent! call job_stop(j.jobid)
1355 call s:rm_rf(g:plugs[name].dir)
1361 function! s:last_non_empty_line(lines)
1362 let len = len(a:lines)
1363 for idx in range(len)
1364 let line = a:lines[len-idx-1]
1372 function! s:job_out_cb(self, data) abort
1374 let data = remove(self.lines, -1) . a:data
1375 let lines = map(split(data, "\n", 1), 'split(v:val, "\r", 1)[-1]')
1376 call extend(self.lines, lines)
1377 " To reduce the number of buffer updates
1378 let self.tick = get(self, 'tick', -1) + 1
1379 if !self.running || self.tick % len(s:jobs) == 0
1380 let bullet = self.running ? (self.new ? '+' : '*') : (self.error ? 'x' : '-')
1381 let result = self.error ? join(self.lines, "\n") : s:last_non_empty_line(self.lines)
1382 call s:log(bullet, self.name, result)
1386 function! s:job_exit_cb(self, data) abort
1387 let a:self.running = 0
1388 let a:self.error = a:data != 0
1389 call s:reap(a:self.name)
1393 function! s:job_cb(fn, job, ch, data)
1394 if !s:plug_window_exists() " plug window closed
1395 return s:job_abort()
1397 call call(a:fn, [a:job, a:data])
1400 function! s:nvim_cb(job_id, data, event) dict abort
1401 return (a:event == 'stdout' || a:event == 'stderr') ?
1402 \ s:job_cb('s:job_out_cb', self, 0, join(a:data, "\n")) :
1403 \ s:job_cb('s:job_exit_cb', self, 0, a:data)
1406 function! s:spawn(name, cmd, opts)
1407 let job = { 'name': a:name, 'running': 1, 'error': 0, 'lines': [''],
1408 \ 'new': get(a:opts, 'new', 0) }
1409 let s:jobs[a:name] = job
1412 if has_key(a:opts, 'dir')
1413 let job.cwd = a:opts.dir
1417 \ 'on_stdout': function('s:nvim_cb'),
1418 \ 'on_stderr': function('s:nvim_cb'),
1419 \ 'on_exit': function('s:nvim_cb'),
1421 let jid = s:plug_call('jobstart', argv, job)
1427 let job.lines = [jid < 0 ? argv[0].' is not executable' :
1428 \ 'Invalid arguments (or job table is full)']
1431 let cmd = join(map(copy(a:cmd), 'plug#shellescape(v:val, {"script": 0})'))
1432 if has_key(a:opts, 'dir')
1433 let cmd = s:with_cd(cmd, a:opts.dir, 0)
1435 let argv = s:is_win ? ['cmd', '/s', '/c', '"'.cmd.'"'] : ['sh', '-c', cmd]
1436 let jid = job_start(s:is_win ? join(argv, ' ') : argv, {
1437 \ 'out_cb': function('s:job_cb', ['s:job_out_cb', job]),
1438 \ 'err_cb': function('s:job_cb', ['s:job_out_cb', job]),
1439 \ 'exit_cb': function('s:job_cb', ['s:job_exit_cb', job]),
1440 \ 'err_mode': 'raw',
1443 if job_status(jid) == 'run'
1448 let job.lines = ['Failed to start job']
1451 let job.lines = s:lines(call('s:system', has_key(a:opts, 'dir') ? [a:cmd, a:opts.dir] : [a:cmd]))
1452 let job.error = v:shell_error != 0
1457 function! s:reap(name)
1458 let job = s:jobs[a:name]
1460 call add(s:update.errors, a:name)
1461 elseif get(job, 'new', 0)
1462 let s:update.new[a:name] = 1
1464 let s:update.bar .= job.error ? 'x' : '='
1466 let bullet = job.error ? 'x' : '-'
1467 let result = job.error ? join(job.lines, "\n") : s:last_non_empty_line(job.lines)
1468 call s:log(bullet, a:name, empty(result) ? 'OK' : result)
1471 call remove(s:jobs, a:name)
1476 let total = len(s:update.all)
1477 call setline(1, (s:update.pull ? 'Updating' : 'Installing').
1478 \ ' plugins ('.len(s:update.bar).'/'.total.')')
1479 call s:progress_bar(2, s:update.bar, total)
1484 function! s:logpos(name)
1486 for i in range(4, max > 4 ? max : 4)
1487 if getline(i) =~# '^[-+x*] '.a:name.':'
1488 for j in range(i + 1, max > 5 ? max : 5)
1489 if getline(j) !~ '^ '
1499 function! s:log(bullet, name, lines)
1501 let [b, e] = s:logpos(a:name)
1503 silent execute printf('%d,%d d _', b, e)
1504 if b > winheight('.')
1510 " FIXME For some reason, nomodifiable is set after :d in vim8
1512 call append(b - 1, s:format_message(a:bullet, a:name, a:lines))
1517 function! s:update_vim()
1525 let pull = s:update.pull
1526 let prog = s:progress_opt(s:nvim || s:vim8)
1527 while 1 " Without TCO, Vim stack is bound to explode
1528 if empty(s:update.todo)
1529 if empty(s:jobs) && !s:update.fin
1530 call s:update_finish()
1531 let s:update.fin = 1
1536 let name = keys(s:update.todo)[0]
1537 let spec = remove(s:update.todo, name)
1538 let new = empty(globpath(spec.dir, '.git', 1))
1540 call s:log(new ? '+' : '*', name, pull ? 'Updating ...' : 'Installing ...')
1543 let has_tag = has_key(spec, 'tag')
1545 let [error, _] = s:git_validate(spec, 0)
1548 let cmd = s:git_version_requirement(2) ? ['git', '-c', 'credential.helper=', 'fetch'] : ['git', 'fetch']
1549 if has_tag && !empty(globpath(spec.dir, '.git/shallow'))
1550 call extend(cmd, ['--depth', '99999999'])
1555 call s:spawn(name, cmd, { 'dir': spec.dir })
1557 let s:jobs[name] = { 'running': 0, 'lines': ['Already installed'], 'error': 0 }
1560 let s:jobs[name] = { 'running': 0, 'lines': s:lines(error), 'error': 1 }
1563 let cmd = ['git', 'clone']
1565 call extend(cmd, s:clone_opt)
1570 call s:spawn(name, extend(cmd, [spec.uri, s:trim(spec.dir)]), { 'new': 1 })
1573 if !s:jobs[name].running
1576 if len(s:jobs) >= s:update.threads
1582 function! s:update_python()
1583 let py_exe = has('python') ? 'python' : 'python3'
1584 execute py_exe "<< EOF"
1591 import Queue as queue
1598 import threading as thr
1603 G_NVIM = vim.eval("has('nvim')") == '1'
1604 G_PULL = vim.eval('s:update.pull') == '1'
1605 G_RETRIES = int(vim.eval('get(g:, "plug_retries", 2)')) + 1
1606 G_TIMEOUT = int(vim.eval('get(g:, "plug_timeout", 60)'))
1607 G_CLONE_OPT = ' '.join(vim.eval('s:clone_opt'))
1608 G_PROGRESS = vim.eval('s:progress_opt(1)')
1609 G_LOG_PROB = 1.0 / int(vim.eval('s:update.threads'))
1610 G_STOP = thr.Event()
1611 G_IS_WIN = vim.eval('s:is_win') == '1'
1613 class PlugError(Exception):
1614 def __init__(self, msg):
1616 class CmdTimedOut(PlugError):
1618 class CmdFailed(PlugError):
1620 class InvalidURI(PlugError):
1622 class Action(object):
1623 INSTALL, UPDATE, ERROR, DONE = ['+', '*', 'x', '-']
1625 class Buffer(object):
1626 def __init__(self, lock, num_plugs, is_pull):
1628 self.event = 'Updating' if is_pull else 'Installing'
1630 self.maxy = int(vim.eval('winheight(".")'))
1631 self.num_plugs = num_plugs
1633 def __where(self, name):
1634 """ Find first line with name in current buffer. Return line num. """
1635 found, lnum = False, 0
1636 matcher = re.compile('^[-+x*] {0}:'.format(name))
1637 for line in vim.current.buffer:
1638 if matcher.search(line) is not None:
1648 curbuf = vim.current.buffer
1649 curbuf[0] = self.event + ' plugins ({0}/{1})'.format(len(self.bar), self.num_plugs)
1651 num_spaces = self.num_plugs - len(self.bar)
1652 curbuf[1] = '[{0}{1}]'.format(self.bar, num_spaces * ' ')
1655 vim.command('normal! 2G')
1656 vim.command('redraw')
1658 def write(self, action, name, lines):
1659 first, rest = lines[0], lines[1:]
1660 msg = ['{0} {1}{2}{3}'.format(action, name, ': ' if first else '', first)]
1661 msg.extend([' ' + line for line in rest])
1664 if action == Action.ERROR:
1666 vim.command("call add(s:update.errors, '{0}')".format(name))
1667 elif action == Action.DONE:
1670 curbuf = vim.current.buffer
1671 lnum = self.__where(name)
1672 if lnum != -1: # Found matching line num
1674 if lnum > self.maxy and action in set([Action.INSTALL, Action.UPDATE]):
1678 curbuf.append(msg, lnum)
1684 class Command(object):
1685 CD = 'cd /d' if G_IS_WIN else 'cd'
1687 def __init__(self, cmd, cmd_dir=None, timeout=60, cb=None, clean=None):
1690 self.cmd = '{0} {1} && {2}'.format(Command.CD, cmd_dir, self.cmd)
1691 self.timeout = timeout
1692 self.callback = cb if cb else (lambda msg: None)
1693 self.clean = clean if clean else (lambda: None)
1698 """ Returns true only if command still running. """
1699 return self.proc and self.proc.poll() is None
1701 def execute(self, ntries=3):
1702 """ Execute the command with ntries if CmdTimedOut.
1703 Returns the output of the command if no Exception.
1705 attempt, finished, limit = 0, False, self.timeout
1710 result = self.try_command()
1714 if attempt != ntries:
1716 self.timeout += limit
1720 def notify_retry(self):
1721 """ Retry required for command, notify user. """
1722 for count in range(3, 0, -1):
1724 raise KeyboardInterrupt
1725 msg = 'Timeout. Will retry in {0} second{1} ...'.format(
1726 count, 's' if count != 1 else '')
1727 self.callback([msg])
1729 self.callback(['Retrying ...'])
1731 def try_command(self):
1732 """ Execute a cmd & poll for callback. Returns list of output.
1733 Raises CmdFailed -> return code for Popen isn't 0
1734 Raises CmdTimedOut -> command exceeded timeout without new output
1739 tfile = tempfile.NamedTemporaryFile(mode='w+b')
1740 preexec_fn = not G_IS_WIN and os.setsid or None
1741 self.proc = subprocess.Popen(self.cmd, stdout=tfile,
1742 stderr=subprocess.STDOUT,
1743 stdin=subprocess.PIPE, shell=True,
1744 preexec_fn=preexec_fn)
1745 thrd = thr.Thread(target=(lambda proc: proc.wait()), args=(self.proc,))
1748 thread_not_started = True
1749 while thread_not_started:
1752 thread_not_started = False
1753 except RuntimeError:
1758 raise KeyboardInterrupt
1760 if first_line or random.random() < G_LOG_PROB:
1762 line = '' if G_IS_WIN else nonblock_read(tfile.name)
1764 self.callback([line])
1766 time_diff = time.time() - os.path.getmtime(tfile.name)
1767 if time_diff > self.timeout:
1768 raise CmdTimedOut(['Timeout!'])
1773 result = [line.decode('utf-8', 'replace').rstrip() for line in tfile]
1775 if self.proc.returncode != 0:
1776 raise CmdFailed([''] + result)
1783 def terminate(self):
1784 """ Terminate process and cleanup. """
1787 os.kill(self.proc.pid, signal.SIGINT)
1789 os.killpg(self.proc.pid, signal.SIGTERM)
1792 class Plugin(object):
1793 def __init__(self, name, args, buf_q, lock):
1798 self.tag = args.get('tag', 0)
1802 if os.path.exists(self.args['dir']):
1807 thread_vim_command("let s:update.new['{0}'] = 1".format(self.name))
1808 except PlugError as exc:
1809 self.write(Action.ERROR, self.name, exc.msg)
1810 except KeyboardInterrupt:
1812 self.write(Action.ERROR, self.name, ['Interrupted!'])
1814 # Any exception except those above print stack trace
1815 msg = 'Trace:\n{0}'.format(traceback.format_exc().rstrip())
1816 self.write(Action.ERROR, self.name, msg.split('\n'))
1820 target = self.args['dir']
1821 if target[-1] == '\\':
1822 target = target[0:-1]
1827 shutil.rmtree(target)
1832 self.write(Action.INSTALL, self.name, ['Installing ...'])
1833 callback = functools.partial(self.write, Action.INSTALL, self.name)
1834 cmd = 'git clone {0} {1} {2} {3} 2>&1'.format(
1835 '' if self.tag else G_CLONE_OPT, G_PROGRESS, self.args['uri'],
1837 com = Command(cmd, None, G_TIMEOUT, callback, clean(target))
1838 result = com.execute(G_RETRIES)
1839 self.write(Action.DONE, self.name, result[-1:])
1842 cmd = 'git rev-parse --abbrev-ref HEAD 2>&1 && git config -f .git/config remote.origin.url'
1843 command = Command(cmd, self.args['dir'], G_TIMEOUT,)
1844 result = command.execute(G_RETRIES)
1848 actual_uri = self.repo_uri()
1849 expect_uri = self.args['uri']
1850 regex = re.compile(r'^(?:\w+://)?(?:[^@/]*@)?([^:/]*(?::[0-9]*)?)[:/](.*?)(?:\.git)?/?$')
1851 ma = regex.match(actual_uri)
1852 mb = regex.match(expect_uri)
1853 if ma is None or mb is None or ma.groups() != mb.groups():
1855 'Invalid URI: {0}'.format(actual_uri),
1856 'Expected {0}'.format(expect_uri),
1857 'PlugClean required.']
1858 raise InvalidURI(msg)
1861 self.write(Action.UPDATE, self.name, ['Updating ...'])
1862 callback = functools.partial(self.write, Action.UPDATE, self.name)
1863 fetch_opt = '--depth 99999999' if self.tag and os.path.isfile(os.path.join(self.args['dir'], '.git/shallow')) else ''
1864 cmd = 'git fetch {0} {1} 2>&1'.format(fetch_opt, G_PROGRESS)
1865 com = Command(cmd, self.args['dir'], G_TIMEOUT, callback)
1866 result = com.execute(G_RETRIES)
1867 self.write(Action.DONE, self.name, result[-1:])
1869 self.write(Action.DONE, self.name, ['Already installed'])
1871 def write(self, action, name, msg):
1872 self.buf_q.put((action, name, msg))
1874 class PlugThread(thr.Thread):
1875 def __init__(self, tname, args):
1876 super(PlugThread, self).__init__()
1881 thr.current_thread().name = self.tname
1882 buf_q, work_q, lock = self.args
1885 while not G_STOP.is_set():
1886 name, args = work_q.get_nowait()
1887 plug = Plugin(name, args, buf_q, lock)
1893 class RefreshThread(thr.Thread):
1894 def __init__(self, lock):
1895 super(RefreshThread, self).__init__()
1902 thread_vim_command('noautocmd normal! a')
1906 self.running = False
1909 def thread_vim_command(cmd):
1910 vim.session.threadsafe_call(lambda: vim.command(cmd))
1912 def thread_vim_command(cmd):
1916 return '"' + name.replace('"', '\"') + '"'
1918 def nonblock_read(fname):
1919 """ Read a file with nonblock flag. Return the last line. """
1920 fread = os.open(fname, os.O_RDONLY | os.O_NONBLOCK)
1921 buf = os.read(fread, 100000).decode('utf-8', 'replace')
1924 line = buf.rstrip('\r\n')
1925 left = max(line.rfind('\r'), line.rfind('\n'))
1933 thr.current_thread().name = 'main'
1934 nthreads = int(vim.eval('s:update.threads'))
1935 plugs = vim.eval('s:update.todo')
1936 mac_gui = vim.eval('s:mac_gui') == '1'
1939 buf = Buffer(lock, len(plugs), G_PULL)
1940 buf_q, work_q = queue.Queue(), queue.Queue()
1941 for work in plugs.items():
1944 start_cnt = thr.active_count()
1945 for num in range(nthreads):
1946 tname = 'PlugT-{0:02}'.format(num)
1947 thread = PlugThread(tname, (buf_q, work_q, lock))
1950 rthread = RefreshThread(lock)
1953 while not buf_q.empty() or thr.active_count() != start_cnt:
1955 action, name, msg = buf_q.get(True, 0.25)
1956 buf.write(action, name, ['OK'] if not msg else msg)
1960 except KeyboardInterrupt:
1971 function! s:update_ruby()
1974 SEP = ["\r", "\n", nil]
1978 char = readchar rescue return
1979 if SEP.include? char.chr
1988 end unless defined?(PlugStream)
1991 %["#{arg.gsub('"', '\"')}"]
1996 if /mswin|mingw|bccwin/ =~ RUBY_PLATFORM
1997 pids.each { |pid| Process.kill 'INT', pid.to_i rescue nil }
1999 unless `which pgrep 2> /dev/null`.empty?
2001 until children.empty?
2002 children = children.map { |pid|
2003 `pgrep -P #{pid}`.lines.map { |l| l.chomp }
2008 pids.each { |pid| Process.kill 'TERM', pid.to_i rescue nil }
2012 def compare_git_uri a, b
2013 regex = %r{^(?:\w+://)?(?:[^@/]*@)?([^:/]*(?::[0-9]*)?)[:/](.*?)(?:\.git)?/?$}
2014 regex.match(a).to_a.drop(1) == regex.match(b).to_a.drop(1)
2021 iswin = VIM::evaluate('s:is_win').to_i == 1
2022 pull = VIM::evaluate('s:update.pull').to_i == 1
2023 base = VIM::evaluate('g:plug_home')
2024 all = VIM::evaluate('s:update.todo')
2025 limit = VIM::evaluate('get(g:, "plug_timeout", 60)')
2026 tries = VIM::evaluate('get(g:, "plug_retries", 2)') + 1
2027 nthr = VIM::evaluate('s:update.threads').to_i
2028 maxy = VIM::evaluate('winheight(".")').to_i
2029 vim7 = VIM::evaluate('v:version').to_i <= 703 && RUBY_PLATFORM =~ /darwin/
2030 cd = iswin ? 'cd /d' : 'cd'
2031 tot = VIM::evaluate('len(s:update.todo)') || 0
2033 skip = 'Already installed'
2035 take1 = proc { mtx.synchronize { running && all.shift } }
2038 $curbuf[1] = "#{pull ? 'Updating' : 'Installing'} plugins (#{cnt}/#{tot})"
2039 $curbuf[2] = '[' + bar.ljust(tot) + ']'
2040 VIM::command('normal! 2G')
2041 VIM::command('redraw')
2043 where = proc { |name| (1..($curbuf.length)).find { |l| $curbuf[l] =~ /^[-+x*] #{name}:/ } }
2044 log = proc { |name, result, type|
2046 ing = ![true, false].include?(type)
2047 bar += type ? '=' : 'x' unless ing
2049 when :install then '+' when :update then '*'
2050 when true, nil then '-' else
2051 VIM::command("call add(s:update.errors, '#{name}')")
2055 if type || type.nil?
2056 ["#{b} #{name}: #{result.lines.to_a.last || 'OK'}"]
2057 elsif result =~ /^Interrupted|^Timeout/
2058 ["#{b} #{name}: #{result}"]
2060 ["#{b} #{name}"] + result.lines.map { |l| " " << l }
2062 if lnum = where.call(name)
2064 lnum = 4 if ing && lnum > maxy
2066 result.each_with_index do |line, offset|
2067 $curbuf.append((lnum || 4) - 1 + offset, line.gsub(/\e\[./, '').chomp)
2072 bt = proc { |cmd, name, type, cleanup|
2080 Timeout::timeout(timeout) do
2081 tmp = VIM::evaluate('tempname()')
2082 system("(#{cmd}) > #{tmp}")
2083 data = File.read(tmp).chomp
2084 File.unlink tmp rescue nil
2087 fd = IO.popen(cmd).extend(PlugStream)
2089 log_prob = 1.0 / nthr
2090 while line = Timeout::timeout(timeout) { fd.get_line }
2092 log.call name, line.chomp, type if name && (first_line || rand < log_prob)
2097 [$? == 0, data.chomp]
2098 rescue Timeout::Error, Interrupt => e
2099 if fd && !fd.closed?
2103 cleanup.call if cleanup
2104 if e.is_a?(Timeout::Error) && tried < tries
2105 3.downto(1) do |countdown|
2106 s = countdown > 1 ? 's' : ''
2107 log.call name, "Timeout. Will retry in #{countdown} second#{s} ...", type
2110 log.call name, 'Retrying ...', type
2113 [false, e.is_a?(Interrupt) ? "Interrupted!" : "Timeout!"]
2116 main = Thread.current
2118 watcher = Thread.new {
2120 while VIM::evaluate('getchar(1)')
2124 require 'io/console' # >= Ruby 1.9
2125 nil until IO.console.getch == 3.chr
2129 threads.each { |t| t.raise Interrupt } unless vim7
2131 threads.each { |t| t.join rescue nil }
2134 refresh = Thread.new {
2137 break unless running
2138 VIM::command('noautocmd normal! a')
2142 } if VIM::evaluate('s:mac_gui') == 1
2144 clone_opt = VIM::evaluate('s:clone_opt').join(' ')
2145 progress = VIM::evaluate('s:progress_opt(1)')
2148 threads << Thread.new {
2149 while pair = take1.call
2151 dir, uri, tag = pair.last.values_at *%w[dir uri tag]
2152 exists = File.directory? dir
2155 chdir = "#{cd} #{iswin ? dir : esc(dir)}"
2156 ret, data = bt.call "#{chdir} && git rev-parse --abbrev-ref HEAD 2>&1 && git config -f .git/config remote.origin.url", nil, nil, nil
2157 current_uri = data.lines.to_a.last
2159 if data =~ /^Interrupted|^Timeout/
2162 [false, [data.chomp, "PlugClean required."].join($/)]
2164 elsif !compare_git_uri(current_uri, uri)
2165 [false, ["Invalid URI: #{current_uri}",
2167 "PlugClean required."].join($/)]
2170 log.call name, 'Updating ...', :update
2171 fetch_opt = (tag && File.exist?(File.join(dir, '.git/shallow'))) ? '--depth 99999999' : ''
2172 bt.call "#{chdir} && git fetch #{fetch_opt} #{progress} 2>&1", name, :update, nil
2178 d = esc dir.sub(%r{[\\/]+$}, '')
2179 log.call name, 'Installing ...', :install
2180 bt.call "git clone #{clone_opt unless tag} #{progress} #{uri} #{d} 2>&1", name, :install, proc {
2184 mtx.synchronize { VIM::command("let s:update.new['#{name}'] = 1") } if !exists && ok
2185 log.call name, result, ok
2190 threads.each { |t| t.join rescue nil }
2192 refresh.kill if refresh
2197 function! s:shellesc_cmd(arg, script)
2198 let escaped = substitute('"'.a:arg.'"', '[&|<>()@^!"]', '^&', 'g')
2199 return substitute(escaped, '%', (a:script ? '%' : '^') . '&', 'g')
2202 function! s:shellesc_ps1(arg)
2203 return "'".substitute(escape(a:arg, '\"'), "'", "''", 'g')."'"
2206 function! s:shellesc_sh(arg)
2207 return "'".substitute(a:arg, "'", "'\\\\''", 'g')."'"
2210 " Escape the shell argument based on the shell.
2211 " Vim and Neovim's shellescape() are insufficient.
2212 " 1. shellslash determines whether to use single/double quotes.
2213 " Double-quote escaping is fragile for cmd.exe.
2214 " 2. It does not work for powershell.
2215 " 3. It does not work for *sh shells if the command is executed
2216 " via cmd.exe (ie. cmd.exe /c sh -c command command_args)
2217 " 4. It does not support batchfile syntax.
2219 " Accepts an optional dictionary with the following keys:
2220 " - shell: same as Vim/Neovim 'shell' option.
2221 " If unset, fallback to 'cmd.exe' on Windows or 'sh'.
2222 " - script: If truthy and shell is cmd.exe, escape for batchfile syntax.
2223 function! plug#shellescape(arg, ...)
2224 if a:arg =~# '^[A-Za-z0-9_/:.-]\+$'
2227 let opts = a:0 > 0 && type(a:1) == s:TYPE.dict ? a:1 : {}
2228 let shell = get(opts, 'shell', s:is_win ? 'cmd.exe' : 'sh')
2229 let script = get(opts, 'script', 1)
2230 if shell =~# 'cmd\(\.exe\)\?$'
2231 return s:shellesc_cmd(a:arg, script)
2232 elseif s:is_powershell(shell)
2233 return s:shellesc_ps1(a:arg)
2235 return s:shellesc_sh(a:arg)
2238 function! s:glob_dir(path)
2239 return map(filter(s:glob(a:path, '**'), 'isdirectory(v:val)'), 's:dirpath(v:val)')
2242 function! s:progress_bar(line, bar, total)
2243 call setline(a:line, '[' . s:lpad(a:bar, a:total) . ']')
2246 function! s:compare_git_uri(a, b)
2247 " See `git help clone'
2248 " https:// [user@] github.com[:port] / junegunn/vim-plug [.git]
2249 " [git@] github.com[:port] : junegunn/vim-plug [.git]
2250 " file:// / junegunn/vim-plug [/]
2251 " / junegunn/vim-plug [/]
2252 let pat = '^\%(\w\+://\)\='.'\%([^@/]*@\)\='.'\([^:/]*\%(:[0-9]*\)\=\)'.'[:/]'.'\(.\{-}\)'.'\%(\.git\)\=/\?$'
2253 let ma = matchlist(a:a, pat)
2254 let mb = matchlist(a:b, pat)
2255 return ma[1:2] ==# mb[1:2]
2258 function! s:format_message(bullet, name, message)
2260 return [printf('%s %s: %s', a:bullet, a:name, s:lastline(a:message))]
2262 let lines = map(s:lines(a:message), '" ".v:val')
2263 return extend([printf('x %s:', a:name)], lines)
2267 function! s:with_cd(cmd, dir, ...)
2268 let script = a:0 > 0 ? a:1 : 1
2269 return printf('cd%s %s && %s', s:is_win ? ' /d' : '', plug#shellescape(a:dir, {'script': script}), a:cmd)
2272 function! s:system(cmd, ...)
2275 let [sh, shellcmdflag, shrd] = s:chsh(1)
2276 if type(a:cmd) == s:TYPE.list
2277 " Neovim's system() supports list argument to bypass the shell
2278 " but it cannot set the working directory for the command.
2279 " Assume that the command does not rely on the shell.
2280 if has('nvim') && a:0 == 0
2281 return system(a:cmd)
2283 let cmd = join(map(copy(a:cmd), 'plug#shellescape(v:val, {"shell": &shell, "script": 0})'))
2284 if s:is_powershell(&shell)
2285 let cmd = '& ' . cmd
2291 let cmd = s:with_cd(cmd, a:1, type(a:cmd) != s:TYPE.list)
2293 if s:is_win && type(a:cmd) != s:TYPE.list
2294 let [batchfile, cmd] = s:batchfile(cmd)
2298 let [&shell, &shellcmdflag, &shellredir] = [sh, shellcmdflag, shrd]
2299 if s:is_win && filereadable(batchfile)
2300 call delete(batchfile)
2305 function! s:system_chomp(...)
2306 let ret = call('s:system', a:000)
2307 return v:shell_error ? '' : substitute(ret, '\n$', '', '')
2310 function! s:git_validate(spec, check_branch)
2312 if isdirectory(a:spec.dir)
2313 let result = [s:git_local_branch(a:spec.dir), s:git_origin_url(a:spec.dir)]
2314 let remote = result[-1]
2316 let err = join([remote, 'PlugClean required.'], "\n")
2317 elseif !s:compare_git_uri(remote, a:spec.uri)
2318 let err = join(['Invalid URI: '.remote,
2319 \ 'Expected: '.a:spec.uri,
2320 \ 'PlugClean required.'], "\n")
2321 elseif a:check_branch && has_key(a:spec, 'commit')
2322 let sha = s:git_revision(a:spec.dir)
2324 let err = join(add(result, 'PlugClean required.'), "\n")
2325 elseif !s:hash_match(sha, a:spec.commit)
2326 let err = join([printf('Invalid HEAD (expected: %s, actual: %s)',
2327 \ a:spec.commit[:6], sha[:6]),
2328 \ 'PlugUpdate required.'], "\n")
2330 elseif a:check_branch
2331 let current_branch = result[0]
2333 let origin_branch = s:git_origin_branch(a:spec)
2334 if has_key(a:spec, 'tag')
2335 let tag = s:system_chomp('git describe --exact-match --tags HEAD 2>&1', a:spec.dir)
2336 if a:spec.tag !=# tag && a:spec.tag !~ '\*'
2337 let err = printf('Invalid tag: %s (expected: %s). Try PlugUpdate.',
2338 \ (empty(tag) ? 'N/A' : tag), a:spec.tag)
2341 elseif origin_branch !=# current_branch
2342 let err = printf('Invalid branch: %s (expected: %s). Try PlugUpdate.',
2343 \ current_branch, origin_branch)
2346 let [ahead, behind] = split(s:lastline(s:system([
2347 \ 'git', 'rev-list', '--count', '--left-right',
2348 \ printf('HEAD...origin/%s', origin_branch)
2349 \ ], a:spec.dir)), '\t')
2350 if !v:shell_error && ahead
2352 " Only mention PlugClean if diverged, otherwise it's likely to be
2353 " pushable (and probably not that messed up).
2355 \ "Diverged from origin/%s (%d commit(s) ahead and %d commit(s) behind!\n"
2356 \ .'Backup local changes and run PlugClean and PlugUpdate to reinstall it.', origin_branch, ahead, behind)
2358 let err = printf("Ahead of origin/%s by %d commit(s).\n"
2359 \ .'Cannot update until local changes are pushed.',
2360 \ origin_branch, ahead)
2366 let err = 'Not found'
2368 return [err, err =~# 'PlugClean']
2371 function! s:rm_rf(dir)
2372 if isdirectory(a:dir)
2373 return s:system(s:is_win
2374 \ ? 'rmdir /S /Q '.plug#shellescape(a:dir)
2375 \ : ['rm', '-rf', a:dir])
2379 function! s:clean(force)
2381 call append(0, 'Searching for invalid plugins in '.g:plug_home)
2384 " List of valid directories
2387 let [cnt, total] = [0, len(g:plugs)]
2388 for [name, spec] in items(g:plugs)
2389 if !s:is_managed(name)
2390 call add(dirs, spec.dir)
2392 let [err, clean] = s:git_validate(spec, 1)
2394 let errs[spec.dir] = s:lines(err)[0]
2396 call add(dirs, spec.dir)
2400 call s:progress_bar(2, repeat('=', cnt), total)
2407 let allowed[s:dirpath(s:plug_fnamemodify(dir, ':h:h'))] = 1
2408 let allowed[dir] = 1
2409 for child in s:glob_dir(dir)
2410 let allowed[child] = 1
2415 let found = sort(s:glob_dir(g:plug_home))
2417 let f = remove(found, 0)
2418 if !has_key(allowed, f) && isdirectory(f)
2420 call append(line('$'), '- ' . f)
2422 call append(line('$'), ' ' . errs[f])
2424 let found = filter(found, 'stridx(v:val, f) != 0')
2431 call append(line('$'), 'Already clean.')
2433 let s:clean_count = 0
2434 call append(3, ['Directories to delete:', ''])
2436 if a:force || s:ask_no_interrupt('Delete all directories?')
2437 call s:delete([6, line('$')], 1)
2439 call setline(4, 'Cancelled.')
2440 nnoremap <silent> <buffer> d :set opfunc=<sid>delete_op<cr>g@
2441 nmap <silent> <buffer> dd d_
2442 xnoremap <silent> <buffer> d :<c-u>call <sid>delete_op(visualmode(), 1)<cr>
2443 echo 'Delete the lines (d{motion}) to delete the corresponding directories'
2447 setlocal nomodifiable
2450 function! s:delete_op(type, ...)
2451 call s:delete(a:0 ? [line("'<"), line("'>")] : [line("'["), line("']")], 0)
2454 function! s:delete(range, force)
2455 let [l1, l2] = a:range
2459 let line = getline(l1)
2460 if line =~ '^- ' && isdirectory(line[2:])
2463 let answer = force ? 1 : s:ask('Delete '.line[2:].'?', 1)
2464 let force = force || answer > 1
2466 let err = s:rm_rf(line[2:])
2469 call setline(l1, '~'.line[1:])
2470 let s:clean_count += 1
2473 call append(l1 - 1, s:format_message('x', line[1:], err))
2474 let l2 += len(s:lines(err))
2477 let msg = printf('Removed %d directories.', s:clean_count)
2479 let msg .= printf(' Failed to remove %d directories.', err_count)
2481 call setline(4, msg)
2482 setlocal nomodifiable
2489 function! s:upgrade()
2490 echo 'Downloading the latest version of vim-plug'
2492 let tmp = s:plug_tempname()
2493 let new = tmp . '/plug.vim'
2496 let out = s:system(['git', 'clone', '--depth', '1', s:plug_src, tmp])
2498 return s:err('Error upgrading vim-plug: '. out)
2501 if readfile(s:me) ==# readfile(new)
2502 echo 'vim-plug is already up-to-date'
2505 call rename(s:me, s:me . '.old')
2506 call rename(new, s:me)
2508 echo 'vim-plug has been upgraded'
2512 silent! call s:rm_rf(tmp)
2516 function! s:upgrade_specs()
2517 for spec in values(g:plugs)
2518 let spec.frozen = get(spec, 'frozen', 0)
2522 function! s:status()
2524 call append(0, 'Checking plugins')
2529 let [cnt, total] = [0, len(g:plugs)]
2530 for [name, spec] in items(g:plugs)
2531 let is_dir = isdirectory(spec.dir)
2532 if has_key(spec, 'uri')
2534 let [err, _] = s:git_validate(spec, 1)
2535 let [valid, msg] = [empty(err), empty(err) ? 'OK' : err]
2537 let [valid, msg] = [0, 'Not found. Try PlugInstall.']
2541 let [valid, msg] = [1, 'OK']
2543 let [valid, msg] = [0, 'Not found.']
2548 " `s:loaded` entry can be missing if PlugUpgraded
2549 if is_dir && get(s:loaded, name, -1) == 0
2551 let msg .= ' (not loaded)'
2553 call s:progress_bar(2, repeat('=', cnt), total)
2554 call append(3, s:format_message(valid ? '-' : 'x', name, msg))
2558 call setline(1, 'Finished. '.ecnt.' error(s).')
2560 setlocal nomodifiable
2562 echo "Press 'L' on each line to load plugin, or 'U' to update"
2563 nnoremap <silent> <buffer> L :call <SID>status_load(line('.'))<cr>
2564 xnoremap <silent> <buffer> L :call <SID>status_load(line('.'))<cr>
2568 function! s:extract_name(str, prefix, suffix)
2569 return matchstr(a:str, '^'.a:prefix.' \zs[^:]\+\ze:.*'.a:suffix.'$')
2572 function! s:status_load(lnum)
2573 let line = getline(a:lnum)
2574 let name = s:extract_name(line, '-', '(not loaded)')
2576 call plug#load(name)
2578 call setline(a:lnum, substitute(line, ' (not loaded)$', '', ''))
2579 setlocal nomodifiable
2583 function! s:status_update() range
2584 let lines = getline(a:firstline, a:lastline)
2585 let names = filter(map(lines, 's:extract_name(v:val, "[x-]", "")'), '!empty(v:val)')
2588 execute 'PlugUpdate' join(names)
2592 function! s:is_preview_window_open()
2600 function! s:find_name(lnum)
2601 for lnum in reverse(range(1, a:lnum))
2602 let line = getline(lnum)
2606 let name = s:extract_name(line, '-', '')
2614 function! s:preview_commit()
2615 if b:plug_preview < 0
2616 let b:plug_preview = !s:is_preview_window_open()
2619 let sha = matchstr(getline('.'), '^ \X*\zs[0-9a-f]\{7,9}')
2624 let name = s:find_name(line('.'))
2625 if empty(name) || !has_key(g:plugs, name) || !isdirectory(g:plugs[name].dir)
2629 if exists('g:plug_pwindow') && !s:is_preview_window_open()
2630 execute g:plug_pwindow
2636 setlocal previewwindow filetype=git buftype=nofile nobuflisted modifiable
2639 let [sh, shellcmdflag, shrd] = s:chsh(1)
2640 let cmd = 'cd '.plug#shellescape(g:plugs[name].dir).' && git show --no-color --pretty=medium '.sha
2642 let [batchfile, cmd] = s:batchfile(cmd)
2644 execute 'silent %!' cmd
2646 let [&shell, &shellcmdflag, &shellredir] = [sh, shellcmdflag, shrd]
2647 if s:is_win && filereadable(batchfile)
2648 call delete(batchfile)
2651 setlocal nomodifiable
2652 nnoremap <silent> <buffer> q :q<cr>
2656 function! s:section(flags)
2657 call search('\(^[x-] \)\@<=[^:]\+:', a:flags)
2660 function! s:format_git_log(line)
2662 let tokens = split(a:line, nr2char(1))
2664 return indent.substitute(a:line, '\s*$', '', '')
2666 let [graph, sha, refs, subject, date] = tokens
2667 let tag = matchstr(refs, 'tag: [^,)]\+')
2668 let tag = empty(tag) ? ' ' : ' ('.tag.') '
2669 return printf('%s%s%s%s%s (%s)', indent, graph, sha, tag, subject, date)
2672 function! s:append_ul(lnum, text)
2673 call append(a:lnum, ['', a:text, repeat('-', len(a:text))])
2678 call append(0, ['Collecting changes ...', ''])
2681 let total = filter(copy(g:plugs), 's:is_managed(v:key) && isdirectory(v:val.dir)')
2682 call s:progress_bar(2, bar, len(total))
2683 for origin in [1, 0]
2684 let plugs = reverse(sort(items(filter(copy(total), (origin ? '' : '!').'(has_key(v:val, "commit") || has_key(v:val, "tag"))'))))
2688 call s:append_ul(2, origin ? 'Pending updates:' : 'Last update:')
2690 let branch = s:git_origin_branch(v)
2692 let range = origin ? '..origin/'.branch : 'HEAD@{1}..'
2693 let cmd = ['git', 'log', '--graph', '--color=never']
2694 if s:git_version_requirement(2, 10, 0)
2695 call add(cmd, '--no-show-signature')
2697 call extend(cmd, ['--pretty=format:%x01%h%x01%d%x01%s%x01%cr', range])
2698 if has_key(v, 'rtp')
2699 call extend(cmd, ['--', v.rtp])
2701 let diff = s:system_chomp(cmd, v.dir)
2703 let ref = has_key(v, 'tag') ? (' (tag: '.v.tag.')') : has_key(v, 'commit') ? (' '.v.commit) : ''
2704 call append(5, extend(['', '- '.k.':'.ref], map(s:lines(diff), 's:format_git_log(v:val)')))
2705 let cnts[origin] += 1
2709 call s:progress_bar(2, bar, len(total))
2714 call append(5, ['', 'N/A'])
2717 call setline(1, printf('%d plugin(s) updated.', cnts[0])
2718 \ . (cnts[1] ? printf(' %d plugin(s) have pending updates.', cnts[1]) : ''))
2720 if cnts[0] || cnts[1]
2721 nnoremap <silent> <buffer> <plug>(plug-preview) :silent! call <SID>preview_commit()<cr>
2722 if empty(maparg("\<cr>", 'n'))
2723 nmap <buffer> <cr> <plug>(plug-preview)
2725 if empty(maparg('o', 'n'))
2726 nmap <buffer> o <plug>(plug-preview)
2730 nnoremap <silent> <buffer> X :call <SID>revert()<cr>
2731 echo "Press 'X' on each block to revert the update"
2734 setlocal nomodifiable
2737 function! s:revert()
2738 if search('^Pending updates', 'bnW')
2742 let name = s:find_name(line('.'))
2743 if empty(name) || !has_key(g:plugs, name) ||
2744 \ input(printf('Revert the update of %s? (y/N) ', name)) !~? '^y'
2748 call s:system('git reset --hard HEAD@{1} && git checkout '.plug#shellescape(g:plugs[name].branch).' --', g:plugs[name].dir)
2751 setlocal nomodifiable
2755 function! s:snapshot(force, ...) abort
2758 call append(0, ['" Generated by vim-plug',
2759 \ '" '.strftime("%c"),
2760 \ '" :source this file in vim to restore the snapshot',
2761 \ '" or execute: vim -S snapshot.vim',
2762 \ '', '', 'PlugUpdate!'])
2764 let anchor = line('$') - 3
2765 let names = sort(keys(filter(copy(g:plugs),
2766 \'has_key(v:val, "uri") && !has_key(v:val, "commit") && isdirectory(v:val.dir)')))
2767 for name in reverse(names)
2768 let sha = s:git_revision(g:plugs[name].dir)
2770 call append(anchor, printf("silent! let g:plugs['%s'].commit = '%s'", name, sha))
2776 let fn = s:plug_expand(a:1)
2777 if filereadable(fn) && !(a:force || s:ask(a:1.' already exists. Overwrite?'))
2780 call writefile(getline(1, '$'), fn)
2781 echo 'Saved as '.a:1
2782 silent execute 'e' s:esc(fn)
2787 function! s:split_rtp()
2788 return split(&rtp, '\\\@<!,')
2791 let s:first_rtp = s:escrtp(get(s:split_rtp(), 0, ''))
2792 let s:last_rtp = s:escrtp(get(s:split_rtp(), -1, ''))
2794 if exists('g:plugs')
2795 let g:plugs_order = get(g:, 'plugs_order', keys(g:plugs))
2796 call s:upgrade_specs()
2797 call s:define_commands()
2800 let &cpo = s:cpo_save