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 'preservim/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 = stdpath('data') . '/plugged'
248 let home = s:path(split(&rtp, ',')[0]) . '/plugged'
250 return s:err('Unable to determine plug home. Try calling plug#begin() with a path argument.')
252 if s:plug_fnamemodify(home, ':t') ==# 'plugin' && s:plug_fnamemodify(home, ':h') ==# s:first_rtp
253 return s:err('Invalid plug home. '.home.' is a standard Vim runtime path and is not allowed.')
256 let g:plug_home = home
258 let g:plugs_order = []
261 call s:define_commands()
265 function! s:define_commands()
266 command! -nargs=+ -bar Plug call plug#(<args>)
267 if !executable('git')
268 return s:err('`git` executable not found. Most commands will not be available. To suppress this message, prepend `silent!` to `call plug#begin(...)`.')
272 \ && (&shell =~# 'cmd\(\.exe\)\?$' || s:is_powershell(&shell))
273 return s:err('vim-plug does not support shell, ' . &shell . ', when shellslash is set.')
276 \ && (has('win32') || has('win32unix'))
277 \ && !has('multi_byte')
278 return s:err('Vim needs +multi_byte feature on Windows to run shell commands. Enable +iconv for best results.')
280 command! -nargs=* -bar -bang -complete=customlist,s:names PlugInstall call s:install(<bang>0, [<f-args>])
281 command! -nargs=* -bar -bang -complete=customlist,s:names PlugUpdate call s:update(<bang>0, [<f-args>])
282 command! -nargs=0 -bar -bang PlugClean call s:clean(<bang>0)
283 command! -nargs=0 -bar PlugUpgrade if s:upgrade() | execute 'source' s:esc(s:me) | endif
284 command! -nargs=0 -bar PlugStatus call s:status()
285 command! -nargs=0 -bar PlugDiff call s:diff()
286 command! -nargs=? -bar -bang -complete=file PlugSnapshot call s:snapshot(<bang>0, <f-args>)
290 return type(a:v) == s:TYPE.list ? a:v : [a:v]
294 return type(a:v) == s:TYPE.string ? a:v : join(a:v, "\n") . "\n"
297 function! s:glob(from, pattern)
298 return s:lines(globpath(a:from, a:pattern))
301 function! s:source(from, ...)
304 for vim in s:glob(a:from, pattern)
305 execute 'source' s:esc(vim)
312 function! s:assoc(dict, key, val)
313 let a:dict[a:key] = add(get(a:dict, a:key, []), a:val)
316 function! s:ask(message, ...)
319 let answer = input(a:message.(a:0 ? ' (y/N/a) ' : ' (y/N) '))
323 return (a:0 && answer =~? '^a') ? 2 : (answer =~? '^y') ? 1 : 0
326 function! s:ask_no_interrupt(...)
328 return call('s:ask', a:000)
334 function! s:lazy(plug, opt)
335 return has_key(a:plug, a:opt) &&
336 \ (empty(s:to_a(a:plug[a:opt])) ||
337 \ !isdirectory(a:plug.dir) ||
338 \ len(s:glob(s:rtp(a:plug), 'plugin')) ||
339 \ len(s:glob(s:rtp(a:plug), 'after/plugin')))
343 if !exists('g:plugs')
344 return s:err('plug#end() called without calling plug#begin() first')
347 if exists('#PlugLOD')
353 let lod = { 'ft': {}, 'map': {}, 'cmd': {} }
355 if get(g:, 'did_load_filetypes', 0)
358 for name in g:plugs_order
359 if !has_key(g:plugs, name)
362 let plug = g:plugs[name]
363 if get(s:loaded, name, 0) || !s:lazy(plug, 'on') && !s:lazy(plug, 'for')
364 let s:loaded[name] = 1
368 if has_key(plug, 'on')
369 let s:triggers[name] = { 'map': [], 'cmd': [] }
370 for cmd in s:to_a(plug.on)
371 if cmd =~? '^<Plug>.\+'
372 if empty(mapcheck(cmd)) && empty(mapcheck(cmd, 'i'))
373 call s:assoc(lod.map, cmd, name)
375 call add(s:triggers[name].map, cmd)
376 elseif cmd =~# '^[A-Z]'
377 let cmd = substitute(cmd, '!*$', '', '')
378 if exists(':'.cmd) != 2
379 call s:assoc(lod.cmd, cmd, name)
381 call add(s:triggers[name].cmd, cmd)
383 call s:err('Invalid `on` option: '.cmd.
384 \ '. Should start with an uppercase letter or `<Plug>`.')
389 if has_key(plug, 'for')
390 let types = s:to_a(plug.for)
392 augroup filetypedetect
393 call s:source(s:rtp(plug), 'ftdetect/**/*.vim', 'after/ftdetect/**/*.vim')
397 call s:assoc(lod.ft, type, name)
402 for [cmd, names] in items(lod.cmd)
404 \ 'command! -nargs=* -range -bang -complete=file %s call s:lod_cmd(%s, "<bang>", <line1>, <line2>, <q-args>, %s)',
405 \ cmd, string(cmd), string(names))
408 for [map, names] in items(lod.map)
409 for [mode, map_prefix, key_prefix] in
410 \ [['i', '<C-\><C-O>', ''], ['n', '', ''], ['v', '', 'gv'], ['o', '', '']]
412 \ '%snoremap <silent> %s %s:<C-U>call <SID>lod_map(%s, %s, %s, "%s")<CR>',
413 \ mode, map, map_prefix, string(map), string(names), mode != 'i', key_prefix)
417 for [ft, names] in items(lod.ft)
419 execute printf('autocmd FileType %s call <SID>lod_ft(%s, %s)',
420 \ ft, string(ft), string(names))
425 filetype plugin indent on
426 if has('vim_starting')
427 if has('syntax') && !exists('g:syntax_on')
431 call s:reload_plugins()
435 function! s:loaded_names()
436 return filter(copy(g:plugs_order), 'get(s:loaded, v:val, 0)')
439 function! s:load_plugin(spec)
440 call s:source(s:rtp(a:spec), 'plugin/**/*.vim', 'after/plugin/**/*.vim')
443 function! s:reload_plugins()
444 for name in s:loaded_names()
445 call s:load_plugin(g:plugs[name])
449 function! s:trim(str)
450 return substitute(a:str, '[\/]\+$', '', '')
453 function! s:version_requirement(val, min)
454 for idx in range(0, len(a:min) - 1)
455 let v = get(a:val, idx, 0)
456 if v < a:min[idx] | return 0
457 elseif v > a:min[idx] | return 1
463 function! s:git_version_requirement(...)
464 if !exists('s:git_version')
465 let s:git_version = map(split(split(s:system(['git', '--version']))[2], '\.'), 'str2nr(v:val)')
467 return s:version_requirement(s:git_version, a:000)
470 function! s:progress_opt(base)
471 return a:base && !s:is_win &&
472 \ s:git_version_requirement(1, 7, 1) ? '--progress' : ''
475 function! s:rtp(spec)
476 return s:path(a:spec.dir . get(a:spec, 'rtp', ''))
480 function! s:path(path)
481 return s:trim(substitute(a:path, '/', '\', 'g'))
484 function! s:dirpath(path)
485 return s:path(a:path) . '\'
488 function! s:is_local_plug(repo)
489 return a:repo =~? '^[a-z]:\|^[%~]'
493 function! s:wrap_cmds(cmds)
496 \ 'setlocal enabledelayedexpansion']
497 \ + (type(a:cmds) == type([]) ? a:cmds : [a:cmds])
500 if !exists('s:codepage')
501 let s:codepage = libcallnr('kernel32.dll', 'GetACP', 0)
503 return map(cmds, printf('iconv(v:val."\r", "%s", "cp%d")', &encoding, s:codepage))
505 return map(cmds, 'v:val."\r"')
508 function! s:batchfile(cmd)
509 let batchfile = s:plug_tempname().'.bat'
510 call writefile(s:wrap_cmds(a:cmd), batchfile)
511 let cmd = plug#shellescape(batchfile, {'shell': &shell, 'script': 0})
512 if s:is_powershell(&shell)
515 return [batchfile, cmd]
518 function! s:path(path)
519 return s:trim(a:path)
522 function! s:dirpath(path)
523 return substitute(a:path, '[/\\]*$', '/', '')
526 function! s:is_local_plug(repo)
527 return a:repo[0] =~ '[/$~]'
533 echom '[vim-plug] '.a:msg
537 function! s:warn(cmd, msg)
539 execute a:cmd 'a:msg'
543 function! s:esc(path)
544 return escape(a:path, ' ')
547 function! s:escrtp(path)
548 return escape(a:path, ' ,')
551 function! s:remove_rtp()
552 for name in s:loaded_names()
553 let rtp = s:rtp(g:plugs[name])
554 execute 'set rtp-='.s:escrtp(rtp)
555 let after = globpath(rtp, 'after')
556 if isdirectory(after)
557 execute 'set rtp-='.s:escrtp(after)
562 function! s:reorg_rtp()
563 if !empty(s:first_rtp)
564 execute 'set rtp-='.s:first_rtp
565 execute 'set rtp-='.s:last_rtp
568 " &rtp is modified from outside
569 if exists('s:prtp') && s:prtp !=# &rtp
574 let s:middle = get(s:, 'middle', &rtp)
575 let rtps = map(s:loaded_names(), 's:rtp(g:plugs[v:val])')
576 let afters = filter(map(copy(rtps), 'globpath(v:val, "after")'), '!empty(v:val)')
577 let rtp = join(map(rtps, 'escape(v:val, ",")'), ',')
579 \ . join(map(afters, 'escape(v:val, ",")'), ',')
580 let &rtp = substitute(substitute(rtp, ',,*', ',', 'g'), '^,\|,$', '', 'g')
583 if !empty(s:first_rtp)
584 execute 'set rtp^='.s:first_rtp
585 execute 'set rtp+='.s:last_rtp
589 function! s:doautocmd(...)
590 if exists('#'.join(a:000, '#'))
591 execute 'doautocmd' ((v:version > 703 || has('patch442')) ? '<nomodeline>' : '') join(a:000)
595 function! s:dobufread(names)
597 let path = s:rtp(g:plugs[name])
598 for dir in ['ftdetect', 'ftplugin', 'after/ftdetect', 'after/ftplugin']
599 if len(finddir(dir, path))
600 if exists('#BufRead')
609 function! plug#load(...)
611 return s:err('Argument missing: plugin name(s) required')
613 if !exists('g:plugs')
614 return s:err('plug#begin was not called')
616 let names = a:0 == 1 && type(a:1) == s:TYPE.list ? a:1 : a:000
617 let unknowns = filter(copy(names), '!has_key(g:plugs, v:val)')
619 let s = len(unknowns) > 1 ? 's' : ''
620 return s:err(printf('Unknown plugin%s: %s', s, join(unknowns, ', ')))
622 let unloaded = filter(copy(names), '!get(s:loaded, v:val, 0)')
625 call s:lod([name], ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin'])
627 call s:dobufread(unloaded)
633 function! s:remove_triggers(name)
634 if !has_key(s:triggers, a:name)
637 for cmd in s:triggers[a:name].cmd
638 execute 'silent! delc' cmd
640 for map in s:triggers[a:name].map
641 execute 'silent! unmap' map
642 execute 'silent! iunmap' map
644 call remove(s:triggers, a:name)
647 function! s:lod(names, types, ...)
649 call s:remove_triggers(name)
650 let s:loaded[name] = 1
655 let rtp = s:rtp(g:plugs[name])
657 call s:source(rtp, dir.'/**/*.vim')
660 if !s:source(rtp, a:1) && !empty(s:glob(rtp, a:2))
661 execute 'runtime' a:1
663 call s:source(rtp, a:2)
665 call s:doautocmd('User', name)
669 function! s:lod_ft(pat, names)
670 let syn = 'syntax/'.a:pat.'.vim'
671 call s:lod(a:names, ['plugin', 'after/plugin'], syn, 'after/'.syn)
672 execute 'autocmd! PlugLOD FileType' a:pat
673 call s:doautocmd('filetypeplugin', 'FileType')
674 call s:doautocmd('filetypeindent', 'FileType')
677 function! s:lod_cmd(cmd, bang, l1, l2, args, names)
678 call s:lod(a:names, ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin'])
679 call s:dobufread(a:names)
680 execute printf('%s%s%s %s', (a:l1 == a:l2 ? '' : (a:l1.','.a:l2)), a:cmd, a:bang, a:args)
683 function! s:lod_map(map, names, with_prefix, prefix)
684 call s:lod(a:names, ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin'])
685 call s:dobufread(a:names)
692 let extra .= nr2char(c)
696 let prefix = v:count ? v:count : ''
697 let prefix .= '"'.v:register.a:prefix
700 let prefix = "\<esc>" . prefix
702 let prefix .= v:operator
704 call feedkeys(prefix, 'n')
706 call feedkeys(substitute(a:map, '^<Plug>', "\<Plug>", '') . extra)
709 function! plug#(repo, ...)
711 return s:err('Invalid number of arguments (1..2)')
715 let repo = s:trim(a:repo)
716 let opts = a:0 == 1 ? s:parse_options(a:1) : s:base_spec
717 let name = get(opts, 'as', s:plug_fnamemodify(repo, ':t:s?\.git$??'))
718 let spec = extend(s:infer_properties(name, repo), opts)
719 if !has_key(g:plugs, name)
720 call add(g:plugs_order, name)
722 let g:plugs[name] = spec
723 let s:loaded[name] = get(s:loaded, name, 0)
725 return s:err(repo . ' ' . v:exception)
729 function! s:parse_options(arg)
730 let opts = copy(s:base_spec)
731 let type = type(a:arg)
732 let opt_errfmt = 'Invalid argument for "%s" option of :Plug (expected: %s)'
733 if type == s:TYPE.string
735 throw printf(opt_errfmt, 'tag', 'string')
738 elseif type == s:TYPE.dict
739 for opt in ['branch', 'tag', 'commit', 'rtp', 'dir', 'as']
740 if has_key(a:arg, opt)
741 \ && (type(a:arg[opt]) != s:TYPE.string || empty(a:arg[opt]))
742 throw printf(opt_errfmt, opt, 'string')
745 for opt in ['on', 'for']
746 if has_key(a:arg, opt)
747 \ && type(a:arg[opt]) != s:TYPE.list
748 \ && (type(a:arg[opt]) != s:TYPE.string || empty(a:arg[opt]))
749 throw printf(opt_errfmt, opt, 'string or list')
752 if has_key(a:arg, 'do')
753 \ && type(a:arg.do) != s:TYPE.funcref
754 \ && (type(a:arg.do) != s:TYPE.string || empty(a:arg.do))
755 throw printf(opt_errfmt, 'do', 'string or funcref')
757 call extend(opts, a:arg)
758 if has_key(opts, 'dir')
759 let opts.dir = s:dirpath(s:plug_expand(opts.dir))
762 throw 'Invalid argument type (expected: string or dictionary)'
767 function! s:infer_properties(name, repo)
769 if s:is_local_plug(repo)
770 return { 'dir': s:dirpath(s:plug_expand(repo)) }
776 throw printf('Invalid argument: %s (implicit `vim-scripts'' expansion is deprecated)', repo)
778 let fmt = get(g:, 'plug_url_format', 'https://git::@github.com/%s.git')
779 let uri = printf(fmt, repo)
781 return { 'dir': s:dirpath(g:plug_home.'/'.a:name), 'uri': uri }
785 function! s:install(force, names)
786 call s:update_impl(0, a:force, a:names)
789 function! s:update(force, names)
790 call s:update_impl(1, a:force, a:names)
793 function! plug#helptags()
794 if !exists('g:plugs')
795 return s:err('plug#begin was not called')
797 for spec in values(g:plugs)
798 let docd = join([s:rtp(spec), 'doc'], '/')
800 silent! execute 'helptags' s:esc(docd)
808 syntax region plug1 start=/\%1l/ end=/\%2l/ contains=plugNumber
809 syntax region plug2 start=/\%2l/ end=/\%3l/ contains=plugBracket,plugX
810 syn match plugNumber /[0-9]\+[0-9.]*/ contained
811 syn match plugBracket /[[\]]/ contained
812 syn match plugX /x/ contained
813 syn match plugDash /^-\{1}\ /
814 syn match plugPlus /^+/
815 syn match plugStar /^*/
816 syn match plugMessage /\(^- \)\@<=.*/
817 syn match plugName /\(^- \)\@<=[^ ]*:/
818 syn match plugSha /\%(: \)\@<=[0-9a-f]\{4,}$/
819 syn match plugTag /(tag: [^)]\+)/
820 syn match plugInstall /\(^+ \)\@<=[^:]*/
821 syn match plugUpdate /\(^* \)\@<=[^:]*/
822 syn match plugCommit /^ \X*[0-9a-f]\{7,9} .*/ contains=plugRelDate,plugEdge,plugTag
823 syn match plugEdge /^ \X\+$/
824 syn match plugEdge /^ \X*/ contained nextgroup=plugSha
825 syn match plugSha /[0-9a-f]\{7,9}/ contained
826 syn match plugRelDate /([^)]*)$/ contained
827 syn match plugNotLoaded /(not loaded)$/
828 syn match plugError /^x.*/
829 syn region plugDeleted start=/^\~ .*/ end=/^\ze\S/
830 syn match plugH2 /^.*:\n-\+$/
831 syn match plugH2 /^-\{2,}/
832 syn keyword Function PlugInstall PlugStatus PlugUpdate PlugClean
833 hi def link plug1 Title
834 hi def link plug2 Repeat
835 hi def link plugH2 Type
836 hi def link plugX Exception
837 hi def link plugBracket Structure
838 hi def link plugNumber Number
840 hi def link plugDash Special
841 hi def link plugPlus Constant
842 hi def link plugStar Boolean
844 hi def link plugMessage Function
845 hi def link plugName Label
846 hi def link plugInstall Function
847 hi def link plugUpdate Type
849 hi def link plugError Error
850 hi def link plugDeleted Ignore
851 hi def link plugRelDate Comment
852 hi def link plugEdge PreProc
853 hi def link plugSha Identifier
854 hi def link plugTag Constant
856 hi def link plugNotLoaded Comment
859 function! s:lpad(str, len)
860 return a:str . repeat(' ', a:len - len(a:str))
863 function! s:lines(msg)
864 return split(a:msg, "[\r\n]")
867 function! s:lastline(msg)
868 return get(s:lines(a:msg), -1, '')
871 function! s:new_window()
872 execute get(g:, 'plug_window', 'vertical topleft new')
875 function! s:plug_window_exists()
876 let buflist = tabpagebuflist(s:plug_tab)
877 return !empty(buflist) && index(buflist, s:plug_buf) >= 0
880 function! s:switch_in()
881 if !s:plug_window_exists()
885 if winbufnr(0) != s:plug_buf
886 let s:pos = [tabpagenr(), winnr(), winsaveview()]
887 execute 'normal!' s:plug_tab.'gt'
888 let winnr = bufwinnr(s:plug_buf)
889 execute winnr.'wincmd w'
890 call add(s:pos, winsaveview())
892 let s:pos = [winsaveview()]
899 function! s:switch_out(...)
900 call winrestview(s:pos[-1])
901 setlocal nomodifiable
907 execute 'normal!' s:pos[0].'gt'
908 execute s:pos[1] 'wincmd w'
909 call winrestview(s:pos[2])
913 function! s:finish_bindings()
914 nnoremap <silent> <buffer> R :call <SID>retry()<cr>
915 nnoremap <silent> <buffer> D :PlugDiff<cr>
916 nnoremap <silent> <buffer> S :PlugStatus<cr>
917 nnoremap <silent> <buffer> U :call <SID>status_update()<cr>
918 xnoremap <silent> <buffer> U :call <SID>status_update()<cr>
919 nnoremap <silent> <buffer> ]] :silent! call <SID>section('')<cr>
920 nnoremap <silent> <buffer> [[ :silent! call <SID>section('b')<cr>
923 function! s:prepare(...)
924 if empty(s:plug_getcwd())
925 throw 'Invalid current working directory. Cannot proceed.'
928 for evar in ['$GIT_DIR', '$GIT_WORK_TREE']
930 throw evar.' detected. Cannot proceed.'
936 if b:plug_preview == 1
944 nnoremap <silent> <buffer> q :call <SID>close_pane()<cr>
946 call s:finish_bindings()
948 let b:plug_preview = -1
949 let s:plug_tab = tabpagenr()
950 let s:plug_buf = winbufnr(0)
953 for k in ['<cr>', 'L', 'o', 'X', 'd', 'dd']
954 execute 'silent! unmap <buffer>' k
956 setlocal buftype=nofile bufhidden=wipe nobuflisted nolist noswapfile nowrap cursorline modifiable nospell
957 if exists('+colorcolumn')
958 setlocal colorcolumn=
961 if exists('g:syntax_on')
966 function! s:close_pane()
967 if b:plug_preview == 1
969 let b:plug_preview = -1
975 function! s:assign_name()
977 let prefix = '[Plugins]'
980 while bufexists(name)
981 let name = printf('%s (%s)', prefix, idx)
984 silent! execute 'f' fnameescape(name)
987 function! s:chsh(swap)
988 let prev = [&shell, &shellcmdflag, &shellredir]
993 if s:is_powershell(&shell)
994 let &shellredir = '2>&1 | Out-File -Encoding UTF8 %s'
995 elseif &shell =~# 'sh' || &shell =~# 'cmd\(\.exe\)\?$'
996 set shellredir=>%s\ 2>&1
1002 function! s:bang(cmd, ...)
1005 let [sh, shellcmdflag, shrd] = s:chsh(a:0)
1006 " FIXME: Escaping is incomplete. We could use shellescape with eval,
1007 " but it won't work on Windows.
1008 let cmd = a:0 ? s:with_cd(a:cmd, a:1) : a:cmd
1010 let [batchfile, cmd] = s:batchfile(cmd)
1012 let g:_plug_bang = (s:is_win && has('gui_running') ? 'silent ' : '').'!'.escape(cmd, '#!%')
1013 execute "normal! :execute g:_plug_bang\<cr>\<cr>"
1016 let [&shell, &shellcmdflag, &shellredir] = [sh, shellcmdflag, shrd]
1017 if s:is_win && filereadable(batchfile)
1018 call delete(batchfile)
1021 return v:shell_error ? 'Exit status: ' . v:shell_error : ''
1024 function! s:regress_bar()
1025 let bar = substitute(getline(2)[1:-2], '.*\zs=', 'x', '')
1026 call s:progress_bar(2, bar, len(bar))
1029 function! s:is_updated(dir)
1030 return !empty(s:system_chomp(['git', 'log', '--pretty=format:%h', 'HEAD...HEAD@{1}'], a:dir))
1033 function! s:do(pull, force, todo)
1034 for [name, spec] in items(a:todo)
1035 if !isdirectory(spec.dir)
1038 let installed = has_key(s:update.new, name)
1039 let updated = installed ? 0 :
1040 \ (a:pull && index(s:update.errors, name) < 0 && s:is_updated(spec.dir))
1041 if a:force || installed || updated
1042 execute 'cd' s:esc(spec.dir)
1043 call append(3, '- Post-update hook for '. name .' ... ')
1045 let type = type(spec.do)
1046 if type == s:TYPE.string
1047 if spec.do[0] == ':'
1048 if !get(s:loaded, name, 0)
1049 let s:loaded[name] = 1
1052 call s:load_plugin(spec)
1056 let error = v:exception
1058 if !s:plug_window_exists()
1060 throw 'Warning: vim-plug was terminated by the post-update hook of '.name
1063 let error = s:bang(spec.do)
1065 elseif type == s:TYPE.funcref
1067 call s:load_plugin(spec)
1068 let status = installed ? 'installed' : (updated ? 'updated' : 'unchanged')
1069 call spec.do({ 'name': name, 'status': status, 'force': a:force })
1071 let error = v:exception
1074 let error = 'Invalid hook type'
1077 call setline(4, empty(error) ? (getline(4) . 'OK')
1078 \ : ('x' . getline(4)[1:] . error))
1080 call add(s:update.errors, name)
1081 call s:regress_bar()
1088 function! s:hash_match(a, b)
1089 return stridx(a:a, a:b) == 0 || stridx(a:b, a:a) == 0
1092 function! s:checkout(spec)
1093 let sha = a:spec.commit
1094 let output = s:git_revision(a:spec.dir)
1095 if !empty(output) && !s:hash_match(sha, s:lines(output)[0])
1096 let credential_helper = s:git_version_requirement(2) ? '-c credential.helper= ' : ''
1097 let output = s:system(
1098 \ 'git '.credential_helper.'fetch --depth 999999 && git checkout '.plug#shellescape(sha).' --', a:spec.dir)
1103 function! s:finish(pull)
1104 let new_frozen = len(filter(keys(s:update.new), 'g:plugs[v:val].frozen'))
1106 let s = new_frozen > 1 ? 's' : ''
1107 call append(3, printf('- Installed %d frozen plugin%s', new_frozen, s))
1109 call append(3, '- Finishing ... ') | 4
1111 call plug#helptags()
1113 call setline(4, getline(4) . 'Done!')
1116 if !empty(s:update.errors)
1117 call add(msgs, "Press 'R' to retry.")
1119 if a:pull && len(s:update.new) < len(filter(getline(5, '$'),
1120 \ "v:val =~ '^- ' && v:val !~# 'Already up.to.date'"))
1121 call add(msgs, "Press 'D' to see the updated changes.")
1123 echo join(msgs, ' ')
1124 call s:finish_bindings()
1128 if empty(s:update.errors)
1132 call s:update_impl(s:update.pull, s:update.force,
1133 \ extend(copy(s:update.errors), [s:update.threads]))
1136 function! s:is_managed(name)
1137 return has_key(g:plugs[a:name], 'uri')
1140 function! s:names(...)
1141 return sort(filter(keys(g:plugs), 'stridx(v:val, a:1) == 0 && s:is_managed(v:val)'))
1144 function! s:check_ruby()
1145 silent! ruby require 'thread'; VIM::command("let g:plug_ruby = '#{RUBY_VERSION}'")
1146 if !exists('g:plug_ruby')
1148 return s:warn('echom', 'Warning: Ruby interface is broken')
1150 let ruby_version = split(g:plug_ruby, '\.')
1152 return s:version_requirement(ruby_version, [1, 8, 7])
1155 function! s:update_impl(pull, force, args) abort
1156 let sync = index(a:args, '--sync') >= 0 || has('vim_starting')
1157 let args = filter(copy(a:args), 'v:val != "--sync"')
1158 let threads = (len(args) > 0 && args[-1] =~ '^[1-9][0-9]*$') ?
1159 \ remove(args, -1) : get(g:, 'plug_threads', 16)
1161 let managed = filter(copy(g:plugs), 's:is_managed(v:key)')
1162 let todo = empty(args) ? filter(managed, '!v:val.frozen || !isdirectory(v:val.dir)') :
1163 \ filter(managed, 'index(args, v:key) >= 0')
1166 return s:warn('echo', 'No plugin to '. (a:pull ? 'update' : 'install'))
1169 if !s:is_win && s:git_version_requirement(2, 3)
1170 let s:git_terminal_prompt = exists('$GIT_TERMINAL_PROMPT') ? $GIT_TERMINAL_PROMPT : ''
1171 let $GIT_TERMINAL_PROMPT = 0
1172 for plug in values(todo)
1173 let plug.uri = substitute(plug.uri,
1174 \ '^https://git::@github\.com', 'https://github.com', '')
1178 if !isdirectory(g:plug_home)
1180 call mkdir(g:plug_home, 'p')
1182 return s:err(printf('Invalid plug directory: %s. '.
1183 \ 'Try to call plug#begin with a valid directory', g:plug_home))
1187 if has('nvim') && !exists('*jobwait') && threads > 1
1188 call s:warn('echom', '[vim-plug] Update Neovim for parallel installer')
1191 let use_job = s:nvim || s:vim8
1192 let python = (has('python') || has('python3')) && !use_job
1193 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()
1196 \ 'start': reltime(),
1198 \ 'todo': copy(todo),
1203 \ 'threads': (python || ruby || use_job) ? min([len(todo), threads]) : 1,
1209 call append(0, ['', ''])
1213 " Set remote name, overriding a possible user git config's clone.defaultRemoteName
1214 let s:clone_opt = ['--origin', 'origin']
1215 if get(g:, 'plug_shallow', 1)
1216 call extend(s:clone_opt, ['--depth', '1'])
1217 if s:git_version_requirement(1, 7, 10)
1218 call add(s:clone_opt, '--no-single-branch')
1222 if has('win32unix') || has('wsl')
1223 call extend(s:clone_opt, ['-c', 'core.eol=lf', '-c', 'core.autocrlf=input'])
1226 let s:submodule_opt = s:git_version_requirement(2, 8) ? ' --jobs='.threads : ''
1228 " Python version requirement (>= 2.7)
1229 if python && !has('python3') && !ruby && !use_job && s:update.threads > 1
1231 silent python import platform; print platform.python_version()
1233 let python = s:version_requirement(
1234 \ map(split(split(pyv)[0], '\.'), 'str2nr(v:val)'), [2, 6])
1237 if (python || ruby) && s:update.threads > 1
1244 call s:update_ruby()
1246 call s:update_python()
1249 let lines = getline(4, '$')
1253 let name = s:extract_name(line, '.', '')
1254 if empty(name) || !has_key(printed, name)
1255 call append('$', line)
1257 let printed[name] = 1
1258 if line[0] == 'x' && index(s:update.errors, name) < 0
1259 call add(s:update.errors, name)
1266 call s:update_finish()
1270 while use_job && sync
1279 function! s:log4(name, msg)
1280 call setline(4, printf('- %s (%s)', a:msg, a:name))
1284 function! s:update_finish()
1285 if exists('s:git_terminal_prompt')
1286 let $GIT_TERMINAL_PROMPT = s:git_terminal_prompt
1289 call append(3, '- Updating ...') | 4
1290 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))'))
1291 let [pos, _] = s:logpos(name)
1295 if has_key(spec, 'commit')
1296 call s:log4(name, 'Checking out '.spec.commit)
1297 let out = s:checkout(spec)
1298 elseif has_key(spec, 'tag')
1301 let tags = s:lines(s:system('git tag --list '.plug#shellescape(tag).' --sort -version:refname 2>&1', spec.dir))
1302 if !v:shell_error && !empty(tags)
1304 call s:log4(name, printf('Latest tag for %s -> %s', spec.tag, tag))
1308 call s:log4(name, 'Checking out '.tag)
1309 let out = s:system('git checkout -q '.plug#shellescape(tag).' -- 2>&1', spec.dir)
1311 let branch = s:git_origin_branch(spec)
1312 call s:log4(name, 'Merging origin/'.s:esc(branch))
1313 let out = s:system('git checkout -q '.plug#shellescape(branch).' -- 2>&1'
1314 \. (has_key(s:update.new, name) ? '' : ('&& git merge --ff-only '.plug#shellescape('origin/'.branch).' 2>&1')), spec.dir)
1316 if !v:shell_error && filereadable(spec.dir.'/.gitmodules') &&
1317 \ (s:update.force || has_key(s:update.new, name) || s:is_updated(spec.dir))
1318 call s:log4(name, 'Updating submodules. This may take a while.')
1319 let out .= s:bang('git submodule update --init --recursive'.s:submodule_opt.' 2>&1', spec.dir)
1321 let msg = s:format_message(v:shell_error ? 'x': '-', name, out)
1323 call add(s:update.errors, name)
1324 call s:regress_bar()
1325 silent execute pos 'd _'
1326 call append(4, msg) | 4
1328 call setline(pos, msg[0])
1334 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")'))
1336 call s:warn('echom', v:exception)
1337 call s:warn('echo', '')
1340 call s:finish(s:update.pull)
1341 call setline(1, 'Updated. Elapsed time: ' . split(reltimestr(reltime(s:update.start)))[0] . ' sec.')
1342 call s:switch_out('normal! gg')
1346 function! s:job_abort()
1347 if (!s:nvim && !s:vim8) || !exists('s:jobs')
1351 for [name, j] in items(s:jobs)
1353 silent! call jobstop(j.jobid)
1355 silent! call job_stop(j.jobid)
1358 call s:rm_rf(g:plugs[name].dir)
1364 function! s:last_non_empty_line(lines)
1365 let len = len(a:lines)
1366 for idx in range(len)
1367 let line = a:lines[len-idx-1]
1375 function! s:job_out_cb(self, data) abort
1377 let data = remove(self.lines, -1) . a:data
1378 let lines = map(split(data, "\n", 1), 'split(v:val, "\r", 1)[-1]')
1379 call extend(self.lines, lines)
1380 " To reduce the number of buffer updates
1381 let self.tick = get(self, 'tick', -1) + 1
1382 if !self.running || self.tick % len(s:jobs) == 0
1383 let bullet = self.running ? (self.new ? '+' : '*') : (self.error ? 'x' : '-')
1384 let result = self.error ? join(self.lines, "\n") : s:last_non_empty_line(self.lines)
1385 call s:log(bullet, self.name, result)
1389 function! s:job_exit_cb(self, data) abort
1390 let a:self.running = 0
1391 let a:self.error = a:data != 0
1392 call s:reap(a:self.name)
1396 function! s:job_cb(fn, job, ch, data)
1397 if !s:plug_window_exists() " plug window closed
1398 return s:job_abort()
1400 call call(a:fn, [a:job, a:data])
1403 function! s:nvim_cb(job_id, data, event) dict abort
1404 return (a:event == 'stdout' || a:event == 'stderr') ?
1405 \ s:job_cb('s:job_out_cb', self, 0, join(a:data, "\n")) :
1406 \ s:job_cb('s:job_exit_cb', self, 0, a:data)
1409 function! s:spawn(name, cmd, opts)
1410 let job = { 'name': a:name, 'running': 1, 'error': 0, 'lines': [''],
1411 \ 'new': get(a:opts, 'new', 0) }
1412 let s:jobs[a:name] = job
1415 if has_key(a:opts, 'dir')
1416 let job.cwd = a:opts.dir
1420 \ 'on_stdout': function('s:nvim_cb'),
1421 \ 'on_stderr': function('s:nvim_cb'),
1422 \ 'on_exit': function('s:nvim_cb'),
1424 let jid = s:plug_call('jobstart', argv, job)
1430 let job.lines = [jid < 0 ? argv[0].' is not executable' :
1431 \ 'Invalid arguments (or job table is full)']
1434 let cmd = join(map(copy(a:cmd), 'plug#shellescape(v:val, {"script": 0})'))
1435 if has_key(a:opts, 'dir')
1436 let cmd = s:with_cd(cmd, a:opts.dir, 0)
1438 let argv = s:is_win ? ['cmd', '/s', '/c', '"'.cmd.'"'] : ['sh', '-c', cmd]
1439 let jid = job_start(s:is_win ? join(argv, ' ') : argv, {
1440 \ 'out_cb': function('s:job_cb', ['s:job_out_cb', job]),
1441 \ 'err_cb': function('s:job_cb', ['s:job_out_cb', job]),
1442 \ 'exit_cb': function('s:job_cb', ['s:job_exit_cb', job]),
1443 \ 'err_mode': 'raw',
1446 if job_status(jid) == 'run'
1451 let job.lines = ['Failed to start job']
1454 let job.lines = s:lines(call('s:system', has_key(a:opts, 'dir') ? [a:cmd, a:opts.dir] : [a:cmd]))
1455 let job.error = v:shell_error != 0
1460 function! s:reap(name)
1461 let job = s:jobs[a:name]
1463 call add(s:update.errors, a:name)
1464 elseif get(job, 'new', 0)
1465 let s:update.new[a:name] = 1
1467 let s:update.bar .= job.error ? 'x' : '='
1469 let bullet = job.error ? 'x' : '-'
1470 let result = job.error ? join(job.lines, "\n") : s:last_non_empty_line(job.lines)
1471 call s:log(bullet, a:name, empty(result) ? 'OK' : result)
1474 call remove(s:jobs, a:name)
1479 let total = len(s:update.all)
1480 call setline(1, (s:update.pull ? 'Updating' : 'Installing').
1481 \ ' plugins ('.len(s:update.bar).'/'.total.')')
1482 call s:progress_bar(2, s:update.bar, total)
1487 function! s:logpos(name)
1489 for i in range(4, max > 4 ? max : 4)
1490 if getline(i) =~# '^[-+x*] '.a:name.':'
1491 for j in range(i + 1, max > 5 ? max : 5)
1492 if getline(j) !~ '^ '
1502 function! s:log(bullet, name, lines)
1504 let [b, e] = s:logpos(a:name)
1506 silent execute printf('%d,%d d _', b, e)
1507 if b > winheight('.')
1513 " FIXME For some reason, nomodifiable is set after :d in vim8
1515 call append(b - 1, s:format_message(a:bullet, a:name, a:lines))
1520 function! s:update_vim()
1528 let pull = s:update.pull
1529 let prog = s:progress_opt(s:nvim || s:vim8)
1530 while 1 " Without TCO, Vim stack is bound to explode
1531 if empty(s:update.todo)
1532 if empty(s:jobs) && !s:update.fin
1533 call s:update_finish()
1534 let s:update.fin = 1
1539 let name = keys(s:update.todo)[0]
1540 let spec = remove(s:update.todo, name)
1541 let new = empty(globpath(spec.dir, '.git', 1))
1543 call s:log(new ? '+' : '*', name, pull ? 'Updating ...' : 'Installing ...')
1546 let has_tag = has_key(spec, 'tag')
1548 let [error, _] = s:git_validate(spec, 0)
1551 let cmd = s:git_version_requirement(2) ? ['git', '-c', 'credential.helper=', 'fetch'] : ['git', 'fetch']
1552 if has_tag && !empty(globpath(spec.dir, '.git/shallow'))
1553 call extend(cmd, ['--depth', '99999999'])
1558 call s:spawn(name, cmd, { 'dir': spec.dir })
1560 let s:jobs[name] = { 'running': 0, 'lines': ['Already installed'], 'error': 0 }
1563 let s:jobs[name] = { 'running': 0, 'lines': s:lines(error), 'error': 1 }
1566 let cmd = ['git', 'clone']
1568 call extend(cmd, s:clone_opt)
1573 call s:spawn(name, extend(cmd, [spec.uri, s:trim(spec.dir)]), { 'new': 1 })
1576 if !s:jobs[name].running
1579 if len(s:jobs) >= s:update.threads
1585 function! s:update_python()
1586 let py_exe = has('python') ? 'python' : 'python3'
1587 execute py_exe "<< EOF"
1594 import Queue as queue
1601 import threading as thr
1606 G_NVIM = vim.eval("has('nvim')") == '1'
1607 G_PULL = vim.eval('s:update.pull') == '1'
1608 G_RETRIES = int(vim.eval('get(g:, "plug_retries", 2)')) + 1
1609 G_TIMEOUT = int(vim.eval('get(g:, "plug_timeout", 60)'))
1610 G_CLONE_OPT = ' '.join(vim.eval('s:clone_opt'))
1611 G_PROGRESS = vim.eval('s:progress_opt(1)')
1612 G_LOG_PROB = 1.0 / int(vim.eval('s:update.threads'))
1613 G_STOP = thr.Event()
1614 G_IS_WIN = vim.eval('s:is_win') == '1'
1616 class PlugError(Exception):
1617 def __init__(self, msg):
1619 class CmdTimedOut(PlugError):
1621 class CmdFailed(PlugError):
1623 class InvalidURI(PlugError):
1625 class Action(object):
1626 INSTALL, UPDATE, ERROR, DONE = ['+', '*', 'x', '-']
1628 class Buffer(object):
1629 def __init__(self, lock, num_plugs, is_pull):
1631 self.event = 'Updating' if is_pull else 'Installing'
1633 self.maxy = int(vim.eval('winheight(".")'))
1634 self.num_plugs = num_plugs
1636 def __where(self, name):
1637 """ Find first line with name in current buffer. Return line num. """
1638 found, lnum = False, 0
1639 matcher = re.compile('^[-+x*] {0}:'.format(name))
1640 for line in vim.current.buffer:
1641 if matcher.search(line) is not None:
1651 curbuf = vim.current.buffer
1652 curbuf[0] = self.event + ' plugins ({0}/{1})'.format(len(self.bar), self.num_plugs)
1654 num_spaces = self.num_plugs - len(self.bar)
1655 curbuf[1] = '[{0}{1}]'.format(self.bar, num_spaces * ' ')
1658 vim.command('normal! 2G')
1659 vim.command('redraw')
1661 def write(self, action, name, lines):
1662 first, rest = lines[0], lines[1:]
1663 msg = ['{0} {1}{2}{3}'.format(action, name, ': ' if first else '', first)]
1664 msg.extend([' ' + line for line in rest])
1667 if action == Action.ERROR:
1669 vim.command("call add(s:update.errors, '{0}')".format(name))
1670 elif action == Action.DONE:
1673 curbuf = vim.current.buffer
1674 lnum = self.__where(name)
1675 if lnum != -1: # Found matching line num
1677 if lnum > self.maxy and action in set([Action.INSTALL, Action.UPDATE]):
1681 curbuf.append(msg, lnum)
1687 class Command(object):
1688 CD = 'cd /d' if G_IS_WIN else 'cd'
1690 def __init__(self, cmd, cmd_dir=None, timeout=60, cb=None, clean=None):
1693 self.cmd = '{0} {1} && {2}'.format(Command.CD, cmd_dir, self.cmd)
1694 self.timeout = timeout
1695 self.callback = cb if cb else (lambda msg: None)
1696 self.clean = clean if clean else (lambda: None)
1701 """ Returns true only if command still running. """
1702 return self.proc and self.proc.poll() is None
1704 def execute(self, ntries=3):
1705 """ Execute the command with ntries if CmdTimedOut.
1706 Returns the output of the command if no Exception.
1708 attempt, finished, limit = 0, False, self.timeout
1713 result = self.try_command()
1717 if attempt != ntries:
1719 self.timeout += limit
1723 def notify_retry(self):
1724 """ Retry required for command, notify user. """
1725 for count in range(3, 0, -1):
1727 raise KeyboardInterrupt
1728 msg = 'Timeout. Will retry in {0} second{1} ...'.format(
1729 count, 's' if count != 1 else '')
1730 self.callback([msg])
1732 self.callback(['Retrying ...'])
1734 def try_command(self):
1735 """ Execute a cmd & poll for callback. Returns list of output.
1736 Raises CmdFailed -> return code for Popen isn't 0
1737 Raises CmdTimedOut -> command exceeded timeout without new output
1742 tfile = tempfile.NamedTemporaryFile(mode='w+b')
1743 preexec_fn = not G_IS_WIN and os.setsid or None
1744 self.proc = subprocess.Popen(self.cmd, stdout=tfile,
1745 stderr=subprocess.STDOUT,
1746 stdin=subprocess.PIPE, shell=True,
1747 preexec_fn=preexec_fn)
1748 thrd = thr.Thread(target=(lambda proc: proc.wait()), args=(self.proc,))
1751 thread_not_started = True
1752 while thread_not_started:
1755 thread_not_started = False
1756 except RuntimeError:
1761 raise KeyboardInterrupt
1763 if first_line or random.random() < G_LOG_PROB:
1765 line = '' if G_IS_WIN else nonblock_read(tfile.name)
1767 self.callback([line])
1769 time_diff = time.time() - os.path.getmtime(tfile.name)
1770 if time_diff > self.timeout:
1771 raise CmdTimedOut(['Timeout!'])
1776 result = [line.decode('utf-8', 'replace').rstrip() for line in tfile]
1778 if self.proc.returncode != 0:
1779 raise CmdFailed([''] + result)
1786 def terminate(self):
1787 """ Terminate process and cleanup. """
1790 os.kill(self.proc.pid, signal.SIGINT)
1792 os.killpg(self.proc.pid, signal.SIGTERM)
1795 class Plugin(object):
1796 def __init__(self, name, args, buf_q, lock):
1801 self.tag = args.get('tag', 0)
1805 if os.path.exists(self.args['dir']):
1810 thread_vim_command("let s:update.new['{0}'] = 1".format(self.name))
1811 except PlugError as exc:
1812 self.write(Action.ERROR, self.name, exc.msg)
1813 except KeyboardInterrupt:
1815 self.write(Action.ERROR, self.name, ['Interrupted!'])
1817 # Any exception except those above print stack trace
1818 msg = 'Trace:\n{0}'.format(traceback.format_exc().rstrip())
1819 self.write(Action.ERROR, self.name, msg.split('\n'))
1823 target = self.args['dir']
1824 if target[-1] == '\\':
1825 target = target[0:-1]
1830 shutil.rmtree(target)
1835 self.write(Action.INSTALL, self.name, ['Installing ...'])
1836 callback = functools.partial(self.write, Action.INSTALL, self.name)
1837 cmd = 'git clone {0} {1} {2} {3} 2>&1'.format(
1838 '' if self.tag else G_CLONE_OPT, G_PROGRESS, self.args['uri'],
1840 com = Command(cmd, None, G_TIMEOUT, callback, clean(target))
1841 result = com.execute(G_RETRIES)
1842 self.write(Action.DONE, self.name, result[-1:])
1845 cmd = 'git rev-parse --abbrev-ref HEAD 2>&1 && git config -f .git/config remote.origin.url'
1846 command = Command(cmd, self.args['dir'], G_TIMEOUT,)
1847 result = command.execute(G_RETRIES)
1851 actual_uri = self.repo_uri()
1852 expect_uri = self.args['uri']
1853 regex = re.compile(r'^(?:\w+://)?(?:[^@/]*@)?([^:/]*(?::[0-9]*)?)[:/](.*?)(?:\.git)?/?$')
1854 ma = regex.match(actual_uri)
1855 mb = regex.match(expect_uri)
1856 if ma is None or mb is None or ma.groups() != mb.groups():
1858 'Invalid URI: {0}'.format(actual_uri),
1859 'Expected {0}'.format(expect_uri),
1860 'PlugClean required.']
1861 raise InvalidURI(msg)
1864 self.write(Action.UPDATE, self.name, ['Updating ...'])
1865 callback = functools.partial(self.write, Action.UPDATE, self.name)
1866 fetch_opt = '--depth 99999999' if self.tag and os.path.isfile(os.path.join(self.args['dir'], '.git/shallow')) else ''
1867 cmd = 'git fetch {0} {1} 2>&1'.format(fetch_opt, G_PROGRESS)
1868 com = Command(cmd, self.args['dir'], G_TIMEOUT, callback)
1869 result = com.execute(G_RETRIES)
1870 self.write(Action.DONE, self.name, result[-1:])
1872 self.write(Action.DONE, self.name, ['Already installed'])
1874 def write(self, action, name, msg):
1875 self.buf_q.put((action, name, msg))
1877 class PlugThread(thr.Thread):
1878 def __init__(self, tname, args):
1879 super(PlugThread, self).__init__()
1884 thr.current_thread().name = self.tname
1885 buf_q, work_q, lock = self.args
1888 while not G_STOP.is_set():
1889 name, args = work_q.get_nowait()
1890 plug = Plugin(name, args, buf_q, lock)
1896 class RefreshThread(thr.Thread):
1897 def __init__(self, lock):
1898 super(RefreshThread, self).__init__()
1905 thread_vim_command('noautocmd normal! a')
1909 self.running = False
1912 def thread_vim_command(cmd):
1913 vim.session.threadsafe_call(lambda: vim.command(cmd))
1915 def thread_vim_command(cmd):
1919 return '"' + name.replace('"', '\"') + '"'
1921 def nonblock_read(fname):
1922 """ Read a file with nonblock flag. Return the last line. """
1923 fread = os.open(fname, os.O_RDONLY | os.O_NONBLOCK)
1924 buf = os.read(fread, 100000).decode('utf-8', 'replace')
1927 line = buf.rstrip('\r\n')
1928 left = max(line.rfind('\r'), line.rfind('\n'))
1936 thr.current_thread().name = 'main'
1937 nthreads = int(vim.eval('s:update.threads'))
1938 plugs = vim.eval('s:update.todo')
1939 mac_gui = vim.eval('s:mac_gui') == '1'
1942 buf = Buffer(lock, len(plugs), G_PULL)
1943 buf_q, work_q = queue.Queue(), queue.Queue()
1944 for work in plugs.items():
1947 start_cnt = thr.active_count()
1948 for num in range(nthreads):
1949 tname = 'PlugT-{0:02}'.format(num)
1950 thread = PlugThread(tname, (buf_q, work_q, lock))
1953 rthread = RefreshThread(lock)
1956 while not buf_q.empty() or thr.active_count() != start_cnt:
1958 action, name, msg = buf_q.get(True, 0.25)
1959 buf.write(action, name, ['OK'] if not msg else msg)
1963 except KeyboardInterrupt:
1974 function! s:update_ruby()
1977 SEP = ["\r", "\n", nil]
1981 char = readchar rescue return
1982 if SEP.include? char.chr
1991 end unless defined?(PlugStream)
1994 %["#{arg.gsub('"', '\"')}"]
1999 if /mswin|mingw|bccwin/ =~ RUBY_PLATFORM
2000 pids.each { |pid| Process.kill 'INT', pid.to_i rescue nil }
2002 unless `which pgrep 2> /dev/null`.empty?
2004 until children.empty?
2005 children = children.map { |pid|
2006 `pgrep -P #{pid}`.lines.map { |l| l.chomp }
2011 pids.each { |pid| Process.kill 'TERM', pid.to_i rescue nil }
2015 def compare_git_uri a, b
2016 regex = %r{^(?:\w+://)?(?:[^@/]*@)?([^:/]*(?::[0-9]*)?)[:/](.*?)(?:\.git)?/?$}
2017 regex.match(a).to_a.drop(1) == regex.match(b).to_a.drop(1)
2024 iswin = VIM::evaluate('s:is_win').to_i == 1
2025 pull = VIM::evaluate('s:update.pull').to_i == 1
2026 base = VIM::evaluate('g:plug_home')
2027 all = VIM::evaluate('s:update.todo')
2028 limit = VIM::evaluate('get(g:, "plug_timeout", 60)')
2029 tries = VIM::evaluate('get(g:, "plug_retries", 2)') + 1
2030 nthr = VIM::evaluate('s:update.threads').to_i
2031 maxy = VIM::evaluate('winheight(".")').to_i
2032 vim7 = VIM::evaluate('v:version').to_i <= 703 && RUBY_PLATFORM =~ /darwin/
2033 cd = iswin ? 'cd /d' : 'cd'
2034 tot = VIM::evaluate('len(s:update.todo)') || 0
2036 skip = 'Already installed'
2038 take1 = proc { mtx.synchronize { running && all.shift } }
2041 $curbuf[1] = "#{pull ? 'Updating' : 'Installing'} plugins (#{cnt}/#{tot})"
2042 $curbuf[2] = '[' + bar.ljust(tot) + ']'
2043 VIM::command('normal! 2G')
2044 VIM::command('redraw')
2046 where = proc { |name| (1..($curbuf.length)).find { |l| $curbuf[l] =~ /^[-+x*] #{name}:/ } }
2047 log = proc { |name, result, type|
2049 ing = ![true, false].include?(type)
2050 bar += type ? '=' : 'x' unless ing
2052 when :install then '+' when :update then '*'
2053 when true, nil then '-' else
2054 VIM::command("call add(s:update.errors, '#{name}')")
2058 if type || type.nil?
2059 ["#{b} #{name}: #{result.lines.to_a.last || 'OK'}"]
2060 elsif result =~ /^Interrupted|^Timeout/
2061 ["#{b} #{name}: #{result}"]
2063 ["#{b} #{name}"] + result.lines.map { |l| " " << l }
2065 if lnum = where.call(name)
2067 lnum = 4 if ing && lnum > maxy
2069 result.each_with_index do |line, offset|
2070 $curbuf.append((lnum || 4) - 1 + offset, line.gsub(/\e\[./, '').chomp)
2075 bt = proc { |cmd, name, type, cleanup|
2083 Timeout::timeout(timeout) do
2084 tmp = VIM::evaluate('tempname()')
2085 system("(#{cmd}) > #{tmp}")
2086 data = File.read(tmp).chomp
2087 File.unlink tmp rescue nil
2090 fd = IO.popen(cmd).extend(PlugStream)
2092 log_prob = 1.0 / nthr
2093 while line = Timeout::timeout(timeout) { fd.get_line }
2095 log.call name, line.chomp, type if name && (first_line || rand < log_prob)
2100 [$? == 0, data.chomp]
2101 rescue Timeout::Error, Interrupt => e
2102 if fd && !fd.closed?
2106 cleanup.call if cleanup
2107 if e.is_a?(Timeout::Error) && tried < tries
2108 3.downto(1) do |countdown|
2109 s = countdown > 1 ? 's' : ''
2110 log.call name, "Timeout. Will retry in #{countdown} second#{s} ...", type
2113 log.call name, 'Retrying ...', type
2116 [false, e.is_a?(Interrupt) ? "Interrupted!" : "Timeout!"]
2119 main = Thread.current
2121 watcher = Thread.new {
2123 while VIM::evaluate('getchar(1)')
2127 require 'io/console' # >= Ruby 1.9
2128 nil until IO.console.getch == 3.chr
2132 threads.each { |t| t.raise Interrupt } unless vim7
2134 threads.each { |t| t.join rescue nil }
2137 refresh = Thread.new {
2140 break unless running
2141 VIM::command('noautocmd normal! a')
2145 } if VIM::evaluate('s:mac_gui') == 1
2147 clone_opt = VIM::evaluate('s:clone_opt').join(' ')
2148 progress = VIM::evaluate('s:progress_opt(1)')
2151 threads << Thread.new {
2152 while pair = take1.call
2154 dir, uri, tag = pair.last.values_at *%w[dir uri tag]
2155 exists = File.directory? dir
2158 chdir = "#{cd} #{iswin ? dir : esc(dir)}"
2159 ret, data = bt.call "#{chdir} && git rev-parse --abbrev-ref HEAD 2>&1 && git config -f .git/config remote.origin.url", nil, nil, nil
2160 current_uri = data.lines.to_a.last
2162 if data =~ /^Interrupted|^Timeout/
2165 [false, [data.chomp, "PlugClean required."].join($/)]
2167 elsif !compare_git_uri(current_uri, uri)
2168 [false, ["Invalid URI: #{current_uri}",
2170 "PlugClean required."].join($/)]
2173 log.call name, 'Updating ...', :update
2174 fetch_opt = (tag && File.exist?(File.join(dir, '.git/shallow'))) ? '--depth 99999999' : ''
2175 bt.call "#{chdir} && git fetch #{fetch_opt} #{progress} 2>&1", name, :update, nil
2181 d = esc dir.sub(%r{[\\/]+$}, '')
2182 log.call name, 'Installing ...', :install
2183 bt.call "git clone #{clone_opt unless tag} #{progress} #{uri} #{d} 2>&1", name, :install, proc {
2187 mtx.synchronize { VIM::command("let s:update.new['#{name}'] = 1") } if !exists && ok
2188 log.call name, result, ok
2193 threads.each { |t| t.join rescue nil }
2195 refresh.kill if refresh
2200 function! s:shellesc_cmd(arg, script)
2201 let escaped = substitute('"'.a:arg.'"', '[&|<>()@^!"]', '^&', 'g')
2202 return substitute(escaped, '%', (a:script ? '%' : '^') . '&', 'g')
2205 function! s:shellesc_ps1(arg)
2206 return "'".substitute(escape(a:arg, '\"'), "'", "''", 'g')."'"
2209 function! s:shellesc_sh(arg)
2210 return "'".substitute(a:arg, "'", "'\\\\''", 'g')."'"
2213 " Escape the shell argument based on the shell.
2214 " Vim and Neovim's shellescape() are insufficient.
2215 " 1. shellslash determines whether to use single/double quotes.
2216 " Double-quote escaping is fragile for cmd.exe.
2217 " 2. It does not work for powershell.
2218 " 3. It does not work for *sh shells if the command is executed
2219 " via cmd.exe (ie. cmd.exe /c sh -c command command_args)
2220 " 4. It does not support batchfile syntax.
2222 " Accepts an optional dictionary with the following keys:
2223 " - shell: same as Vim/Neovim 'shell' option.
2224 " If unset, fallback to 'cmd.exe' on Windows or 'sh'.
2225 " - script: If truthy and shell is cmd.exe, escape for batchfile syntax.
2226 function! plug#shellescape(arg, ...)
2227 if a:arg =~# '^[A-Za-z0-9_/:.-]\+$'
2230 let opts = a:0 > 0 && type(a:1) == s:TYPE.dict ? a:1 : {}
2231 let shell = get(opts, 'shell', s:is_win ? 'cmd.exe' : 'sh')
2232 let script = get(opts, 'script', 1)
2233 if shell =~# 'cmd\(\.exe\)\?$'
2234 return s:shellesc_cmd(a:arg, script)
2235 elseif s:is_powershell(shell)
2236 return s:shellesc_ps1(a:arg)
2238 return s:shellesc_sh(a:arg)
2241 function! s:glob_dir(path)
2242 return map(filter(s:glob(a:path, '**'), 'isdirectory(v:val)'), 's:dirpath(v:val)')
2245 function! s:progress_bar(line, bar, total)
2246 call setline(a:line, '[' . s:lpad(a:bar, a:total) . ']')
2249 function! s:compare_git_uri(a, b)
2250 " See `git help clone'
2251 " https:// [user@] github.com[:port] / junegunn/vim-plug [.git]
2252 " [git@] github.com[:port] : junegunn/vim-plug [.git]
2253 " file:// / junegunn/vim-plug [/]
2254 " / junegunn/vim-plug [/]
2255 let pat = '^\%(\w\+://\)\='.'\%([^@/]*@\)\='.'\([^:/]*\%(:[0-9]*\)\=\)'.'[:/]'.'\(.\{-}\)'.'\%(\.git\)\=/\?$'
2256 let ma = matchlist(a:a, pat)
2257 let mb = matchlist(a:b, pat)
2258 return ma[1:2] ==# mb[1:2]
2261 function! s:format_message(bullet, name, message)
2263 return [printf('%s %s: %s', a:bullet, a:name, s:lastline(a:message))]
2265 let lines = map(s:lines(a:message), '" ".v:val')
2266 return extend([printf('x %s:', a:name)], lines)
2270 function! s:with_cd(cmd, dir, ...)
2271 let script = a:0 > 0 ? a:1 : 1
2272 return printf('cd%s %s && %s', s:is_win ? ' /d' : '', plug#shellescape(a:dir, {'script': script}), a:cmd)
2275 function! s:system(cmd, ...)
2278 let [sh, shellcmdflag, shrd] = s:chsh(1)
2279 if type(a:cmd) == s:TYPE.list
2280 " Neovim's system() supports list argument to bypass the shell
2281 " but it cannot set the working directory for the command.
2282 " Assume that the command does not rely on the shell.
2283 if has('nvim') && a:0 == 0
2284 return system(a:cmd)
2286 let cmd = join(map(copy(a:cmd), 'plug#shellescape(v:val, {"shell": &shell, "script": 0})'))
2287 if s:is_powershell(&shell)
2288 let cmd = '& ' . cmd
2294 let cmd = s:with_cd(cmd, a:1, type(a:cmd) != s:TYPE.list)
2296 if s:is_win && type(a:cmd) != s:TYPE.list
2297 let [batchfile, cmd] = s:batchfile(cmd)
2301 let [&shell, &shellcmdflag, &shellredir] = [sh, shellcmdflag, shrd]
2302 if s:is_win && filereadable(batchfile)
2303 call delete(batchfile)
2308 function! s:system_chomp(...)
2309 let ret = call('s:system', a:000)
2310 return v:shell_error ? '' : substitute(ret, '\n$', '', '')
2313 function! s:git_validate(spec, check_branch)
2315 if isdirectory(a:spec.dir)
2316 let result = [s:git_local_branch(a:spec.dir), s:git_origin_url(a:spec.dir)]
2317 let remote = result[-1]
2319 let err = join([remote, 'PlugClean required.'], "\n")
2320 elseif !s:compare_git_uri(remote, a:spec.uri)
2321 let err = join(['Invalid URI: '.remote,
2322 \ 'Expected: '.a:spec.uri,
2323 \ 'PlugClean required.'], "\n")
2324 elseif a:check_branch && has_key(a:spec, 'commit')
2325 let sha = s:git_revision(a:spec.dir)
2327 let err = join(add(result, 'PlugClean required.'), "\n")
2328 elseif !s:hash_match(sha, a:spec.commit)
2329 let err = join([printf('Invalid HEAD (expected: %s, actual: %s)',
2330 \ a:spec.commit[:6], sha[:6]),
2331 \ 'PlugUpdate required.'], "\n")
2333 elseif a:check_branch
2334 let current_branch = result[0]
2336 let origin_branch = s:git_origin_branch(a:spec)
2337 if has_key(a:spec, 'tag')
2338 let tag = s:system_chomp('git describe --exact-match --tags HEAD 2>&1', a:spec.dir)
2339 if a:spec.tag !=# tag && a:spec.tag !~ '\*'
2340 let err = printf('Invalid tag: %s (expected: %s). Try PlugUpdate.',
2341 \ (empty(tag) ? 'N/A' : tag), a:spec.tag)
2344 elseif origin_branch !=# current_branch
2345 let err = printf('Invalid branch: %s (expected: %s). Try PlugUpdate.',
2346 \ current_branch, origin_branch)
2349 let [ahead, behind] = split(s:lastline(s:system([
2350 \ 'git', 'rev-list', '--count', '--left-right',
2351 \ printf('HEAD...origin/%s', origin_branch)
2352 \ ], a:spec.dir)), '\t')
2353 if !v:shell_error && ahead
2355 " Only mention PlugClean if diverged, otherwise it's likely to be
2356 " pushable (and probably not that messed up).
2358 \ "Diverged from origin/%s (%d commit(s) ahead and %d commit(s) behind!\n"
2359 \ .'Backup local changes and run PlugClean and PlugUpdate to reinstall it.', origin_branch, ahead, behind)
2361 let err = printf("Ahead of origin/%s by %d commit(s).\n"
2362 \ .'Cannot update until local changes are pushed.',
2363 \ origin_branch, ahead)
2369 let err = 'Not found'
2371 return [err, err =~# 'PlugClean']
2374 function! s:rm_rf(dir)
2375 if isdirectory(a:dir)
2376 return s:system(s:is_win
2377 \ ? 'rmdir /S /Q '.plug#shellescape(a:dir)
2378 \ : ['rm', '-rf', a:dir])
2382 function! s:clean(force)
2384 call append(0, 'Searching for invalid plugins in '.g:plug_home)
2387 " List of valid directories
2390 let [cnt, total] = [0, len(g:plugs)]
2391 for [name, spec] in items(g:plugs)
2392 if !s:is_managed(name)
2393 call add(dirs, spec.dir)
2395 let [err, clean] = s:git_validate(spec, 1)
2397 let errs[spec.dir] = s:lines(err)[0]
2399 call add(dirs, spec.dir)
2403 call s:progress_bar(2, repeat('=', cnt), total)
2410 let allowed[s:dirpath(s:plug_fnamemodify(dir, ':h:h'))] = 1
2411 let allowed[dir] = 1
2412 for child in s:glob_dir(dir)
2413 let allowed[child] = 1
2418 let found = sort(s:glob_dir(g:plug_home))
2420 let f = remove(found, 0)
2421 if !has_key(allowed, f) && isdirectory(f)
2423 call append(line('$'), '- ' . f)
2425 call append(line('$'), ' ' . errs[f])
2427 let found = filter(found, 'stridx(v:val, f) != 0')
2434 call append(line('$'), 'Already clean.')
2436 let s:clean_count = 0
2437 call append(3, ['Directories to delete:', ''])
2439 if a:force || s:ask_no_interrupt('Delete all directories?')
2440 call s:delete([6, line('$')], 1)
2442 call setline(4, 'Cancelled.')
2443 nnoremap <silent> <buffer> d :set opfunc=<sid>delete_op<cr>g@
2444 nmap <silent> <buffer> dd d_
2445 xnoremap <silent> <buffer> d :<c-u>call <sid>delete_op(visualmode(), 1)<cr>
2446 echo 'Delete the lines (d{motion}) to delete the corresponding directories'
2450 setlocal nomodifiable
2453 function! s:delete_op(type, ...)
2454 call s:delete(a:0 ? [line("'<"), line("'>")] : [line("'["), line("']")], 0)
2457 function! s:delete(range, force)
2458 let [l1, l2] = a:range
2462 let line = getline(l1)
2463 if line =~ '^- ' && isdirectory(line[2:])
2466 let answer = force ? 1 : s:ask('Delete '.line[2:].'?', 1)
2467 let force = force || answer > 1
2469 let err = s:rm_rf(line[2:])
2472 call setline(l1, '~'.line[1:])
2473 let s:clean_count += 1
2476 call append(l1 - 1, s:format_message('x', line[1:], err))
2477 let l2 += len(s:lines(err))
2480 let msg = printf('Removed %d directories.', s:clean_count)
2482 let msg .= printf(' Failed to remove %d directories.', err_count)
2484 call setline(4, msg)
2485 setlocal nomodifiable
2492 function! s:upgrade()
2493 echo 'Downloading the latest version of vim-plug'
2495 let tmp = s:plug_tempname()
2496 let new = tmp . '/plug.vim'
2499 let out = s:system(['git', 'clone', '--depth', '1', s:plug_src, tmp])
2501 return s:err('Error upgrading vim-plug: '. out)
2504 if readfile(s:me) ==# readfile(new)
2505 echo 'vim-plug is already up-to-date'
2508 call rename(s:me, s:me . '.old')
2509 call rename(new, s:me)
2511 echo 'vim-plug has been upgraded'
2515 silent! call s:rm_rf(tmp)
2519 function! s:upgrade_specs()
2520 for spec in values(g:plugs)
2521 let spec.frozen = get(spec, 'frozen', 0)
2525 function! s:status()
2527 call append(0, 'Checking plugins')
2532 let [cnt, total] = [0, len(g:plugs)]
2533 for [name, spec] in items(g:plugs)
2534 let is_dir = isdirectory(spec.dir)
2535 if has_key(spec, 'uri')
2537 let [err, _] = s:git_validate(spec, 1)
2538 let [valid, msg] = [empty(err), empty(err) ? 'OK' : err]
2540 let [valid, msg] = [0, 'Not found. Try PlugInstall.']
2544 let [valid, msg] = [1, 'OK']
2546 let [valid, msg] = [0, 'Not found.']
2551 " `s:loaded` entry can be missing if PlugUpgraded
2552 if is_dir && get(s:loaded, name, -1) == 0
2554 let msg .= ' (not loaded)'
2556 call s:progress_bar(2, repeat('=', cnt), total)
2557 call append(3, s:format_message(valid ? '-' : 'x', name, msg))
2561 call setline(1, 'Finished. '.ecnt.' error(s).')
2563 setlocal nomodifiable
2565 echo "Press 'L' on each line to load plugin, or 'U' to update"
2566 nnoremap <silent> <buffer> L :call <SID>status_load(line('.'))<cr>
2567 xnoremap <silent> <buffer> L :call <SID>status_load(line('.'))<cr>
2571 function! s:extract_name(str, prefix, suffix)
2572 return matchstr(a:str, '^'.a:prefix.' \zs[^:]\+\ze:.*'.a:suffix.'$')
2575 function! s:status_load(lnum)
2576 let line = getline(a:lnum)
2577 let name = s:extract_name(line, '-', '(not loaded)')
2579 call plug#load(name)
2581 call setline(a:lnum, substitute(line, ' (not loaded)$', '', ''))
2582 setlocal nomodifiable
2586 function! s:status_update() range
2587 let lines = getline(a:firstline, a:lastline)
2588 let names = filter(map(lines, 's:extract_name(v:val, "[x-]", "")'), '!empty(v:val)')
2591 execute 'PlugUpdate' join(names)
2595 function! s:is_preview_window_open()
2603 function! s:find_name(lnum)
2604 for lnum in reverse(range(1, a:lnum))
2605 let line = getline(lnum)
2609 let name = s:extract_name(line, '-', '')
2617 function! s:preview_commit()
2618 if b:plug_preview < 0
2619 let b:plug_preview = !s:is_preview_window_open()
2622 let sha = matchstr(getline('.'), '^ \X*\zs[0-9a-f]\{7,9}')
2624 let name = matchstr(getline('.'), '^- \zs[^:]*\ze:$')
2628 let title = 'HEAD@{1}..'
2629 let command = 'git diff --no-color HEAD@{1}'
2632 let command = 'git show --no-color --pretty=medium '.sha
2633 let name = s:find_name(line('.'))
2636 if empty(name) || !has_key(g:plugs, name) || !isdirectory(g:plugs[name].dir)
2640 if exists('g:plug_pwindow') && !s:is_preview_window_open()
2641 execute g:plug_pwindow
2644 execute 'pedit' title
2647 setlocal previewwindow filetype=git buftype=nofile bufhidden=wipe nobuflisted modifiable
2650 let [sh, shellcmdflag, shrd] = s:chsh(1)
2651 let cmd = 'cd '.plug#shellescape(g:plugs[name].dir).' && '.command
2653 let [batchfile, cmd] = s:batchfile(cmd)
2655 execute 'silent %!' cmd
2657 let [&shell, &shellcmdflag, &shellredir] = [sh, shellcmdflag, shrd]
2658 if s:is_win && filereadable(batchfile)
2659 call delete(batchfile)
2662 setlocal nomodifiable
2663 nnoremap <silent> <buffer> q :q<cr>
2667 function! s:section(flags)
2668 call search('\(^[x-] \)\@<=[^:]\+:', a:flags)
2671 function! s:format_git_log(line)
2673 let tokens = split(a:line, nr2char(1))
2675 return indent.substitute(a:line, '\s*$', '', '')
2677 let [graph, sha, refs, subject, date] = tokens
2678 let tag = matchstr(refs, 'tag: [^,)]\+')
2679 let tag = empty(tag) ? ' ' : ' ('.tag.') '
2680 return printf('%s%s%s%s%s (%s)', indent, graph, sha, tag, subject, date)
2683 function! s:append_ul(lnum, text)
2684 call append(a:lnum, ['', a:text, repeat('-', len(a:text))])
2689 call append(0, ['Collecting changes ...', ''])
2692 let total = filter(copy(g:plugs), 's:is_managed(v:key) && isdirectory(v:val.dir)')
2693 call s:progress_bar(2, bar, len(total))
2694 for origin in [1, 0]
2695 let plugs = reverse(sort(items(filter(copy(total), (origin ? '' : '!').'(has_key(v:val, "commit") || has_key(v:val, "tag"))'))))
2699 call s:append_ul(2, origin ? 'Pending updates:' : 'Last update:')
2701 let branch = s:git_origin_branch(v)
2703 let range = origin ? '..origin/'.branch : 'HEAD@{1}..'
2704 let cmd = ['git', 'log', '--graph', '--color=never']
2705 if s:git_version_requirement(2, 10, 0)
2706 call add(cmd, '--no-show-signature')
2708 call extend(cmd, ['--pretty=format:%x01%h%x01%d%x01%s%x01%cr', range])
2709 if has_key(v, 'rtp')
2710 call extend(cmd, ['--', v.rtp])
2712 let diff = s:system_chomp(cmd, v.dir)
2714 let ref = has_key(v, 'tag') ? (' (tag: '.v.tag.')') : has_key(v, 'commit') ? (' '.v.commit) : ''
2715 call append(5, extend(['', '- '.k.':'.ref], map(s:lines(diff), 's:format_git_log(v:val)')))
2716 let cnts[origin] += 1
2720 call s:progress_bar(2, bar, len(total))
2725 call append(5, ['', 'N/A'])
2728 call setline(1, printf('%d plugin(s) updated.', cnts[0])
2729 \ . (cnts[1] ? printf(' %d plugin(s) have pending updates.', cnts[1]) : ''))
2731 if cnts[0] || cnts[1]
2732 nnoremap <silent> <buffer> <plug>(plug-preview) :silent! call <SID>preview_commit()<cr>
2733 if empty(maparg("\<cr>", 'n'))
2734 nmap <buffer> <cr> <plug>(plug-preview)
2736 if empty(maparg('o', 'n'))
2737 nmap <buffer> o <plug>(plug-preview)
2741 nnoremap <silent> <buffer> X :call <SID>revert()<cr>
2742 echo "Press 'X' on each block to revert the update"
2745 setlocal nomodifiable
2748 function! s:revert()
2749 if search('^Pending updates', 'bnW')
2753 let name = s:find_name(line('.'))
2754 if empty(name) || !has_key(g:plugs, name) ||
2755 \ input(printf('Revert the update of %s? (y/N) ', name)) !~? '^y'
2759 call s:system('git reset --hard HEAD@{1} && git checkout '.plug#shellescape(g:plugs[name].branch).' --', g:plugs[name].dir)
2762 setlocal nomodifiable
2766 function! s:snapshot(force, ...) abort
2769 call append(0, ['" Generated by vim-plug',
2770 \ '" '.strftime("%c"),
2771 \ '" :source this file in vim to restore the snapshot',
2772 \ '" or execute: vim -S snapshot.vim',
2773 \ '', '', 'PlugUpdate!'])
2775 let anchor = line('$') - 3
2776 let names = sort(keys(filter(copy(g:plugs),
2777 \'has_key(v:val, "uri") && isdirectory(v:val.dir)')))
2778 for name in reverse(names)
2779 let sha = has_key(g:plugs[name], 'commit') ? g:plugs[name].commit : s:git_revision(g:plugs[name].dir)
2781 call append(anchor, printf("silent! let g:plugs['%s'].commit = '%s'", name, sha))
2787 let fn = s:plug_expand(a:1)
2788 if filereadable(fn) && !(a:force || s:ask(a:1.' already exists. Overwrite?'))
2791 call writefile(getline(1, '$'), fn)
2792 echo 'Saved as '.a:1
2793 silent execute 'e' s:esc(fn)
2798 function! s:split_rtp()
2799 return split(&rtp, '\\\@<!,')
2802 let s:first_rtp = s:escrtp(get(s:split_rtp(), 0, ''))
2803 let s:last_rtp = s:escrtp(get(s:split_rtp(), -1, ''))
2805 if exists('g:plugs')
2806 let g:plugs_order = get(g:, 'plugs_order', keys(g:plugs))
2807 call s:upgrade_specs()
2808 call s:define_commands()
2811 let &cpo = s:cpo_save