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-\><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 " Set remote name, overriding a possible user git config's clone.defaultRemoteName
1212 let s:clone_opt = ['--origin', 'origin']
1213 if get(g:, 'plug_shallow', 1)
1214 call extend(s:clone_opt, ['--depth', '1'])
1215 if s:git_version_requirement(1, 7, 10)
1216 call add(s:clone_opt, '--no-single-branch')
1220 if has('win32unix') || has('wsl')
1221 call extend(s:clone_opt, ['-c', 'core.eol=lf', '-c', 'core.autocrlf=input'])
1224 let s:submodule_opt = s:git_version_requirement(2, 8) ? ' --jobs='.threads : ''
1226 " Python version requirement (>= 2.7)
1227 if python && !has('python3') && !ruby && !use_job && s:update.threads > 1
1229 silent python import platform; print platform.python_version()
1231 let python = s:version_requirement(
1232 \ map(split(split(pyv)[0], '\.'), 'str2nr(v:val)'), [2, 6])
1235 if (python || ruby) && s:update.threads > 1
1242 call s:update_ruby()
1244 call s:update_python()
1247 let lines = getline(4, '$')
1251 let name = s:extract_name(line, '.', '')
1252 if empty(name) || !has_key(printed, name)
1253 call append('$', line)
1255 let printed[name] = 1
1256 if line[0] == 'x' && index(s:update.errors, name) < 0
1257 call add(s:update.errors, name)
1264 call s:update_finish()
1268 while use_job && sync
1277 function! s:log4(name, msg)
1278 call setline(4, printf('- %s (%s)', a:msg, a:name))
1282 function! s:update_finish()
1283 if exists('s:git_terminal_prompt')
1284 let $GIT_TERMINAL_PROMPT = s:git_terminal_prompt
1287 call append(3, '- Updating ...') | 4
1288 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))'))
1289 let [pos, _] = s:logpos(name)
1293 if has_key(spec, 'commit')
1294 call s:log4(name, 'Checking out '.spec.commit)
1295 let out = s:checkout(spec)
1296 elseif has_key(spec, 'tag')
1299 let tags = s:lines(s:system('git tag --list '.plug#shellescape(tag).' --sort -version:refname 2>&1', spec.dir))
1300 if !v:shell_error && !empty(tags)
1302 call s:log4(name, printf('Latest tag for %s -> %s', spec.tag, tag))
1306 call s:log4(name, 'Checking out '.tag)
1307 let out = s:system('git checkout -q '.plug#shellescape(tag).' -- 2>&1', spec.dir)
1309 let branch = s:git_origin_branch(spec)
1310 call s:log4(name, 'Merging origin/'.s:esc(branch))
1311 let out = s:system('git checkout -q '.plug#shellescape(branch).' -- 2>&1'
1312 \. (has_key(s:update.new, name) ? '' : ('&& git merge --ff-only '.plug#shellescape('origin/'.branch).' 2>&1')), spec.dir)
1314 if !v:shell_error && filereadable(spec.dir.'/.gitmodules') &&
1315 \ (s:update.force || has_key(s:update.new, name) || s:is_updated(spec.dir))
1316 call s:log4(name, 'Updating submodules. This may take a while.')
1317 let out .= s:bang('git submodule update --init --recursive'.s:submodule_opt.' 2>&1', spec.dir)
1319 let msg = s:format_message(v:shell_error ? 'x': '-', name, out)
1321 call add(s:update.errors, name)
1322 call s:regress_bar()
1323 silent execute pos 'd _'
1324 call append(4, msg) | 4
1326 call setline(pos, msg[0])
1332 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")'))
1334 call s:warn('echom', v:exception)
1335 call s:warn('echo', '')
1338 call s:finish(s:update.pull)
1339 call setline(1, 'Updated. Elapsed time: ' . split(reltimestr(reltime(s:update.start)))[0] . ' sec.')
1340 call s:switch_out('normal! gg')
1344 function! s:job_abort()
1345 if (!s:nvim && !s:vim8) || !exists('s:jobs')
1349 for [name, j] in items(s:jobs)
1351 silent! call jobstop(j.jobid)
1353 silent! call job_stop(j.jobid)
1356 call s:rm_rf(g:plugs[name].dir)
1362 function! s:last_non_empty_line(lines)
1363 let len = len(a:lines)
1364 for idx in range(len)
1365 let line = a:lines[len-idx-1]
1373 function! s:job_out_cb(self, data) abort
1375 let data = remove(self.lines, -1) . a:data
1376 let lines = map(split(data, "\n", 1), 'split(v:val, "\r", 1)[-1]')
1377 call extend(self.lines, lines)
1378 " To reduce the number of buffer updates
1379 let self.tick = get(self, 'tick', -1) + 1
1380 if !self.running || self.tick % len(s:jobs) == 0
1381 let bullet = self.running ? (self.new ? '+' : '*') : (self.error ? 'x' : '-')
1382 let result = self.error ? join(self.lines, "\n") : s:last_non_empty_line(self.lines)
1383 call s:log(bullet, self.name, result)
1387 function! s:job_exit_cb(self, data) abort
1388 let a:self.running = 0
1389 let a:self.error = a:data != 0
1390 call s:reap(a:self.name)
1394 function! s:job_cb(fn, job, ch, data)
1395 if !s:plug_window_exists() " plug window closed
1396 return s:job_abort()
1398 call call(a:fn, [a:job, a:data])
1401 function! s:nvim_cb(job_id, data, event) dict abort
1402 return (a:event == 'stdout' || a:event == 'stderr') ?
1403 \ s:job_cb('s:job_out_cb', self, 0, join(a:data, "\n")) :
1404 \ s:job_cb('s:job_exit_cb', self, 0, a:data)
1407 function! s:spawn(name, cmd, opts)
1408 let job = { 'name': a:name, 'running': 1, 'error': 0, 'lines': [''],
1409 \ 'new': get(a:opts, 'new', 0) }
1410 let s:jobs[a:name] = job
1413 if has_key(a:opts, 'dir')
1414 let job.cwd = a:opts.dir
1418 \ 'on_stdout': function('s:nvim_cb'),
1419 \ 'on_stderr': function('s:nvim_cb'),
1420 \ 'on_exit': function('s:nvim_cb'),
1422 let jid = s:plug_call('jobstart', argv, job)
1428 let job.lines = [jid < 0 ? argv[0].' is not executable' :
1429 \ 'Invalid arguments (or job table is full)']
1432 let cmd = join(map(copy(a:cmd), 'plug#shellescape(v:val, {"script": 0})'))
1433 if has_key(a:opts, 'dir')
1434 let cmd = s:with_cd(cmd, a:opts.dir, 0)
1436 let argv = s:is_win ? ['cmd', '/s', '/c', '"'.cmd.'"'] : ['sh', '-c', cmd]
1437 let jid = job_start(s:is_win ? join(argv, ' ') : argv, {
1438 \ 'out_cb': function('s:job_cb', ['s:job_out_cb', job]),
1439 \ 'err_cb': function('s:job_cb', ['s:job_out_cb', job]),
1440 \ 'exit_cb': function('s:job_cb', ['s:job_exit_cb', job]),
1441 \ 'err_mode': 'raw',
1444 if job_status(jid) == 'run'
1449 let job.lines = ['Failed to start job']
1452 let job.lines = s:lines(call('s:system', has_key(a:opts, 'dir') ? [a:cmd, a:opts.dir] : [a:cmd]))
1453 let job.error = v:shell_error != 0
1458 function! s:reap(name)
1459 let job = s:jobs[a:name]
1461 call add(s:update.errors, a:name)
1462 elseif get(job, 'new', 0)
1463 let s:update.new[a:name] = 1
1465 let s:update.bar .= job.error ? 'x' : '='
1467 let bullet = job.error ? 'x' : '-'
1468 let result = job.error ? join(job.lines, "\n") : s:last_non_empty_line(job.lines)
1469 call s:log(bullet, a:name, empty(result) ? 'OK' : result)
1472 call remove(s:jobs, a:name)
1477 let total = len(s:update.all)
1478 call setline(1, (s:update.pull ? 'Updating' : 'Installing').
1479 \ ' plugins ('.len(s:update.bar).'/'.total.')')
1480 call s:progress_bar(2, s:update.bar, total)
1485 function! s:logpos(name)
1487 for i in range(4, max > 4 ? max : 4)
1488 if getline(i) =~# '^[-+x*] '.a:name.':'
1489 for j in range(i + 1, max > 5 ? max : 5)
1490 if getline(j) !~ '^ '
1500 function! s:log(bullet, name, lines)
1502 let [b, e] = s:logpos(a:name)
1504 silent execute printf('%d,%d d _', b, e)
1505 if b > winheight('.')
1511 " FIXME For some reason, nomodifiable is set after :d in vim8
1513 call append(b - 1, s:format_message(a:bullet, a:name, a:lines))
1518 function! s:update_vim()
1526 let pull = s:update.pull
1527 let prog = s:progress_opt(s:nvim || s:vim8)
1528 while 1 " Without TCO, Vim stack is bound to explode
1529 if empty(s:update.todo)
1530 if empty(s:jobs) && !s:update.fin
1531 call s:update_finish()
1532 let s:update.fin = 1
1537 let name = keys(s:update.todo)[0]
1538 let spec = remove(s:update.todo, name)
1539 let new = empty(globpath(spec.dir, '.git', 1))
1541 call s:log(new ? '+' : '*', name, pull ? 'Updating ...' : 'Installing ...')
1544 let has_tag = has_key(spec, 'tag')
1546 let [error, _] = s:git_validate(spec, 0)
1549 let cmd = s:git_version_requirement(2) ? ['git', '-c', 'credential.helper=', 'fetch'] : ['git', 'fetch']
1550 if has_tag && !empty(globpath(spec.dir, '.git/shallow'))
1551 call extend(cmd, ['--depth', '99999999'])
1556 call s:spawn(name, cmd, { 'dir': spec.dir })
1558 let s:jobs[name] = { 'running': 0, 'lines': ['Already installed'], 'error': 0 }
1561 let s:jobs[name] = { 'running': 0, 'lines': s:lines(error), 'error': 1 }
1564 let cmd = ['git', 'clone']
1566 call extend(cmd, s:clone_opt)
1571 call s:spawn(name, extend(cmd, [spec.uri, s:trim(spec.dir)]), { 'new': 1 })
1574 if !s:jobs[name].running
1577 if len(s:jobs) >= s:update.threads
1583 function! s:update_python()
1584 let py_exe = has('python') ? 'python' : 'python3'
1585 execute py_exe "<< EOF"
1592 import Queue as queue
1599 import threading as thr
1604 G_NVIM = vim.eval("has('nvim')") == '1'
1605 G_PULL = vim.eval('s:update.pull') == '1'
1606 G_RETRIES = int(vim.eval('get(g:, "plug_retries", 2)')) + 1
1607 G_TIMEOUT = int(vim.eval('get(g:, "plug_timeout", 60)'))
1608 G_CLONE_OPT = ' '.join(vim.eval('s:clone_opt'))
1609 G_PROGRESS = vim.eval('s:progress_opt(1)')
1610 G_LOG_PROB = 1.0 / int(vim.eval('s:update.threads'))
1611 G_STOP = thr.Event()
1612 G_IS_WIN = vim.eval('s:is_win') == '1'
1614 class PlugError(Exception):
1615 def __init__(self, msg):
1617 class CmdTimedOut(PlugError):
1619 class CmdFailed(PlugError):
1621 class InvalidURI(PlugError):
1623 class Action(object):
1624 INSTALL, UPDATE, ERROR, DONE = ['+', '*', 'x', '-']
1626 class Buffer(object):
1627 def __init__(self, lock, num_plugs, is_pull):
1629 self.event = 'Updating' if is_pull else 'Installing'
1631 self.maxy = int(vim.eval('winheight(".")'))
1632 self.num_plugs = num_plugs
1634 def __where(self, name):
1635 """ Find first line with name in current buffer. Return line num. """
1636 found, lnum = False, 0
1637 matcher = re.compile('^[-+x*] {0}:'.format(name))
1638 for line in vim.current.buffer:
1639 if matcher.search(line) is not None:
1649 curbuf = vim.current.buffer
1650 curbuf[0] = self.event + ' plugins ({0}/{1})'.format(len(self.bar), self.num_plugs)
1652 num_spaces = self.num_plugs - len(self.bar)
1653 curbuf[1] = '[{0}{1}]'.format(self.bar, num_spaces * ' ')
1656 vim.command('normal! 2G')
1657 vim.command('redraw')
1659 def write(self, action, name, lines):
1660 first, rest = lines[0], lines[1:]
1661 msg = ['{0} {1}{2}{3}'.format(action, name, ': ' if first else '', first)]
1662 msg.extend([' ' + line for line in rest])
1665 if action == Action.ERROR:
1667 vim.command("call add(s:update.errors, '{0}')".format(name))
1668 elif action == Action.DONE:
1671 curbuf = vim.current.buffer
1672 lnum = self.__where(name)
1673 if lnum != -1: # Found matching line num
1675 if lnum > self.maxy and action in set([Action.INSTALL, Action.UPDATE]):
1679 curbuf.append(msg, lnum)
1685 class Command(object):
1686 CD = 'cd /d' if G_IS_WIN else 'cd'
1688 def __init__(self, cmd, cmd_dir=None, timeout=60, cb=None, clean=None):
1691 self.cmd = '{0} {1} && {2}'.format(Command.CD, cmd_dir, self.cmd)
1692 self.timeout = timeout
1693 self.callback = cb if cb else (lambda msg: None)
1694 self.clean = clean if clean else (lambda: None)
1699 """ Returns true only if command still running. """
1700 return self.proc and self.proc.poll() is None
1702 def execute(self, ntries=3):
1703 """ Execute the command with ntries if CmdTimedOut.
1704 Returns the output of the command if no Exception.
1706 attempt, finished, limit = 0, False, self.timeout
1711 result = self.try_command()
1715 if attempt != ntries:
1717 self.timeout += limit
1721 def notify_retry(self):
1722 """ Retry required for command, notify user. """
1723 for count in range(3, 0, -1):
1725 raise KeyboardInterrupt
1726 msg = 'Timeout. Will retry in {0} second{1} ...'.format(
1727 count, 's' if count != 1 else '')
1728 self.callback([msg])
1730 self.callback(['Retrying ...'])
1732 def try_command(self):
1733 """ Execute a cmd & poll for callback. Returns list of output.
1734 Raises CmdFailed -> return code for Popen isn't 0
1735 Raises CmdTimedOut -> command exceeded timeout without new output
1740 tfile = tempfile.NamedTemporaryFile(mode='w+b')
1741 preexec_fn = not G_IS_WIN and os.setsid or None
1742 self.proc = subprocess.Popen(self.cmd, stdout=tfile,
1743 stderr=subprocess.STDOUT,
1744 stdin=subprocess.PIPE, shell=True,
1745 preexec_fn=preexec_fn)
1746 thrd = thr.Thread(target=(lambda proc: proc.wait()), args=(self.proc,))
1749 thread_not_started = True
1750 while thread_not_started:
1753 thread_not_started = False
1754 except RuntimeError:
1759 raise KeyboardInterrupt
1761 if first_line or random.random() < G_LOG_PROB:
1763 line = '' if G_IS_WIN else nonblock_read(tfile.name)
1765 self.callback([line])
1767 time_diff = time.time() - os.path.getmtime(tfile.name)
1768 if time_diff > self.timeout:
1769 raise CmdTimedOut(['Timeout!'])
1774 result = [line.decode('utf-8', 'replace').rstrip() for line in tfile]
1776 if self.proc.returncode != 0:
1777 raise CmdFailed([''] + result)
1784 def terminate(self):
1785 """ Terminate process and cleanup. """
1788 os.kill(self.proc.pid, signal.SIGINT)
1790 os.killpg(self.proc.pid, signal.SIGTERM)
1793 class Plugin(object):
1794 def __init__(self, name, args, buf_q, lock):
1799 self.tag = args.get('tag', 0)
1803 if os.path.exists(self.args['dir']):
1808 thread_vim_command("let s:update.new['{0}'] = 1".format(self.name))
1809 except PlugError as exc:
1810 self.write(Action.ERROR, self.name, exc.msg)
1811 except KeyboardInterrupt:
1813 self.write(Action.ERROR, self.name, ['Interrupted!'])
1815 # Any exception except those above print stack trace
1816 msg = 'Trace:\n{0}'.format(traceback.format_exc().rstrip())
1817 self.write(Action.ERROR, self.name, msg.split('\n'))
1821 target = self.args['dir']
1822 if target[-1] == '\\':
1823 target = target[0:-1]
1828 shutil.rmtree(target)
1833 self.write(Action.INSTALL, self.name, ['Installing ...'])
1834 callback = functools.partial(self.write, Action.INSTALL, self.name)
1835 cmd = 'git clone {0} {1} {2} {3} 2>&1'.format(
1836 '' if self.tag else G_CLONE_OPT, G_PROGRESS, self.args['uri'],
1838 com = Command(cmd, None, G_TIMEOUT, callback, clean(target))
1839 result = com.execute(G_RETRIES)
1840 self.write(Action.DONE, self.name, result[-1:])
1843 cmd = 'git rev-parse --abbrev-ref HEAD 2>&1 && git config -f .git/config remote.origin.url'
1844 command = Command(cmd, self.args['dir'], G_TIMEOUT,)
1845 result = command.execute(G_RETRIES)
1849 actual_uri = self.repo_uri()
1850 expect_uri = self.args['uri']
1851 regex = re.compile(r'^(?:\w+://)?(?:[^@/]*@)?([^:/]*(?::[0-9]*)?)[:/](.*?)(?:\.git)?/?$')
1852 ma = regex.match(actual_uri)
1853 mb = regex.match(expect_uri)
1854 if ma is None or mb is None or ma.groups() != mb.groups():
1856 'Invalid URI: {0}'.format(actual_uri),
1857 'Expected {0}'.format(expect_uri),
1858 'PlugClean required.']
1859 raise InvalidURI(msg)
1862 self.write(Action.UPDATE, self.name, ['Updating ...'])
1863 callback = functools.partial(self.write, Action.UPDATE, self.name)
1864 fetch_opt = '--depth 99999999' if self.tag and os.path.isfile(os.path.join(self.args['dir'], '.git/shallow')) else ''
1865 cmd = 'git fetch {0} {1} 2>&1'.format(fetch_opt, G_PROGRESS)
1866 com = Command(cmd, self.args['dir'], G_TIMEOUT, callback)
1867 result = com.execute(G_RETRIES)
1868 self.write(Action.DONE, self.name, result[-1:])
1870 self.write(Action.DONE, self.name, ['Already installed'])
1872 def write(self, action, name, msg):
1873 self.buf_q.put((action, name, msg))
1875 class PlugThread(thr.Thread):
1876 def __init__(self, tname, args):
1877 super(PlugThread, self).__init__()
1882 thr.current_thread().name = self.tname
1883 buf_q, work_q, lock = self.args
1886 while not G_STOP.is_set():
1887 name, args = work_q.get_nowait()
1888 plug = Plugin(name, args, buf_q, lock)
1894 class RefreshThread(thr.Thread):
1895 def __init__(self, lock):
1896 super(RefreshThread, self).__init__()
1903 thread_vim_command('noautocmd normal! a')
1907 self.running = False
1910 def thread_vim_command(cmd):
1911 vim.session.threadsafe_call(lambda: vim.command(cmd))
1913 def thread_vim_command(cmd):
1917 return '"' + name.replace('"', '\"') + '"'
1919 def nonblock_read(fname):
1920 """ Read a file with nonblock flag. Return the last line. """
1921 fread = os.open(fname, os.O_RDONLY | os.O_NONBLOCK)
1922 buf = os.read(fread, 100000).decode('utf-8', 'replace')
1925 line = buf.rstrip('\r\n')
1926 left = max(line.rfind('\r'), line.rfind('\n'))
1934 thr.current_thread().name = 'main'
1935 nthreads = int(vim.eval('s:update.threads'))
1936 plugs = vim.eval('s:update.todo')
1937 mac_gui = vim.eval('s:mac_gui') == '1'
1940 buf = Buffer(lock, len(plugs), G_PULL)
1941 buf_q, work_q = queue.Queue(), queue.Queue()
1942 for work in plugs.items():
1945 start_cnt = thr.active_count()
1946 for num in range(nthreads):
1947 tname = 'PlugT-{0:02}'.format(num)
1948 thread = PlugThread(tname, (buf_q, work_q, lock))
1951 rthread = RefreshThread(lock)
1954 while not buf_q.empty() or thr.active_count() != start_cnt:
1956 action, name, msg = buf_q.get(True, 0.25)
1957 buf.write(action, name, ['OK'] if not msg else msg)
1961 except KeyboardInterrupt:
1972 function! s:update_ruby()
1975 SEP = ["\r", "\n", nil]
1979 char = readchar rescue return
1980 if SEP.include? char.chr
1989 end unless defined?(PlugStream)
1992 %["#{arg.gsub('"', '\"')}"]
1997 if /mswin|mingw|bccwin/ =~ RUBY_PLATFORM
1998 pids.each { |pid| Process.kill 'INT', pid.to_i rescue nil }
2000 unless `which pgrep 2> /dev/null`.empty?
2002 until children.empty?
2003 children = children.map { |pid|
2004 `pgrep -P #{pid}`.lines.map { |l| l.chomp }
2009 pids.each { |pid| Process.kill 'TERM', pid.to_i rescue nil }
2013 def compare_git_uri a, b
2014 regex = %r{^(?:\w+://)?(?:[^@/]*@)?([^:/]*(?::[0-9]*)?)[:/](.*?)(?:\.git)?/?$}
2015 regex.match(a).to_a.drop(1) == regex.match(b).to_a.drop(1)
2022 iswin = VIM::evaluate('s:is_win').to_i == 1
2023 pull = VIM::evaluate('s:update.pull').to_i == 1
2024 base = VIM::evaluate('g:plug_home')
2025 all = VIM::evaluate('s:update.todo')
2026 limit = VIM::evaluate('get(g:, "plug_timeout", 60)')
2027 tries = VIM::evaluate('get(g:, "plug_retries", 2)') + 1
2028 nthr = VIM::evaluate('s:update.threads').to_i
2029 maxy = VIM::evaluate('winheight(".")').to_i
2030 vim7 = VIM::evaluate('v:version').to_i <= 703 && RUBY_PLATFORM =~ /darwin/
2031 cd = iswin ? 'cd /d' : 'cd'
2032 tot = VIM::evaluate('len(s:update.todo)') || 0
2034 skip = 'Already installed'
2036 take1 = proc { mtx.synchronize { running && all.shift } }
2039 $curbuf[1] = "#{pull ? 'Updating' : 'Installing'} plugins (#{cnt}/#{tot})"
2040 $curbuf[2] = '[' + bar.ljust(tot) + ']'
2041 VIM::command('normal! 2G')
2042 VIM::command('redraw')
2044 where = proc { |name| (1..($curbuf.length)).find { |l| $curbuf[l] =~ /^[-+x*] #{name}:/ } }
2045 log = proc { |name, result, type|
2047 ing = ![true, false].include?(type)
2048 bar += type ? '=' : 'x' unless ing
2050 when :install then '+' when :update then '*'
2051 when true, nil then '-' else
2052 VIM::command("call add(s:update.errors, '#{name}')")
2056 if type || type.nil?
2057 ["#{b} #{name}: #{result.lines.to_a.last || 'OK'}"]
2058 elsif result =~ /^Interrupted|^Timeout/
2059 ["#{b} #{name}: #{result}"]
2061 ["#{b} #{name}"] + result.lines.map { |l| " " << l }
2063 if lnum = where.call(name)
2065 lnum = 4 if ing && lnum > maxy
2067 result.each_with_index do |line, offset|
2068 $curbuf.append((lnum || 4) - 1 + offset, line.gsub(/\e\[./, '').chomp)
2073 bt = proc { |cmd, name, type, cleanup|
2081 Timeout::timeout(timeout) do
2082 tmp = VIM::evaluate('tempname()')
2083 system("(#{cmd}) > #{tmp}")
2084 data = File.read(tmp).chomp
2085 File.unlink tmp rescue nil
2088 fd = IO.popen(cmd).extend(PlugStream)
2090 log_prob = 1.0 / nthr
2091 while line = Timeout::timeout(timeout) { fd.get_line }
2093 log.call name, line.chomp, type if name && (first_line || rand < log_prob)
2098 [$? == 0, data.chomp]
2099 rescue Timeout::Error, Interrupt => e
2100 if fd && !fd.closed?
2104 cleanup.call if cleanup
2105 if e.is_a?(Timeout::Error) && tried < tries
2106 3.downto(1) do |countdown|
2107 s = countdown > 1 ? 's' : ''
2108 log.call name, "Timeout. Will retry in #{countdown} second#{s} ...", type
2111 log.call name, 'Retrying ...', type
2114 [false, e.is_a?(Interrupt) ? "Interrupted!" : "Timeout!"]
2117 main = Thread.current
2119 watcher = Thread.new {
2121 while VIM::evaluate('getchar(1)')
2125 require 'io/console' # >= Ruby 1.9
2126 nil until IO.console.getch == 3.chr
2130 threads.each { |t| t.raise Interrupt } unless vim7
2132 threads.each { |t| t.join rescue nil }
2135 refresh = Thread.new {
2138 break unless running
2139 VIM::command('noautocmd normal! a')
2143 } if VIM::evaluate('s:mac_gui') == 1
2145 clone_opt = VIM::evaluate('s:clone_opt').join(' ')
2146 progress = VIM::evaluate('s:progress_opt(1)')
2149 threads << Thread.new {
2150 while pair = take1.call
2152 dir, uri, tag = pair.last.values_at *%w[dir uri tag]
2153 exists = File.directory? dir
2156 chdir = "#{cd} #{iswin ? dir : esc(dir)}"
2157 ret, data = bt.call "#{chdir} && git rev-parse --abbrev-ref HEAD 2>&1 && git config -f .git/config remote.origin.url", nil, nil, nil
2158 current_uri = data.lines.to_a.last
2160 if data =~ /^Interrupted|^Timeout/
2163 [false, [data.chomp, "PlugClean required."].join($/)]
2165 elsif !compare_git_uri(current_uri, uri)
2166 [false, ["Invalid URI: #{current_uri}",
2168 "PlugClean required."].join($/)]
2171 log.call name, 'Updating ...', :update
2172 fetch_opt = (tag && File.exist?(File.join(dir, '.git/shallow'))) ? '--depth 99999999' : ''
2173 bt.call "#{chdir} && git fetch #{fetch_opt} #{progress} 2>&1", name, :update, nil
2179 d = esc dir.sub(%r{[\\/]+$}, '')
2180 log.call name, 'Installing ...', :install
2181 bt.call "git clone #{clone_opt unless tag} #{progress} #{uri} #{d} 2>&1", name, :install, proc {
2185 mtx.synchronize { VIM::command("let s:update.new['#{name}'] = 1") } if !exists && ok
2186 log.call name, result, ok
2191 threads.each { |t| t.join rescue nil }
2193 refresh.kill if refresh
2198 function! s:shellesc_cmd(arg, script)
2199 let escaped = substitute('"'.a:arg.'"', '[&|<>()@^!"]', '^&', 'g')
2200 return substitute(escaped, '%', (a:script ? '%' : '^') . '&', 'g')
2203 function! s:shellesc_ps1(arg)
2204 return "'".substitute(escape(a:arg, '\"'), "'", "''", 'g')."'"
2207 function! s:shellesc_sh(arg)
2208 return "'".substitute(a:arg, "'", "'\\\\''", 'g')."'"
2211 " Escape the shell argument based on the shell.
2212 " Vim and Neovim's shellescape() are insufficient.
2213 " 1. shellslash determines whether to use single/double quotes.
2214 " Double-quote escaping is fragile for cmd.exe.
2215 " 2. It does not work for powershell.
2216 " 3. It does not work for *sh shells if the command is executed
2217 " via cmd.exe (ie. cmd.exe /c sh -c command command_args)
2218 " 4. It does not support batchfile syntax.
2220 " Accepts an optional dictionary with the following keys:
2221 " - shell: same as Vim/Neovim 'shell' option.
2222 " If unset, fallback to 'cmd.exe' on Windows or 'sh'.
2223 " - script: If truthy and shell is cmd.exe, escape for batchfile syntax.
2224 function! plug#shellescape(arg, ...)
2225 if a:arg =~# '^[A-Za-z0-9_/:.-]\+$'
2228 let opts = a:0 > 0 && type(a:1) == s:TYPE.dict ? a:1 : {}
2229 let shell = get(opts, 'shell', s:is_win ? 'cmd.exe' : 'sh')
2230 let script = get(opts, 'script', 1)
2231 if shell =~# 'cmd\(\.exe\)\?$'
2232 return s:shellesc_cmd(a:arg, script)
2233 elseif s:is_powershell(shell)
2234 return s:shellesc_ps1(a:arg)
2236 return s:shellesc_sh(a:arg)
2239 function! s:glob_dir(path)
2240 return map(filter(s:glob(a:path, '**'), 'isdirectory(v:val)'), 's:dirpath(v:val)')
2243 function! s:progress_bar(line, bar, total)
2244 call setline(a:line, '[' . s:lpad(a:bar, a:total) . ']')
2247 function! s:compare_git_uri(a, b)
2248 " See `git help clone'
2249 " https:// [user@] github.com[:port] / junegunn/vim-plug [.git]
2250 " [git@] github.com[:port] : junegunn/vim-plug [.git]
2251 " file:// / junegunn/vim-plug [/]
2252 " / junegunn/vim-plug [/]
2253 let pat = '^\%(\w\+://\)\='.'\%([^@/]*@\)\='.'\([^:/]*\%(:[0-9]*\)\=\)'.'[:/]'.'\(.\{-}\)'.'\%(\.git\)\=/\?$'
2254 let ma = matchlist(a:a, pat)
2255 let mb = matchlist(a:b, pat)
2256 return ma[1:2] ==# mb[1:2]
2259 function! s:format_message(bullet, name, message)
2261 return [printf('%s %s: %s', a:bullet, a:name, s:lastline(a:message))]
2263 let lines = map(s:lines(a:message), '" ".v:val')
2264 return extend([printf('x %s:', a:name)], lines)
2268 function! s:with_cd(cmd, dir, ...)
2269 let script = a:0 > 0 ? a:1 : 1
2270 return printf('cd%s %s && %s', s:is_win ? ' /d' : '', plug#shellescape(a:dir, {'script': script}), a:cmd)
2273 function! s:system(cmd, ...)
2276 let [sh, shellcmdflag, shrd] = s:chsh(1)
2277 if type(a:cmd) == s:TYPE.list
2278 " Neovim's system() supports list argument to bypass the shell
2279 " but it cannot set the working directory for the command.
2280 " Assume that the command does not rely on the shell.
2281 if has('nvim') && a:0 == 0
2282 return system(a:cmd)
2284 let cmd = join(map(copy(a:cmd), 'plug#shellescape(v:val, {"shell": &shell, "script": 0})'))
2285 if s:is_powershell(&shell)
2286 let cmd = '& ' . cmd
2292 let cmd = s:with_cd(cmd, a:1, type(a:cmd) != s:TYPE.list)
2294 if s:is_win && type(a:cmd) != s:TYPE.list
2295 let [batchfile, cmd] = s:batchfile(cmd)
2299 let [&shell, &shellcmdflag, &shellredir] = [sh, shellcmdflag, shrd]
2300 if s:is_win && filereadable(batchfile)
2301 call delete(batchfile)
2306 function! s:system_chomp(...)
2307 let ret = call('s:system', a:000)
2308 return v:shell_error ? '' : substitute(ret, '\n$', '', '')
2311 function! s:git_validate(spec, check_branch)
2313 if isdirectory(a:spec.dir)
2314 let result = [s:git_local_branch(a:spec.dir), s:git_origin_url(a:spec.dir)]
2315 let remote = result[-1]
2317 let err = join([remote, 'PlugClean required.'], "\n")
2318 elseif !s:compare_git_uri(remote, a:spec.uri)
2319 let err = join(['Invalid URI: '.remote,
2320 \ 'Expected: '.a:spec.uri,
2321 \ 'PlugClean required.'], "\n")
2322 elseif a:check_branch && has_key(a:spec, 'commit')
2323 let sha = s:git_revision(a:spec.dir)
2325 let err = join(add(result, 'PlugClean required.'), "\n")
2326 elseif !s:hash_match(sha, a:spec.commit)
2327 let err = join([printf('Invalid HEAD (expected: %s, actual: %s)',
2328 \ a:spec.commit[:6], sha[:6]),
2329 \ 'PlugUpdate required.'], "\n")
2331 elseif a:check_branch
2332 let current_branch = result[0]
2334 let origin_branch = s:git_origin_branch(a:spec)
2335 if has_key(a:spec, 'tag')
2336 let tag = s:system_chomp('git describe --exact-match --tags HEAD 2>&1', a:spec.dir)
2337 if a:spec.tag !=# tag && a:spec.tag !~ '\*'
2338 let err = printf('Invalid tag: %s (expected: %s). Try PlugUpdate.',
2339 \ (empty(tag) ? 'N/A' : tag), a:spec.tag)
2342 elseif origin_branch !=# current_branch
2343 let err = printf('Invalid branch: %s (expected: %s). Try PlugUpdate.',
2344 \ current_branch, origin_branch)
2347 let [ahead, behind] = split(s:lastline(s:system([
2348 \ 'git', 'rev-list', '--count', '--left-right',
2349 \ printf('HEAD...origin/%s', origin_branch)
2350 \ ], a:spec.dir)), '\t')
2351 if !v:shell_error && ahead
2353 " Only mention PlugClean if diverged, otherwise it's likely to be
2354 " pushable (and probably not that messed up).
2356 \ "Diverged from origin/%s (%d commit(s) ahead and %d commit(s) behind!\n"
2357 \ .'Backup local changes and run PlugClean and PlugUpdate to reinstall it.', origin_branch, ahead, behind)
2359 let err = printf("Ahead of origin/%s by %d commit(s).\n"
2360 \ .'Cannot update until local changes are pushed.',
2361 \ origin_branch, ahead)
2367 let err = 'Not found'
2369 return [err, err =~# 'PlugClean']
2372 function! s:rm_rf(dir)
2373 if isdirectory(a:dir)
2374 return s:system(s:is_win
2375 \ ? 'rmdir /S /Q '.plug#shellescape(a:dir)
2376 \ : ['rm', '-rf', a:dir])
2380 function! s:clean(force)
2382 call append(0, 'Searching for invalid plugins in '.g:plug_home)
2385 " List of valid directories
2388 let [cnt, total] = [0, len(g:plugs)]
2389 for [name, spec] in items(g:plugs)
2390 if !s:is_managed(name)
2391 call add(dirs, spec.dir)
2393 let [err, clean] = s:git_validate(spec, 1)
2395 let errs[spec.dir] = s:lines(err)[0]
2397 call add(dirs, spec.dir)
2401 call s:progress_bar(2, repeat('=', cnt), total)
2408 let allowed[s:dirpath(s:plug_fnamemodify(dir, ':h:h'))] = 1
2409 let allowed[dir] = 1
2410 for child in s:glob_dir(dir)
2411 let allowed[child] = 1
2416 let found = sort(s:glob_dir(g:plug_home))
2418 let f = remove(found, 0)
2419 if !has_key(allowed, f) && isdirectory(f)
2421 call append(line('$'), '- ' . f)
2423 call append(line('$'), ' ' . errs[f])
2425 let found = filter(found, 'stridx(v:val, f) != 0')
2432 call append(line('$'), 'Already clean.')
2434 let s:clean_count = 0
2435 call append(3, ['Directories to delete:', ''])
2437 if a:force || s:ask_no_interrupt('Delete all directories?')
2438 call s:delete([6, line('$')], 1)
2440 call setline(4, 'Cancelled.')
2441 nnoremap <silent> <buffer> d :set opfunc=<sid>delete_op<cr>g@
2442 nmap <silent> <buffer> dd d_
2443 xnoremap <silent> <buffer> d :<c-u>call <sid>delete_op(visualmode(), 1)<cr>
2444 echo 'Delete the lines (d{motion}) to delete the corresponding directories'
2448 setlocal nomodifiable
2451 function! s:delete_op(type, ...)
2452 call s:delete(a:0 ? [line("'<"), line("'>")] : [line("'["), line("']")], 0)
2455 function! s:delete(range, force)
2456 let [l1, l2] = a:range
2460 let line = getline(l1)
2461 if line =~ '^- ' && isdirectory(line[2:])
2464 let answer = force ? 1 : s:ask('Delete '.line[2:].'?', 1)
2465 let force = force || answer > 1
2467 let err = s:rm_rf(line[2:])
2470 call setline(l1, '~'.line[1:])
2471 let s:clean_count += 1
2474 call append(l1 - 1, s:format_message('x', line[1:], err))
2475 let l2 += len(s:lines(err))
2478 let msg = printf('Removed %d directories.', s:clean_count)
2480 let msg .= printf(' Failed to remove %d directories.', err_count)
2482 call setline(4, msg)
2483 setlocal nomodifiable
2490 function! s:upgrade()
2491 echo 'Downloading the latest version of vim-plug'
2493 let tmp = s:plug_tempname()
2494 let new = tmp . '/plug.vim'
2497 let out = s:system(['git', 'clone', '--depth', '1', s:plug_src, tmp])
2499 return s:err('Error upgrading vim-plug: '. out)
2502 if readfile(s:me) ==# readfile(new)
2503 echo 'vim-plug is already up-to-date'
2506 call rename(s:me, s:me . '.old')
2507 call rename(new, s:me)
2509 echo 'vim-plug has been upgraded'
2513 silent! call s:rm_rf(tmp)
2517 function! s:upgrade_specs()
2518 for spec in values(g:plugs)
2519 let spec.frozen = get(spec, 'frozen', 0)
2523 function! s:status()
2525 call append(0, 'Checking plugins')
2530 let [cnt, total] = [0, len(g:plugs)]
2531 for [name, spec] in items(g:plugs)
2532 let is_dir = isdirectory(spec.dir)
2533 if has_key(spec, 'uri')
2535 let [err, _] = s:git_validate(spec, 1)
2536 let [valid, msg] = [empty(err), empty(err) ? 'OK' : err]
2538 let [valid, msg] = [0, 'Not found. Try PlugInstall.']
2542 let [valid, msg] = [1, 'OK']
2544 let [valid, msg] = [0, 'Not found.']
2549 " `s:loaded` entry can be missing if PlugUpgraded
2550 if is_dir && get(s:loaded, name, -1) == 0
2552 let msg .= ' (not loaded)'
2554 call s:progress_bar(2, repeat('=', cnt), total)
2555 call append(3, s:format_message(valid ? '-' : 'x', name, msg))
2559 call setline(1, 'Finished. '.ecnt.' error(s).')
2561 setlocal nomodifiable
2563 echo "Press 'L' on each line to load plugin, or 'U' to update"
2564 nnoremap <silent> <buffer> L :call <SID>status_load(line('.'))<cr>
2565 xnoremap <silent> <buffer> L :call <SID>status_load(line('.'))<cr>
2569 function! s:extract_name(str, prefix, suffix)
2570 return matchstr(a:str, '^'.a:prefix.' \zs[^:]\+\ze:.*'.a:suffix.'$')
2573 function! s:status_load(lnum)
2574 let line = getline(a:lnum)
2575 let name = s:extract_name(line, '-', '(not loaded)')
2577 call plug#load(name)
2579 call setline(a:lnum, substitute(line, ' (not loaded)$', '', ''))
2580 setlocal nomodifiable
2584 function! s:status_update() range
2585 let lines = getline(a:firstline, a:lastline)
2586 let names = filter(map(lines, 's:extract_name(v:val, "[x-]", "")'), '!empty(v:val)')
2589 execute 'PlugUpdate' join(names)
2593 function! s:is_preview_window_open()
2601 function! s:find_name(lnum)
2602 for lnum in reverse(range(1, a:lnum))
2603 let line = getline(lnum)
2607 let name = s:extract_name(line, '-', '')
2615 function! s:preview_commit()
2616 if b:plug_preview < 0
2617 let b:plug_preview = !s:is_preview_window_open()
2620 let sha = matchstr(getline('.'), '^ \X*\zs[0-9a-f]\{7,9}')
2625 let name = s:find_name(line('.'))
2626 if empty(name) || !has_key(g:plugs, name) || !isdirectory(g:plugs[name].dir)
2630 if exists('g:plug_pwindow') && !s:is_preview_window_open()
2631 execute g:plug_pwindow
2637 setlocal previewwindow filetype=git buftype=nofile nobuflisted modifiable
2640 let [sh, shellcmdflag, shrd] = s:chsh(1)
2641 let cmd = 'cd '.plug#shellescape(g:plugs[name].dir).' && git show --no-color --pretty=medium '.sha
2643 let [batchfile, cmd] = s:batchfile(cmd)
2645 execute 'silent %!' cmd
2647 let [&shell, &shellcmdflag, &shellredir] = [sh, shellcmdflag, shrd]
2648 if s:is_win && filereadable(batchfile)
2649 call delete(batchfile)
2652 setlocal nomodifiable
2653 nnoremap <silent> <buffer> q :q<cr>
2657 function! s:section(flags)
2658 call search('\(^[x-] \)\@<=[^:]\+:', a:flags)
2661 function! s:format_git_log(line)
2663 let tokens = split(a:line, nr2char(1))
2665 return indent.substitute(a:line, '\s*$', '', '')
2667 let [graph, sha, refs, subject, date] = tokens
2668 let tag = matchstr(refs, 'tag: [^,)]\+')
2669 let tag = empty(tag) ? ' ' : ' ('.tag.') '
2670 return printf('%s%s%s%s%s (%s)', indent, graph, sha, tag, subject, date)
2673 function! s:append_ul(lnum, text)
2674 call append(a:lnum, ['', a:text, repeat('-', len(a:text))])
2679 call append(0, ['Collecting changes ...', ''])
2682 let total = filter(copy(g:plugs), 's:is_managed(v:key) && isdirectory(v:val.dir)')
2683 call s:progress_bar(2, bar, len(total))
2684 for origin in [1, 0]
2685 let plugs = reverse(sort(items(filter(copy(total), (origin ? '' : '!').'(has_key(v:val, "commit") || has_key(v:val, "tag"))'))))
2689 call s:append_ul(2, origin ? 'Pending updates:' : 'Last update:')
2691 let branch = s:git_origin_branch(v)
2693 let range = origin ? '..origin/'.branch : 'HEAD@{1}..'
2694 let cmd = ['git', 'log', '--graph', '--color=never']
2695 if s:git_version_requirement(2, 10, 0)
2696 call add(cmd, '--no-show-signature')
2698 call extend(cmd, ['--pretty=format:%x01%h%x01%d%x01%s%x01%cr', range])
2699 if has_key(v, 'rtp')
2700 call extend(cmd, ['--', v.rtp])
2702 let diff = s:system_chomp(cmd, v.dir)
2704 let ref = has_key(v, 'tag') ? (' (tag: '.v.tag.')') : has_key(v, 'commit') ? (' '.v.commit) : ''
2705 call append(5, extend(['', '- '.k.':'.ref], map(s:lines(diff), 's:format_git_log(v:val)')))
2706 let cnts[origin] += 1
2710 call s:progress_bar(2, bar, len(total))
2715 call append(5, ['', 'N/A'])
2718 call setline(1, printf('%d plugin(s) updated.', cnts[0])
2719 \ . (cnts[1] ? printf(' %d plugin(s) have pending updates.', cnts[1]) : ''))
2721 if cnts[0] || cnts[1]
2722 nnoremap <silent> <buffer> <plug>(plug-preview) :silent! call <SID>preview_commit()<cr>
2723 if empty(maparg("\<cr>", 'n'))
2724 nmap <buffer> <cr> <plug>(plug-preview)
2726 if empty(maparg('o', 'n'))
2727 nmap <buffer> o <plug>(plug-preview)
2731 nnoremap <silent> <buffer> X :call <SID>revert()<cr>
2732 echo "Press 'X' on each block to revert the update"
2735 setlocal nomodifiable
2738 function! s:revert()
2739 if search('^Pending updates', 'bnW')
2743 let name = s:find_name(line('.'))
2744 if empty(name) || !has_key(g:plugs, name) ||
2745 \ input(printf('Revert the update of %s? (y/N) ', name)) !~? '^y'
2749 call s:system('git reset --hard HEAD@{1} && git checkout '.plug#shellescape(g:plugs[name].branch).' --', g:plugs[name].dir)
2752 setlocal nomodifiable
2756 function! s:snapshot(force, ...) abort
2759 call append(0, ['" Generated by vim-plug',
2760 \ '" '.strftime("%c"),
2761 \ '" :source this file in vim to restore the snapshot',
2762 \ '" or execute: vim -S snapshot.vim',
2763 \ '', '', 'PlugUpdate!'])
2765 let anchor = line('$') - 3
2766 let names = sort(keys(filter(copy(g:plugs),
2767 \'has_key(v:val, "uri") && !has_key(v:val, "commit") && isdirectory(v:val.dir)')))
2768 for name in reverse(names)
2769 let sha = s:git_revision(g:plugs[name].dir)
2771 call append(anchor, printf("silent! let g:plugs['%s'].commit = '%s'", name, sha))
2777 let fn = s:plug_expand(a:1)
2778 if filereadable(fn) && !(a:force || s:ask(a:1.' already exists. Overwrite?'))
2781 call writefile(getline(1, '$'), fn)
2782 echo 'Saved as '.a:1
2783 silent execute 'e' s:esc(fn)
2788 function! s:split_rtp()
2789 return split(&rtp, '\\\@<!,')
2792 let s:first_rtp = s:escrtp(get(s:split_rtp(), 0, ''))
2793 let s:last_rtp = s:escrtp(get(s:split_rtp(), -1, ''))
2795 if exists('g:plugs')
2796 let g:plugs_order = get(g:, 'plugs_order', keys(g:plugs))
2797 call s:upgrade_specs()
2798 call s:define_commands()
2801 let &cpo = s:cpo_save