1 " vim-plug: Vim plugin manager
2 " ============================
4 " 1. Download plug.vim and put it in 'autoload' directory
7 " curl -fLo ~/.vim/autoload/plug.vim --create-dirs \
8 " https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim
11 " sh -c 'curl -fLo "${XDG_DATA_HOME:-$HOME/.local/share}"/nvim/site/autoload/plug.vim --create-dirs \
12 " https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim'
14 " 2. Add a vim-plug section to your ~/.vimrc (or ~/.config/nvim/init.vim for Neovim)
18 " " List your plugins here
19 " Plug 'tpope/vim-sensible'
23 " 3. Reload the file or restart Vim, then you can,
25 " :PlugInstall to install plugins
26 " :PlugUpdate to update plugins
27 " :PlugDiff to review the changes from the last update
28 " :PlugClean to remove plugins no longer in the list
30 " For more information, see https://github.com/junegunn/vim-plug
33 " Copyright (c) 2024 Junegunn Choi
37 " Permission is hereby granted, free of charge, to any person obtaining
38 " a copy of this software and associated documentation files (the
39 " "Software"), to deal in the Software without restriction, including
40 " without limitation the rights to use, copy, modify, merge, publish,
41 " distribute, sublicense, and/or sell copies of the Software, and to
42 " permit persons to whom the Software is furnished to do so, subject to
43 " the following conditions:
45 " The above copyright notice and this permission notice shall be
46 " included in all copies or substantial portions of the Software.
48 " THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
49 " EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
50 " MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
51 " NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
52 " LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
53 " OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
54 " WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
56 if exists('g:loaded_plug')
64 let s:plug_src = 'https://github.com/junegunn/vim-plug.git'
65 let s:plug_tab = get(s:, 'plug_tab', -1)
66 let s:plug_buf = get(s:, 'plug_buf', -1)
67 let s:mac_gui = has('gui_macvim') && has('gui_running')
68 let s:is_win = has('win32')
69 let s:nvim = has('nvim-0.2') || (has('nvim') && exists('*jobwait') && !s:is_win)
70 let s:vim8 = has('patch-8.0.0039') && exists('*job_start')
71 if s:is_win && &shellslash
73 let s:me = resolve(expand('<sfile>:p'))
76 let s:me = resolve(expand('<sfile>:p'))
78 let s:base_spec = { 'branch': '', 'frozen': 0 }
83 \ 'funcref': type(function('call'))
85 let s:loaded = get(s:, 'loaded', {})
86 let s:triggers = get(s:, 'triggers', {})
88 function! s:is_powershell(shell)
89 return a:shell =~# 'powershell\(\.exe\)\?$' || a:shell =~# 'pwsh\(\.exe\)\?$'
92 function! s:isabsolute(dir) abort
93 return a:dir =~# '^/' || (has('win32') && a:dir =~? '^\%(\\\|[A-Z]:\)')
96 function! s:git_dir(dir) abort
97 let gitdir = s:trim(a:dir) . '/.git'
98 if isdirectory(gitdir)
101 if !filereadable(gitdir)
104 let gitdir = matchstr(get(readfile(gitdir), 0, ''), '^gitdir: \zs.*')
105 if len(gitdir) && !s:isabsolute(gitdir)
106 let gitdir = a:dir . '/' . gitdir
108 return isdirectory(gitdir) ? gitdir : ''
111 function! s:git_origin_url(dir) abort
112 let gitdir = s:git_dir(a:dir)
113 let config = gitdir . '/config'
114 if empty(gitdir) || !filereadable(config)
117 return matchstr(join(readfile(config)), '\[remote "origin"\].\{-}url\s*=\s*\zs\S*\ze')
120 function! s:git_revision(dir) abort
121 let gitdir = s:git_dir(a:dir)
122 let head = gitdir . '/HEAD'
123 if empty(gitdir) || !filereadable(head)
127 let line = get(readfile(head), 0, '')
128 let ref = matchstr(line, '^ref: \zs.*')
133 if filereadable(gitdir . '/' . ref)
134 return get(readfile(gitdir . '/' . ref), 0, '')
137 if filereadable(gitdir . '/packed-refs')
138 for line in readfile(gitdir . '/packed-refs')
139 if line =~# ' ' . ref
140 return matchstr(line, '^[0-9a-f]*')
148 function! s:git_local_branch(dir) abort
149 let gitdir = s:git_dir(a:dir)
150 let head = gitdir . '/HEAD'
151 if empty(gitdir) || !filereadable(head)
154 let branch = matchstr(get(readfile(head), 0, ''), '^ref: refs/heads/\zs.*')
155 return len(branch) ? branch : 'HEAD'
158 function! s:git_origin_branch(spec)
159 if len(a:spec.branch)
163 " The file may not be present if this is a local repository
164 let gitdir = s:git_dir(a:spec.dir)
165 let origin_head = gitdir.'/refs/remotes/origin/HEAD'
166 if len(gitdir) && filereadable(origin_head)
167 return matchstr(get(readfile(origin_head), 0, ''),
168 \ '^ref: refs/remotes/origin/\zs.*')
171 " The command may not return the name of a branch in detached HEAD state
172 let result = s:lines(s:system('git symbolic-ref --short HEAD', a:spec.dir))
173 return v:shell_error ? '' : result[-1]
177 function! s:plug_call(fn, ...)
178 let shellslash = &shellslash
181 return call(a:fn, a:000)
183 let &shellslash = shellslash
187 function! s:plug_call(fn, ...)
188 return call(a:fn, a:000)
192 function! s:plug_getcwd()
193 return s:plug_call('getcwd')
196 function! s:plug_fnamemodify(fname, mods)
197 return s:plug_call('fnamemodify', a:fname, a:mods)
200 function! s:plug_expand(fmt)
201 return s:plug_call('expand', a:fmt, 1)
204 function! s:plug_tempname()
205 return s:plug_call('tempname')
208 function! plug#begin(...)
210 let home = s:path(s:plug_fnamemodify(s:plug_expand(a:1), ':p'))
211 elseif exists('g:plug_home')
212 let home = s:path(g:plug_home)
214 let home = stdpath('data') . '/plugged'
216 let home = s:path(split(&rtp, ',')[0]) . '/plugged'
218 return s:err('Unable to determine plug home. Try calling plug#begin() with a path argument.')
220 if s:plug_fnamemodify(home, ':t') ==# 'plugin' && s:plug_fnamemodify(home, ':h') ==# s:first_rtp
221 return s:err('Invalid plug home. '.home.' is a standard Vim runtime path and is not allowed.')
224 let g:plug_home = home
226 let g:plugs_order = []
229 call s:define_commands()
233 function! s:define_commands()
234 command! -nargs=+ -bar Plug call plug#(<args>)
235 if !executable('git')
236 return s:err('`git` executable not found. Most commands will not be available. To suppress this message, prepend `silent!` to `call plug#begin(...)`.')
240 \ && (&shell =~# 'cmd\(\.exe\)\?$' || s:is_powershell(&shell))
241 return s:err('vim-plug does not support shell, ' . &shell . ', when shellslash is set.')
244 \ && (has('win32') || has('win32unix'))
245 \ && !has('multi_byte')
246 return s:err('Vim needs +multi_byte feature on Windows to run shell commands. Enable +iconv for best results.')
248 command! -nargs=* -bar -bang -complete=customlist,s:names PlugInstall call s:install(<bang>0, [<f-args>])
249 command! -nargs=* -bar -bang -complete=customlist,s:names PlugUpdate call s:update(<bang>0, [<f-args>])
250 command! -nargs=0 -bar -bang PlugClean call s:clean(<bang>0)
251 command! -nargs=0 -bar PlugUpgrade if s:upgrade() | execute 'source' s:esc(s:me) | endif
252 command! -nargs=0 -bar PlugStatus call s:status()
253 command! -nargs=0 -bar PlugDiff call s:diff()
254 command! -nargs=? -bar -bang -complete=file PlugSnapshot call s:snapshot(<bang>0, <f-args>)
258 return type(a:v) == s:TYPE.list ? a:v : [a:v]
262 return type(a:v) == s:TYPE.string ? a:v : join(a:v, "\n") . "\n"
265 function! s:glob(from, pattern)
266 return s:lines(globpath(a:from, a:pattern))
269 function! s:source(from, ...)
272 for vim in s:glob(a:from, pattern)
273 execute 'source' s:esc(vim)
280 function! s:assoc(dict, key, val)
281 let a:dict[a:key] = add(get(a:dict, a:key, []), a:val)
284 function! s:ask(message, ...)
287 let answer = input(a:message.(a:0 ? ' (y/N/a) ' : ' (y/N) '))
291 return (a:0 && answer =~? '^a') ? 2 : (answer =~? '^y') ? 1 : 0
294 function! s:ask_no_interrupt(...)
296 return call('s:ask', a:000)
302 function! s:lazy(plug, opt)
303 return has_key(a:plug, a:opt) &&
304 \ (empty(s:to_a(a:plug[a:opt])) ||
305 \ !isdirectory(a:plug.dir) ||
306 \ len(s:glob(s:rtp(a:plug), 'plugin')) ||
307 \ len(s:glob(s:rtp(a:plug), 'after/plugin')))
311 if !exists('g:plugs')
312 return s:err('plug#end() called without calling plug#begin() first')
315 if exists('#PlugLOD')
321 let lod = { 'ft': {}, 'map': {}, 'cmd': {} }
323 if get(g:, 'did_load_filetypes', 0)
326 for name in g:plugs_order
327 if !has_key(g:plugs, name)
330 let plug = g:plugs[name]
331 if get(s:loaded, name, 0) || !s:lazy(plug, 'on') && !s:lazy(plug, 'for')
332 let s:loaded[name] = 1
336 if has_key(plug, 'on')
337 let s:triggers[name] = { 'map': [], 'cmd': [] }
338 for cmd in s:to_a(plug.on)
339 if cmd =~? '^<Plug>.\+'
340 if empty(mapcheck(cmd)) && empty(mapcheck(cmd, 'i'))
341 call s:assoc(lod.map, cmd, name)
343 call add(s:triggers[name].map, cmd)
344 elseif cmd =~# '^[A-Z]'
345 let cmd = substitute(cmd, '!*$', '', '')
346 if exists(':'.cmd) != 2
347 call s:assoc(lod.cmd, cmd, name)
349 call add(s:triggers[name].cmd, cmd)
351 call s:err('Invalid `on` option: '.cmd.
352 \ '. Should start with an uppercase letter or `<Plug>`.')
357 if has_key(plug, 'for')
358 let types = s:to_a(plug.for)
360 augroup filetypedetect
361 call s:source(s:rtp(plug), 'ftdetect/**/*.vim', 'after/ftdetect/**/*.vim')
363 call s:source(s:rtp(plug), 'ftdetect/**/*.lua', 'after/ftdetect/**/*.lua')
368 call s:assoc(lod.ft, type, name)
373 for [cmd, names] in items(lod.cmd)
375 \ 'command! -nargs=* -range -bang -complete=file %s call s:lod_cmd(%s, "<bang>", <line1>, <line2>, <q-args>, %s)',
376 \ cmd, string(cmd), string(names))
379 for [map, names] in items(lod.map)
380 for [mode, map_prefix, key_prefix] in
381 \ [['i', '<C-\><C-O>', ''], ['n', '', ''], ['v', '', 'gv'], ['o', '', '']]
383 \ '%snoremap <silent> %s %s:<C-U>call <SID>lod_map(%s, %s, %s, "%s")<CR>',
384 \ mode, map, map_prefix, string(map), string(names), mode != 'i', key_prefix)
388 for [ft, names] in items(lod.ft)
390 execute printf('autocmd FileType %s call <SID>lod_ft(%s, %s)',
391 \ ft, string(ft), string(names))
396 filetype plugin indent on
397 if has('vim_starting')
398 if has('syntax') && !exists('g:syntax_on')
402 call s:reload_plugins()
406 function! s:loaded_names()
407 return filter(copy(g:plugs_order), 'get(s:loaded, v:val, 0)')
410 function! s:load_plugin(spec)
411 call s:source(s:rtp(a:spec), 'plugin/**/*.vim', 'after/plugin/**/*.vim')
413 call s:source(s:rtp(a:spec), 'plugin/**/*.lua', 'after/plugin/**/*.lua')
417 function! s:reload_plugins()
418 for name in s:loaded_names()
419 call s:load_plugin(g:plugs[name])
423 function! s:trim(str)
424 return substitute(a:str, '[\/]\+$', '', '')
427 function! s:version_requirement(val, min)
428 for idx in range(0, len(a:min) - 1)
429 let v = get(a:val, idx, 0)
430 if v < a:min[idx] | return 0
431 elseif v > a:min[idx] | return 1
437 function! s:git_version_requirement(...)
438 if !exists('s:git_version')
439 let s:git_version = map(split(split(s:system(['git', '--version']))[2], '\.'), 'str2nr(v:val)')
441 return s:version_requirement(s:git_version, a:000)
444 function! s:progress_opt(base)
445 return a:base && !s:is_win &&
446 \ s:git_version_requirement(1, 7, 1) ? '--progress' : ''
449 function! s:rtp(spec)
450 return s:path(a:spec.dir . get(a:spec, 'rtp', ''))
454 function! s:path(path)
455 return s:trim(substitute(a:path, '/', '\', 'g'))
458 function! s:dirpath(path)
459 return s:path(a:path) . '\'
462 function! s:is_local_plug(repo)
463 return a:repo =~? '^[a-z]:\|^[%~]'
467 function! s:wrap_cmds(cmds)
470 \ 'setlocal enabledelayedexpansion']
471 \ + (type(a:cmds) == type([]) ? a:cmds : [a:cmds])
474 if !exists('s:codepage')
475 let s:codepage = libcallnr('kernel32.dll', 'GetACP', 0)
477 return map(cmds, printf('iconv(v:val."\r", "%s", "cp%d")', &encoding, s:codepage))
479 return map(cmds, 'v:val."\r"')
482 function! s:batchfile(cmd)
483 let batchfile = s:plug_tempname().'.bat'
484 call writefile(s:wrap_cmds(a:cmd), batchfile)
485 let cmd = plug#shellescape(batchfile, {'shell': &shell, 'script': 0})
486 if s:is_powershell(&shell)
489 return [batchfile, cmd]
492 function! s:path(path)
493 return s:trim(a:path)
496 function! s:dirpath(path)
497 return substitute(a:path, '[/\\]*$', '/', '')
500 function! s:is_local_plug(repo)
501 return a:repo[0] =~ '[/$~]'
507 echom '[vim-plug] '.a:msg
511 function! s:warn(cmd, msg)
513 execute a:cmd 'a:msg'
517 function! s:esc(path)
518 return escape(a:path, ' ')
521 function! s:escrtp(path)
522 return escape(a:path, ' ,')
525 function! s:remove_rtp()
526 for name in s:loaded_names()
527 let rtp = s:rtp(g:plugs[name])
528 execute 'set rtp-='.s:escrtp(rtp)
529 let after = globpath(rtp, 'after')
530 if isdirectory(after)
531 execute 'set rtp-='.s:escrtp(after)
536 function! s:reorg_rtp()
537 if !empty(s:first_rtp)
538 execute 'set rtp-='.s:first_rtp
539 execute 'set rtp-='.s:last_rtp
542 " &rtp is modified from outside
543 if exists('s:prtp') && s:prtp !=# &rtp
548 let s:middle = get(s:, 'middle', &rtp)
549 let rtps = map(s:loaded_names(), 's:rtp(g:plugs[v:val])')
550 let afters = filter(map(copy(rtps), 'globpath(v:val, "after")'), '!empty(v:val)')
551 let rtp = join(map(rtps, 'escape(v:val, ",")'), ',')
553 \ . join(map(afters, 'escape(v:val, ",")'), ',')
554 let &rtp = substitute(substitute(rtp, ',,*', ',', 'g'), '^,\|,$', '', 'g')
557 if !empty(s:first_rtp)
558 execute 'set rtp^='.s:first_rtp
559 execute 'set rtp+='.s:last_rtp
563 function! s:doautocmd(...)
564 if exists('#'.join(a:000, '#'))
565 execute 'doautocmd' ((v:version > 703 || has('patch442')) ? '<nomodeline>' : '') join(a:000)
569 function! s:dobufread(names)
571 let path = s:rtp(g:plugs[name])
572 for dir in ['ftdetect', 'ftplugin', 'after/ftdetect', 'after/ftplugin']
573 if len(finddir(dir, path))
574 if exists('#BufRead')
583 function! plug#load(...)
585 return s:err('Argument missing: plugin name(s) required')
587 if !exists('g:plugs')
588 return s:err('plug#begin was not called')
590 let names = a:0 == 1 && type(a:1) == s:TYPE.list ? a:1 : a:000
591 let unknowns = filter(copy(names), '!has_key(g:plugs, v:val)')
593 let s = len(unknowns) > 1 ? 's' : ''
594 return s:err(printf('Unknown plugin%s: %s', s, join(unknowns, ', ')))
596 let unloaded = filter(copy(names), '!get(s:loaded, v:val, 0)')
599 call s:lod([name], ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin'])
601 call s:dobufread(unloaded)
607 function! s:remove_triggers(name)
608 if !has_key(s:triggers, a:name)
611 for cmd in s:triggers[a:name].cmd
612 execute 'silent! delc' cmd
614 for map in s:triggers[a:name].map
615 execute 'silent! unmap' map
616 execute 'silent! iunmap' map
618 call remove(s:triggers, a:name)
621 function! s:lod(names, types, ...)
623 call s:remove_triggers(name)
624 let s:loaded[name] = 1
629 let rtp = s:rtp(g:plugs[name])
631 call s:source(rtp, dir.'/**/*.vim')
632 if has('nvim-0.5.0') " see neovim#14686
633 call s:source(rtp, dir.'/**/*.lua')
637 if !s:source(rtp, a:1) && !empty(s:glob(rtp, a:2))
638 execute 'runtime' a:1
640 call s:source(rtp, a:2)
642 call s:doautocmd('User', name)
646 function! s:lod_ft(pat, names)
647 let syn = 'syntax/'.a:pat.'.vim'
648 call s:lod(a:names, ['plugin', 'after/plugin'], syn, 'after/'.syn)
649 execute 'autocmd! PlugLOD FileType' a:pat
650 call s:doautocmd('filetypeplugin', 'FileType')
651 call s:doautocmd('filetypeindent', 'FileType')
654 function! s:lod_cmd(cmd, bang, l1, l2, args, names)
655 call s:lod(a:names, ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin'])
656 call s:dobufread(a:names)
657 execute printf('%s%s%s %s', (a:l1 == a:l2 ? '' : (a:l1.','.a:l2)), a:cmd, a:bang, a:args)
660 function! s:lod_map(map, names, with_prefix, prefix)
661 call s:lod(a:names, ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin'])
662 call s:dobufread(a:names)
669 let extra .= nr2char(c)
673 let prefix = v:count ? v:count : ''
674 let prefix .= '"'.v:register.a:prefix
677 let prefix = "\<esc>" . prefix
679 let prefix .= v:operator
681 call feedkeys(prefix, 'n')
683 call feedkeys(substitute(a:map, '^<Plug>', "\<Plug>", '') . extra)
686 function! plug#(repo, ...)
688 return s:err('Invalid number of arguments (1..2)')
692 let repo = s:trim(a:repo)
693 let opts = a:0 == 1 ? s:parse_options(a:1) : s:base_spec
694 let name = get(opts, 'as', s:plug_fnamemodify(repo, ':t:s?\.git$??'))
695 let spec = extend(s:infer_properties(name, repo), opts)
696 if !has_key(g:plugs, name)
697 call add(g:plugs_order, name)
699 let g:plugs[name] = spec
700 let s:loaded[name] = get(s:loaded, name, 0)
702 return s:err(repo . ' ' . v:exception)
706 function! s:parse_options(arg)
707 let opts = copy(s:base_spec)
708 let type = type(a:arg)
709 let opt_errfmt = 'Invalid argument for "%s" option of :Plug (expected: %s)'
710 if type == s:TYPE.string
712 throw printf(opt_errfmt, 'tag', 'string')
715 elseif type == s:TYPE.dict
716 for opt in ['branch', 'tag', 'commit', 'rtp', 'dir', 'as']
717 if has_key(a:arg, opt)
718 \ && (type(a:arg[opt]) != s:TYPE.string || empty(a:arg[opt]))
719 throw printf(opt_errfmt, opt, 'string')
722 for opt in ['on', 'for']
723 if has_key(a:arg, opt)
724 \ && type(a:arg[opt]) != s:TYPE.list
725 \ && (type(a:arg[opt]) != s:TYPE.string || empty(a:arg[opt]))
726 throw printf(opt_errfmt, opt, 'string or list')
729 if has_key(a:arg, 'do')
730 \ && type(a:arg.do) != s:TYPE.funcref
731 \ && (type(a:arg.do) != s:TYPE.string || empty(a:arg.do))
732 throw printf(opt_errfmt, 'do', 'string or funcref')
734 call extend(opts, a:arg)
735 if has_key(opts, 'dir')
736 let opts.dir = s:dirpath(s:plug_expand(opts.dir))
739 throw 'Invalid argument type (expected: string or dictionary)'
744 function! s:infer_properties(name, repo)
746 if s:is_local_plug(repo)
747 return { 'dir': s:dirpath(s:plug_expand(repo)) }
753 throw printf('Invalid argument: %s (implicit `vim-scripts'' expansion is deprecated)', repo)
755 let fmt = get(g:, 'plug_url_format', 'https://git::@github.com/%s.git')
756 let uri = printf(fmt, repo)
758 return { 'dir': s:dirpath(g:plug_home.'/'.a:name), 'uri': uri }
762 function! s:install(force, names)
763 call s:update_impl(0, a:force, a:names)
766 function! s:update(force, names)
767 call s:update_impl(1, a:force, a:names)
770 function! plug#helptags()
771 if !exists('g:plugs')
772 return s:err('plug#begin was not called')
774 for spec in values(g:plugs)
775 let docd = join([s:rtp(spec), 'doc'], '/')
777 silent! execute 'helptags' s:esc(docd)
785 syntax region plug1 start=/\%1l/ end=/\%2l/ contains=plugNumber
786 syntax region plug2 start=/\%2l/ end=/\%3l/ contains=plugBracket,plugX,plugAbort
787 syn match plugNumber /[0-9]\+[0-9.]*/ contained
788 syn match plugBracket /[[\]]/ contained
789 syn match plugX /x/ contained
790 syn match plugAbort /\~/ contained
791 syn match plugDash /^-\{1}\ /
792 syn match plugPlus /^+/
793 syn match plugStar /^*/
794 syn match plugMessage /\(^- \)\@<=.*/
795 syn match plugName /\(^- \)\@<=[^ ]*:/
796 syn match plugSha /\%(: \)\@<=[0-9a-f]\{4,}$/
797 syn match plugTag /(tag: [^)]\+)/
798 syn match plugInstall /\(^+ \)\@<=[^:]*/
799 syn match plugUpdate /\(^* \)\@<=[^:]*/
800 syn match plugCommit /^ \X*[0-9a-f]\{7,9} .*/ contains=plugRelDate,plugEdge,plugTag
801 syn match plugEdge /^ \X\+$/
802 syn match plugEdge /^ \X*/ contained nextgroup=plugSha
803 syn match plugSha /[0-9a-f]\{7,9}/ contained
804 syn match plugRelDate /([^)]*)$/ contained
805 syn match plugNotLoaded /(not loaded)$/
806 syn match plugError /^x.*/
807 syn region plugDeleted start=/^\~ .*/ end=/^\ze\S/
808 syn match plugH2 /^.*:\n-\+$/
809 syn match plugH2 /^-\{2,}/
810 syn keyword Function PlugInstall PlugStatus PlugUpdate PlugClean
811 hi def link plug1 Title
812 hi def link plug2 Repeat
813 hi def link plugH2 Type
814 hi def link plugX Exception
815 hi def link plugAbort Ignore
816 hi def link plugBracket Structure
817 hi def link plugNumber Number
819 hi def link plugDash Special
820 hi def link plugPlus Constant
821 hi def link plugStar Boolean
823 hi def link plugMessage Function
824 hi def link plugName Label
825 hi def link plugInstall Function
826 hi def link plugUpdate Type
828 hi def link plugError Error
829 hi def link plugDeleted Ignore
830 hi def link plugRelDate Comment
831 hi def link plugEdge PreProc
832 hi def link plugSha Identifier
833 hi def link plugTag Constant
835 hi def link plugNotLoaded Comment
838 function! s:lpad(str, len)
839 return a:str . repeat(' ', a:len - len(a:str))
842 function! s:lines(msg)
843 return split(a:msg, "[\r\n]")
846 function! s:lastline(msg)
847 return get(s:lines(a:msg), -1, '')
850 function! s:new_window()
851 execute get(g:, 'plug_window', '-tabnew')
854 function! s:plug_window_exists()
855 let buflist = tabpagebuflist(s:plug_tab)
856 return !empty(buflist) && index(buflist, s:plug_buf) >= 0
859 function! s:switch_in()
860 if !s:plug_window_exists()
864 if winbufnr(0) != s:plug_buf
865 let s:pos = [tabpagenr(), winnr(), winsaveview()]
866 execute 'normal!' s:plug_tab.'gt'
867 let winnr = bufwinnr(s:plug_buf)
868 execute winnr.'wincmd w'
869 call add(s:pos, winsaveview())
871 let s:pos = [winsaveview()]
878 function! s:switch_out(...)
879 call winrestview(s:pos[-1])
880 setlocal nomodifiable
886 execute 'normal!' s:pos[0].'gt'
887 execute s:pos[1] 'wincmd w'
888 call winrestview(s:pos[2])
892 function! s:finish_bindings()
893 nnoremap <silent> <buffer> R :call <SID>retry()<cr>
894 nnoremap <silent> <buffer> D :PlugDiff<cr>
895 nnoremap <silent> <buffer> S :PlugStatus<cr>
896 nnoremap <silent> <buffer> U :call <SID>status_update()<cr>
897 xnoremap <silent> <buffer> U :call <SID>status_update()<cr>
898 nnoremap <silent> <buffer> ]] :silent! call <SID>section('')<cr>
899 nnoremap <silent> <buffer> [[ :silent! call <SID>section('b')<cr>
902 function! s:prepare(...)
903 if empty(s:plug_getcwd())
904 throw 'Invalid current working directory. Cannot proceed.'
907 for evar in ['$GIT_DIR', '$GIT_WORK_TREE']
909 throw evar.' detected. Cannot proceed.'
915 if b:plug_preview == 1
923 nnoremap <silent> <buffer> q :call <SID>close_pane()<cr>
925 call s:finish_bindings()
927 let b:plug_preview = -1
928 let s:plug_tab = tabpagenr()
929 let s:plug_buf = winbufnr(0)
932 for k in ['<cr>', 'L', 'o', 'X', 'd', 'dd']
933 execute 'silent! unmap <buffer>' k
935 setlocal buftype=nofile bufhidden=wipe nobuflisted nolist noswapfile nowrap cursorline modifiable nospell
936 if exists('+colorcolumn')
937 setlocal colorcolumn=
940 if exists('g:syntax_on')
945 function! s:close_pane()
946 if b:plug_preview == 1
948 let b:plug_preview = -1
949 elseif exists('s:jobs') && !empty(s:jobs)
956 function! s:assign_name()
958 let prefix = '[Plugins]'
961 while bufexists(name)
962 let name = printf('%s (%s)', prefix, idx)
965 silent! execute 'f' fnameescape(name)
968 function! s:chsh(swap)
969 let prev = [&shell, &shellcmdflag, &shellredir]
974 if s:is_powershell(&shell)
975 let &shellredir = '2>&1 | Out-File -Encoding UTF8 %s'
976 elseif &shell =~# 'sh' || &shell =~# 'cmd\(\.exe\)\?$'
977 set shellredir=>%s\ 2>&1
983 function! s:bang(cmd, ...)
986 let [sh, shellcmdflag, shrd] = s:chsh(a:0)
987 " FIXME: Escaping is incomplete. We could use shellescape with eval,
988 " but it won't work on Windows.
989 let cmd = a:0 ? s:with_cd(a:cmd, a:1) : a:cmd
991 let [batchfile, cmd] = s:batchfile(cmd)
993 let g:_plug_bang = (s:is_win && has('gui_running') ? 'silent ' : '').'!'.escape(cmd, '#!%')
994 execute "normal! :execute g:_plug_bang\<cr>\<cr>"
997 let [&shell, &shellcmdflag, &shellredir] = [sh, shellcmdflag, shrd]
998 if s:is_win && filereadable(batchfile)
999 call delete(batchfile)
1002 return v:shell_error ? 'Exit status: ' . v:shell_error : ''
1005 function! s:regress_bar()
1006 let bar = substitute(getline(2)[1:-2], '.*\zs=', 'x', '')
1007 call s:progress_bar(2, bar, len(bar))
1010 function! s:is_updated(dir)
1011 return !empty(s:system_chomp(['git', 'log', '--pretty=format:%h', 'HEAD...HEAD@{1}'], a:dir))
1014 function! s:do(pull, force, todo)
1016 " Reset &rtp to invalidate Neovim cache of loaded Lua modules
1017 " See https://github.com/junegunn/vim-plug/pull/1157#issuecomment-1809226110
1020 for [name, spec] in items(a:todo)
1021 if !isdirectory(spec.dir)
1024 let installed = has_key(s:update.new, name)
1025 let updated = installed ? 0 :
1026 \ (a:pull && index(s:update.errors, name) < 0 && s:is_updated(spec.dir))
1027 if a:force || installed || updated
1028 execute 'cd' s:esc(spec.dir)
1029 call append(3, '- Post-update hook for '. name .' ... ')
1031 let type = type(spec.do)
1032 if type == s:TYPE.string
1033 if spec.do[0] == ':'
1034 if !get(s:loaded, name, 0)
1035 let s:loaded[name] = 1
1038 call s:load_plugin(spec)
1042 let error = v:exception
1044 if !s:plug_window_exists()
1046 throw 'Warning: vim-plug was terminated by the post-update hook of '.name
1049 let error = s:bang(spec.do)
1051 elseif type == s:TYPE.funcref
1053 call s:load_plugin(spec)
1054 let status = installed ? 'installed' : (updated ? 'updated' : 'unchanged')
1055 call spec.do({ 'name': name, 'status': status, 'force': a:force })
1057 let error = v:exception
1060 let error = 'Invalid hook type'
1063 call setline(4, empty(error) ? (getline(4) . 'OK')
1064 \ : ('x' . getline(4)[1:] . error))
1066 call add(s:update.errors, name)
1067 call s:regress_bar()
1074 function! s:hash_match(a, b)
1075 return stridx(a:a, a:b) == 0 || stridx(a:b, a:a) == 0
1078 function! s:checkout(spec)
1079 let sha = a:spec.commit
1080 let output = s:git_revision(a:spec.dir)
1082 if !empty(output) && !s:hash_match(sha, s:lines(output)[0])
1083 let credential_helper = s:git_version_requirement(2) ? '-c credential.helper= ' : ''
1084 let output = s:system(
1085 \ 'git '.credential_helper.'fetch --depth 999999 && git checkout '.plug#shellescape(sha).' --', a:spec.dir)
1086 let error = v:shell_error
1088 return [output, error]
1091 function! s:finish(pull)
1092 let new_frozen = len(filter(keys(s:update.new), 'g:plugs[v:val].frozen'))
1094 let s = new_frozen > 1 ? 's' : ''
1095 call append(3, printf('- Installed %d frozen plugin%s', new_frozen, s))
1097 call append(3, '- Finishing ... ') | 4
1099 call plug#helptags()
1101 call setline(4, getline(4) . 'Done!')
1104 if !empty(s:update.errors)
1105 call add(msgs, "Press 'R' to retry.")
1107 if a:pull && len(s:update.new) < len(filter(getline(5, '$'),
1108 \ "v:val =~ '^- ' && v:val !~# 'Already up.to.date'"))
1109 call add(msgs, "Press 'D' to see the updated changes.")
1111 echo join(msgs, ' ')
1112 call s:finish_bindings()
1116 if empty(s:update.errors)
1120 call s:update_impl(s:update.pull, s:update.force,
1121 \ extend(copy(s:update.errors), [s:update.threads]))
1124 function! s:is_managed(name)
1125 return has_key(g:plugs[a:name], 'uri')
1128 function! s:names(...)
1129 return sort(filter(keys(g:plugs), 'stridx(v:val, a:1) == 0 && s:is_managed(v:val)'))
1132 function! s:check_ruby()
1133 silent! ruby require 'thread'; VIM::command("let g:plug_ruby = '#{RUBY_VERSION}'")
1134 if !exists('g:plug_ruby')
1136 return s:warn('echom', 'Warning: Ruby interface is broken')
1138 let ruby_version = split(g:plug_ruby, '\.')
1140 return s:version_requirement(ruby_version, [1, 8, 7])
1143 function! s:update_impl(pull, force, args) abort
1144 let sync = index(a:args, '--sync') >= 0 || has('vim_starting')
1145 let args = filter(copy(a:args), 'v:val != "--sync"')
1146 let threads = (len(args) > 0 && args[-1] =~ '^[1-9][0-9]*$') ?
1147 \ remove(args, -1) : get(g:, 'plug_threads', 16)
1149 let managed = filter(deepcopy(g:plugs), 's:is_managed(v:key)')
1150 let todo = empty(args) ? filter(managed, '!v:val.frozen || !isdirectory(v:val.dir)') :
1151 \ filter(managed, 'index(args, v:key) >= 0')
1154 return s:warn('echo', 'No plugin to '. (a:pull ? 'update' : 'install'))
1157 if !s:is_win && s:git_version_requirement(2, 3)
1158 let s:git_terminal_prompt = exists('$GIT_TERMINAL_PROMPT') ? $GIT_TERMINAL_PROMPT : ''
1159 let $GIT_TERMINAL_PROMPT = 0
1160 for plug in values(todo)
1161 let plug.uri = substitute(plug.uri,
1162 \ '^https://git::@github\.com', 'https://github.com', '')
1166 if !isdirectory(g:plug_home)
1168 call mkdir(g:plug_home, 'p')
1170 return s:err(printf('Invalid plug directory: %s. '.
1171 \ 'Try to call plug#begin with a valid directory', g:plug_home))
1175 if has('nvim') && !exists('*jobwait') && threads > 1
1176 call s:warn('echom', '[vim-plug] Update Neovim for parallel installer')
1179 let use_job = s:nvim || s:vim8
1180 let python = (has('python') || has('python3')) && !use_job
1181 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()
1184 \ 'start': reltime(),
1186 \ 'todo': copy(todo),
1191 \ 'threads': (python || ruby || use_job) ? min([len(todo), threads]) : 1,
1197 call append(0, ['', ''])
1201 " Set remote name, overriding a possible user git config's clone.defaultRemoteName
1202 let s:clone_opt = ['--origin', 'origin']
1203 if get(g:, 'plug_shallow', 1)
1204 call extend(s:clone_opt, ['--depth', '1'])
1205 if s:git_version_requirement(1, 7, 10)
1206 call add(s:clone_opt, '--no-single-branch')
1210 if has('win32unix') || has('wsl')
1211 call extend(s:clone_opt, ['-c', 'core.eol=lf', '-c', 'core.autocrlf=input'])
1214 let s:submodule_opt = s:git_version_requirement(2, 8) ? ' --jobs='.threads : ''
1216 " Python version requirement (>= 2.7)
1217 if python && !has('python3') && !ruby && !use_job && s:update.threads > 1
1219 silent python import platform; print platform.python_version()
1221 let python = s:version_requirement(
1222 \ map(split(split(pyv)[0], '\.'), 'str2nr(v:val)'), [2, 6])
1225 if (python || ruby) && s:update.threads > 1
1232 call s:update_ruby()
1234 call s:update_python()
1237 let lines = getline(4, '$')
1241 let name = s:extract_name(line, '.', '')
1242 if empty(name) || !has_key(printed, name)
1243 call append('$', line)
1245 let printed[name] = 1
1246 if line[0] == 'x' && index(s:update.errors, name) < 0
1247 call add(s:update.errors, name)
1254 call s:update_finish()
1258 while use_job && sync
1267 function! s:log4(name, msg)
1268 call setline(4, printf('- %s (%s)', a:msg, a:name))
1272 function! s:update_finish()
1273 if exists('s:git_terminal_prompt')
1274 let $GIT_TERMINAL_PROMPT = s:git_terminal_prompt
1277 call append(3, '- Updating ...') | 4
1278 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))'))
1279 let [pos, _] = s:logpos(name)
1285 if has_key(spec, 'commit')
1286 call s:log4(name, 'Checking out '.spec.commit)
1287 let [out, error] = s:checkout(spec)
1288 elseif has_key(spec, 'tag')
1291 let tags = s:lines(s:system('git tag --list '.plug#shellescape(tag).' --sort -version:refname 2>&1', spec.dir))
1292 if !v:shell_error && !empty(tags)
1294 call s:log4(name, printf('Latest tag for %s -> %s', spec.tag, tag))
1298 call s:log4(name, 'Checking out '.tag)
1299 let out = s:system('git checkout -q '.plug#shellescape(tag).' -- 2>&1', spec.dir)
1300 let error = v:shell_error
1302 if !error && filereadable(spec.dir.'/.gitmodules') &&
1303 \ (s:update.force || has_key(s:update.new, name) || s:is_updated(spec.dir))
1304 call s:log4(name, 'Updating submodules. This may take a while.')
1305 let out .= s:bang('git submodule update --init --recursive'.s:submodule_opt.' 2>&1', spec.dir)
1306 let error = v:shell_error
1308 let msg = s:format_message(v:shell_error ? 'x': '-', name, out)
1310 call add(s:update.errors, name)
1311 call s:regress_bar()
1312 silent execute pos 'd _'
1313 call append(4, msg) | 4
1315 call setline(pos, msg[0])
1321 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")'))
1323 call s:warn('echom', v:exception)
1324 call s:warn('echo', '')
1327 call s:finish(s:update.pull)
1328 call setline(1, 'Updated. Elapsed time: ' . split(reltimestr(reltime(s:update.start)))[0] . ' sec.')
1329 call s:switch_out('normal! gg')
1333 function! s:mark_aborted(name, message)
1334 let attrs = { 'running': 0, 'error': 1, 'abort': 1, 'lines': [a:message] }
1335 let s:jobs[a:name] = extend(get(s:jobs, a:name, {}), attrs)
1338 function! s:job_abort(cancel)
1339 if (!s:nvim && !s:vim8) || !exists('s:jobs')
1343 for [name, j] in items(s:jobs)
1345 silent! call jobstop(j.jobid)
1347 silent! call job_stop(j.jobid)
1350 call s:rm_rf(g:plugs[name].dir)
1353 call s:mark_aborted(name, 'Aborted')
1358 for todo in values(s:update.todo)
1366 function! s:last_non_empty_line(lines)
1367 let len = len(a:lines)
1368 for idx in range(len)
1369 let line = a:lines[len-idx-1]
1377 function! s:bullet_for(job, ...)
1379 return a:job.new ? '+' : '*'
1381 if get(a:job, 'abort', 0)
1384 return a:job.error ? 'x' : get(a:000, 0, '-')
1387 function! s:job_out_cb(self, data) abort
1389 let data = remove(self.lines, -1) . a:data
1390 let lines = map(split(data, "\n", 1), 'split(v:val, "\r", 1)[-1]')
1391 call extend(self.lines, lines)
1392 " To reduce the number of buffer updates
1393 let self.tick = get(self, 'tick', -1) + 1
1394 if !self.running || self.tick % len(s:jobs) == 0
1395 let result = self.error ? join(self.lines, "\n") : s:last_non_empty_line(self.lines)
1397 call s:log(s:bullet_for(self), self.name, result)
1402 function! s:job_exit_cb(self, data) abort
1403 let a:self.running = 0
1404 let a:self.error = a:data != 0
1405 call s:reap(a:self.name)
1409 function! s:job_cb(fn, job, ch, data)
1410 if !s:plug_window_exists() " plug window closed
1411 return s:job_abort(0)
1413 call call(a:fn, [a:job, a:data])
1416 function! s:nvim_cb(job_id, data, event) dict abort
1417 return (a:event == 'stdout' || a:event == 'stderr') ?
1418 \ s:job_cb('s:job_out_cb', self, 0, join(a:data, "\n")) :
1419 \ s:job_cb('s:job_exit_cb', self, 0, a:data)
1422 function! s:spawn(name, spec, queue, opts)
1423 let job = { 'name': a:name, 'spec': a:spec, 'running': 1, 'error': 0, 'lines': [''],
1424 \ 'new': get(a:opts, 'new', 0), 'queue': copy(a:queue) }
1425 let Item = remove(job.queue, 0)
1426 let argv = type(Item) == s:TYPE.funcref ? call(Item, [a:spec]) : Item
1427 let s:jobs[a:name] = job
1430 if has_key(a:opts, 'dir')
1431 let job.cwd = a:opts.dir
1434 \ 'on_stdout': function('s:nvim_cb'),
1435 \ 'on_stderr': function('s:nvim_cb'),
1436 \ 'on_exit': function('s:nvim_cb'),
1438 let jid = s:plug_call('jobstart', argv, job)
1444 let job.lines = [jid < 0 ? argv[0].' is not executable' :
1445 \ 'Invalid arguments (or job table is full)']
1448 let cmd = join(map(copy(argv), 'plug#shellescape(v:val, {"script": 0})'))
1449 if has_key(a:opts, 'dir')
1450 let cmd = s:with_cd(cmd, a:opts.dir, 0)
1452 let argv = s:is_win ? ['cmd', '/s', '/c', '"'.cmd.'"'] : ['sh', '-c', cmd]
1453 let jid = job_start(s:is_win ? join(argv, ' ') : argv, {
1454 \ 'out_cb': function('s:job_cb', ['s:job_out_cb', job]),
1455 \ 'err_cb': function('s:job_cb', ['s:job_out_cb', job]),
1456 \ 'exit_cb': function('s:job_cb', ['s:job_exit_cb', job]),
1457 \ 'err_mode': 'raw',
1460 if job_status(jid) == 'run'
1465 let job.lines = ['Failed to start job']
1468 let job.lines = s:lines(call('s:system', has_key(a:opts, 'dir') ? [argv, a:opts.dir] : [argv]))
1469 let job.error = v:shell_error != 0
1474 function! s:reap(name)
1475 let job = remove(s:jobs, a:name)
1477 call add(s:update.errors, a:name)
1478 elseif get(job, 'new', 0)
1479 let s:update.new[a:name] = 1
1482 let more = len(get(job, 'queue', []))
1483 let result = job.error ? join(job.lines, "\n") : s:last_non_empty_line(job.lines)
1485 call s:log(s:bullet_for(job), a:name, result)
1488 if !job.error && more
1489 let job.spec.queue = job.queue
1490 let s:update.todo[a:name] = job.spec
1492 let s:update.bar .= s:bullet_for(job, '=')
1499 let total = len(s:update.all)
1500 call setline(1, (s:update.pull ? 'Updating' : 'Installing').
1501 \ ' plugins ('.len(s:update.bar).'/'.total.')')
1502 call s:progress_bar(2, s:update.bar, total)
1507 function! s:logpos(name)
1509 for i in range(4, max > 4 ? max : 4)
1510 if getline(i) =~# '^[-+x*] '.a:name.':'
1511 for j in range(i + 1, max > 5 ? max : 5)
1512 if getline(j) !~ '^ '
1522 function! s:log(bullet, name, lines)
1524 let [b, e] = s:logpos(a:name)
1526 silent execute printf('%d,%d d _', b, e)
1527 if b > winheight('.')
1533 " FIXME For some reason, nomodifiable is set after :d in vim8
1535 call append(b - 1, s:format_message(a:bullet, a:name, a:lines))
1540 function! s:update_vim()
1547 function! s:checkout_command(spec)
1548 let a:spec.branch = s:git_origin_branch(a:spec)
1549 return ['git', 'checkout', '-q', a:spec.branch, '--']
1552 function! s:merge_command(spec)
1553 let a:spec.branch = s:git_origin_branch(a:spec)
1554 return ['git', 'merge', '--ff-only', 'origin/'.a:spec.branch]
1558 let pull = s:update.pull
1559 let prog = s:progress_opt(s:nvim || s:vim8)
1560 while 1 " Without TCO, Vim stack is bound to explode
1561 if empty(s:update.todo)
1562 if empty(s:jobs) && !s:update.fin
1563 call s:update_finish()
1564 let s:update.fin = 1
1569 let name = keys(s:update.todo)[0]
1570 let spec = remove(s:update.todo, name)
1571 if get(spec, 'abort', 0)
1572 call s:mark_aborted(name, 'Skipped')
1577 let queue = get(spec, 'queue', [])
1578 let new = empty(globpath(spec.dir, '.git', 1))
1581 call s:log(new ? '+' : '*', name, pull ? 'Updating ...' : 'Installing ...')
1585 let has_tag = has_key(spec, 'tag')
1587 call s:spawn(name, spec, queue, { 'dir': spec.dir })
1589 let [error, _] = s:git_validate(spec, 0)
1592 let cmd = s:git_version_requirement(2) ? ['git', '-c', 'credential.helper=', 'fetch'] : ['git', 'fetch']
1593 if has_tag && !empty(globpath(spec.dir, '.git/shallow'))
1594 call extend(cmd, ['--depth', '99999999'])
1599 let queue = [cmd, split('git remote set-head origin -a')]
1600 if !has_tag && !has_key(spec, 'commit')
1601 call extend(queue, [function('s:checkout_command'), function('s:merge_command')])
1603 call s:spawn(name, spec, queue, { 'dir': spec.dir })
1605 let s:jobs[name] = { 'running': 0, 'lines': ['Already installed'], 'error': 0 }
1608 let s:jobs[name] = { 'running': 0, 'lines': s:lines(error), 'error': 1 }
1611 let cmd = ['git', 'clone']
1613 call extend(cmd, s:clone_opt)
1618 call s:spawn(name, spec, [extend(cmd, [spec.uri, s:trim(spec.dir)]), function('s:checkout_command'), function('s:merge_command')], { 'new': 1 })
1621 if !s:jobs[name].running
1624 if len(s:jobs) >= s:update.threads
1630 function! s:update_python()
1631 let py_exe = has('python') ? 'python' : 'python3'
1632 execute py_exe "<< EOF"
1639 import Queue as queue
1646 import threading as thr
1651 G_NVIM = vim.eval("has('nvim')") == '1'
1652 G_PULL = vim.eval('s:update.pull') == '1'
1653 G_RETRIES = int(vim.eval('get(g:, "plug_retries", 2)')) + 1
1654 G_TIMEOUT = int(vim.eval('get(g:, "plug_timeout", 60)'))
1655 G_CLONE_OPT = ' '.join(vim.eval('s:clone_opt'))
1656 G_PROGRESS = vim.eval('s:progress_opt(1)')
1657 G_LOG_PROB = 1.0 / int(vim.eval('s:update.threads'))
1658 G_STOP = thr.Event()
1659 G_IS_WIN = vim.eval('s:is_win') == '1'
1661 class PlugError(Exception):
1662 def __init__(self, msg):
1664 class CmdTimedOut(PlugError):
1666 class CmdFailed(PlugError):
1668 class InvalidURI(PlugError):
1670 class Action(object):
1671 INSTALL, UPDATE, ERROR, DONE = ['+', '*', 'x', '-']
1673 class Buffer(object):
1674 def __init__(self, lock, num_plugs, is_pull):
1676 self.event = 'Updating' if is_pull else 'Installing'
1678 self.maxy = int(vim.eval('winheight(".")'))
1679 self.num_plugs = num_plugs
1681 def __where(self, name):
1682 """ Find first line with name in current buffer. Return line num. """
1683 found, lnum = False, 0
1684 matcher = re.compile('^[-+x*] {0}:'.format(name))
1685 for line in vim.current.buffer:
1686 if matcher.search(line) is not None:
1696 curbuf = vim.current.buffer
1697 curbuf[0] = self.event + ' plugins ({0}/{1})'.format(len(self.bar), self.num_plugs)
1699 num_spaces = self.num_plugs - len(self.bar)
1700 curbuf[1] = '[{0}{1}]'.format(self.bar, num_spaces * ' ')
1703 vim.command('normal! 2G')
1704 vim.command('redraw')
1706 def write(self, action, name, lines):
1707 first, rest = lines[0], lines[1:]
1708 msg = ['{0} {1}{2}{3}'.format(action, name, ': ' if first else '', first)]
1709 msg.extend([' ' + line for line in rest])
1712 if action == Action.ERROR:
1714 vim.command("call add(s:update.errors, '{0}')".format(name))
1715 elif action == Action.DONE:
1718 curbuf = vim.current.buffer
1719 lnum = self.__where(name)
1720 if lnum != -1: # Found matching line num
1722 if lnum > self.maxy and action in set([Action.INSTALL, Action.UPDATE]):
1726 curbuf.append(msg, lnum)
1732 class Command(object):
1733 CD = 'cd /d' if G_IS_WIN else 'cd'
1735 def __init__(self, cmd, cmd_dir=None, timeout=60, cb=None, clean=None):
1738 self.cmd = '{0} {1} && {2}'.format(Command.CD, cmd_dir, self.cmd)
1739 self.timeout = timeout
1740 self.callback = cb if cb else (lambda msg: None)
1741 self.clean = clean if clean else (lambda: None)
1746 """ Returns true only if command still running. """
1747 return self.proc and self.proc.poll() is None
1749 def execute(self, ntries=3):
1750 """ Execute the command with ntries if CmdTimedOut.
1751 Returns the output of the command if no Exception.
1753 attempt, finished, limit = 0, False, self.timeout
1758 result = self.try_command()
1762 if attempt != ntries:
1764 self.timeout += limit
1768 def notify_retry(self):
1769 """ Retry required for command, notify user. """
1770 for count in range(3, 0, -1):
1772 raise KeyboardInterrupt
1773 msg = 'Timeout. Will retry in {0} second{1} ...'.format(
1774 count, 's' if count != 1 else '')
1775 self.callback([msg])
1777 self.callback(['Retrying ...'])
1779 def try_command(self):
1780 """ Execute a cmd & poll for callback. Returns list of output.
1781 Raises CmdFailed -> return code for Popen isn't 0
1782 Raises CmdTimedOut -> command exceeded timeout without new output
1787 tfile = tempfile.NamedTemporaryFile(mode='w+b')
1788 preexec_fn = not G_IS_WIN and os.setsid or None
1789 self.proc = subprocess.Popen(self.cmd, stdout=tfile,
1790 stderr=subprocess.STDOUT,
1791 stdin=subprocess.PIPE, shell=True,
1792 preexec_fn=preexec_fn)
1793 thrd = thr.Thread(target=(lambda proc: proc.wait()), args=(self.proc,))
1796 thread_not_started = True
1797 while thread_not_started:
1800 thread_not_started = False
1801 except RuntimeError:
1806 raise KeyboardInterrupt
1808 if first_line or random.random() < G_LOG_PROB:
1810 line = '' if G_IS_WIN else nonblock_read(tfile.name)
1812 self.callback([line])
1814 time_diff = time.time() - os.path.getmtime(tfile.name)
1815 if time_diff > self.timeout:
1816 raise CmdTimedOut(['Timeout!'])
1821 result = [line.decode('utf-8', 'replace').rstrip() for line in tfile]
1823 if self.proc.returncode != 0:
1824 raise CmdFailed([''] + result)
1831 def terminate(self):
1832 """ Terminate process and cleanup. """
1835 os.kill(self.proc.pid, signal.SIGINT)
1837 os.killpg(self.proc.pid, signal.SIGTERM)
1840 class Plugin(object):
1841 def __init__(self, name, args, buf_q, lock):
1846 self.tag = args.get('tag', 0)
1850 if os.path.exists(self.args['dir']):
1855 thread_vim_command("let s:update.new['{0}'] = 1".format(self.name))
1856 except PlugError as exc:
1857 self.write(Action.ERROR, self.name, exc.msg)
1858 except KeyboardInterrupt:
1860 self.write(Action.ERROR, self.name, ['Interrupted!'])
1862 # Any exception except those above print stack trace
1863 msg = 'Trace:\n{0}'.format(traceback.format_exc().rstrip())
1864 self.write(Action.ERROR, self.name, msg.split('\n'))
1868 target = self.args['dir']
1869 if target[-1] == '\\':
1870 target = target[0:-1]
1875 shutil.rmtree(target)
1880 self.write(Action.INSTALL, self.name, ['Installing ...'])
1881 callback = functools.partial(self.write, Action.INSTALL, self.name)
1882 cmd = 'git clone {0} {1} {2} {3} 2>&1'.format(
1883 '' if self.tag else G_CLONE_OPT, G_PROGRESS, self.args['uri'],
1885 com = Command(cmd, None, G_TIMEOUT, callback, clean(target))
1886 result = com.execute(G_RETRIES)
1887 self.write(Action.DONE, self.name, result[-1:])
1890 cmd = 'git rev-parse --abbrev-ref HEAD 2>&1 && git config -f .git/config remote.origin.url'
1891 command = Command(cmd, self.args['dir'], G_TIMEOUT,)
1892 result = command.execute(G_RETRIES)
1896 actual_uri = self.repo_uri()
1897 expect_uri = self.args['uri']
1898 regex = re.compile(r'^(?:\w+://)?(?:[^@/]*@)?([^:/]*(?::[0-9]*)?)[:/](.*?)(?:\.git)?/?$')
1899 ma = regex.match(actual_uri)
1900 mb = regex.match(expect_uri)
1901 if ma is None or mb is None or ma.groups() != mb.groups():
1903 'Invalid URI: {0}'.format(actual_uri),
1904 'Expected {0}'.format(expect_uri),
1905 'PlugClean required.']
1906 raise InvalidURI(msg)
1909 self.write(Action.UPDATE, self.name, ['Updating ...'])
1910 callback = functools.partial(self.write, Action.UPDATE, self.name)
1911 fetch_opt = '--depth 99999999' if self.tag and os.path.isfile(os.path.join(self.args['dir'], '.git/shallow')) else ''
1912 cmd = 'git fetch {0} {1} 2>&1'.format(fetch_opt, G_PROGRESS)
1913 com = Command(cmd, self.args['dir'], G_TIMEOUT, callback)
1914 result = com.execute(G_RETRIES)
1915 self.write(Action.DONE, self.name, result[-1:])
1917 self.write(Action.DONE, self.name, ['Already installed'])
1919 def write(self, action, name, msg):
1920 self.buf_q.put((action, name, msg))
1922 class PlugThread(thr.Thread):
1923 def __init__(self, tname, args):
1924 super(PlugThread, self).__init__()
1929 thr.current_thread().name = self.tname
1930 buf_q, work_q, lock = self.args
1933 while not G_STOP.is_set():
1934 name, args = work_q.get_nowait()
1935 plug = Plugin(name, args, buf_q, lock)
1941 class RefreshThread(thr.Thread):
1942 def __init__(self, lock):
1943 super(RefreshThread, self).__init__()
1950 thread_vim_command('noautocmd normal! a')
1954 self.running = False
1957 def thread_vim_command(cmd):
1958 vim.session.threadsafe_call(lambda: vim.command(cmd))
1960 def thread_vim_command(cmd):
1964 return '"' + name.replace('"', '\"') + '"'
1966 def nonblock_read(fname):
1967 """ Read a file with nonblock flag. Return the last line. """
1968 fread = os.open(fname, os.O_RDONLY | os.O_NONBLOCK)
1969 buf = os.read(fread, 100000).decode('utf-8', 'replace')
1972 line = buf.rstrip('\r\n')
1973 left = max(line.rfind('\r'), line.rfind('\n'))
1981 thr.current_thread().name = 'main'
1982 nthreads = int(vim.eval('s:update.threads'))
1983 plugs = vim.eval('s:update.todo')
1984 mac_gui = vim.eval('s:mac_gui') == '1'
1987 buf = Buffer(lock, len(plugs), G_PULL)
1988 buf_q, work_q = queue.Queue(), queue.Queue()
1989 for work in plugs.items():
1992 start_cnt = thr.active_count()
1993 for num in range(nthreads):
1994 tname = 'PlugT-{0:02}'.format(num)
1995 thread = PlugThread(tname, (buf_q, work_q, lock))
1998 rthread = RefreshThread(lock)
2001 while not buf_q.empty() or thr.active_count() != start_cnt:
2003 action, name, msg = buf_q.get(True, 0.25)
2004 buf.write(action, name, ['OK'] if not msg else msg)
2008 except KeyboardInterrupt:
2019 function! s:update_ruby()
2022 SEP = ["\r", "\n", nil]
2026 char = readchar rescue return
2027 if SEP.include? char.chr
2036 end unless defined?(PlugStream)
2039 %["#{arg.gsub('"', '\"')}"]
2044 if /mswin|mingw|bccwin/ =~ RUBY_PLATFORM
2045 pids.each { |pid| Process.kill 'INT', pid.to_i rescue nil }
2047 unless `which pgrep 2> /dev/null`.empty?
2049 until children.empty?
2050 children = children.map { |pid|
2051 `pgrep -P #{pid}`.lines.map { |l| l.chomp }
2056 pids.each { |pid| Process.kill 'TERM', pid.to_i rescue nil }
2060 def compare_git_uri a, b
2061 regex = %r{^(?:\w+://)?(?:[^@/]*@)?([^:/]*(?::[0-9]*)?)[:/](.*?)(?:\.git)?/?$}
2062 regex.match(a).to_a.drop(1) == regex.match(b).to_a.drop(1)
2069 iswin = VIM::evaluate('s:is_win').to_i == 1
2070 pull = VIM::evaluate('s:update.pull').to_i == 1
2071 base = VIM::evaluate('g:plug_home')
2072 all = VIM::evaluate('s:update.todo')
2073 limit = VIM::evaluate('get(g:, "plug_timeout", 60)')
2074 tries = VIM::evaluate('get(g:, "plug_retries", 2)') + 1
2075 nthr = VIM::evaluate('s:update.threads').to_i
2076 maxy = VIM::evaluate('winheight(".")').to_i
2077 vim7 = VIM::evaluate('v:version').to_i <= 703 && RUBY_PLATFORM =~ /darwin/
2078 cd = iswin ? 'cd /d' : 'cd'
2079 tot = VIM::evaluate('len(s:update.todo)') || 0
2081 skip = 'Already installed'
2083 take1 = proc { mtx.synchronize { running && all.shift } }
2086 $curbuf[1] = "#{pull ? 'Updating' : 'Installing'} plugins (#{cnt}/#{tot})"
2087 $curbuf[2] = '[' + bar.ljust(tot) + ']'
2088 VIM::command('normal! 2G')
2089 VIM::command('redraw')
2091 where = proc { |name| (1..($curbuf.length)).find { |l| $curbuf[l] =~ /^[-+x*] #{name}:/ } }
2092 log = proc { |name, result, type|
2094 ing = ![true, false].include?(type)
2095 bar += type ? '=' : 'x' unless ing
2097 when :install then '+' when :update then '*'
2098 when true, nil then '-' else
2099 VIM::command("call add(s:update.errors, '#{name}')")
2103 if type || type.nil?
2104 ["#{b} #{name}: #{result.lines.to_a.last || 'OK'}"]
2105 elsif result =~ /^Interrupted|^Timeout/
2106 ["#{b} #{name}: #{result}"]
2108 ["#{b} #{name}"] + result.lines.map { |l| " " << l }
2110 if lnum = where.call(name)
2112 lnum = 4 if ing && lnum > maxy
2114 result.each_with_index do |line, offset|
2115 $curbuf.append((lnum || 4) - 1 + offset, line.gsub(/\e\[./, '').chomp)
2120 bt = proc { |cmd, name, type, cleanup|
2128 Timeout::timeout(timeout) do
2129 tmp = VIM::evaluate('tempname()')
2130 system("(#{cmd}) > #{tmp}")
2131 data = File.read(tmp).chomp
2132 File.unlink tmp rescue nil
2135 fd = IO.popen(cmd).extend(PlugStream)
2137 log_prob = 1.0 / nthr
2138 while line = Timeout::timeout(timeout) { fd.get_line }
2140 log.call name, line.chomp, type if name && (first_line || rand < log_prob)
2145 [$? == 0, data.chomp]
2146 rescue Timeout::Error, Interrupt => e
2147 if fd && !fd.closed?
2151 cleanup.call if cleanup
2152 if e.is_a?(Timeout::Error) && tried < tries
2153 3.downto(1) do |countdown|
2154 s = countdown > 1 ? 's' : ''
2155 log.call name, "Timeout. Will retry in #{countdown} second#{s} ...", type
2158 log.call name, 'Retrying ...', type
2161 [false, e.is_a?(Interrupt) ? "Interrupted!" : "Timeout!"]
2164 main = Thread.current
2166 watcher = Thread.new {
2168 while VIM::evaluate('getchar(1)')
2172 require 'io/console' # >= Ruby 1.9
2173 nil until IO.console.getch == 3.chr
2177 threads.each { |t| t.raise Interrupt } unless vim7
2179 threads.each { |t| t.join rescue nil }
2182 refresh = Thread.new {
2185 break unless running
2186 VIM::command('noautocmd normal! a')
2190 } if VIM::evaluate('s:mac_gui') == 1
2192 clone_opt = VIM::evaluate('s:clone_opt').join(' ')
2193 progress = VIM::evaluate('s:progress_opt(1)')
2196 threads << Thread.new {
2197 while pair = take1.call
2199 dir, uri, tag = pair.last.values_at *%w[dir uri tag]
2200 exists = File.directory? dir
2203 chdir = "#{cd} #{iswin ? dir : esc(dir)}"
2204 ret, data = bt.call "#{chdir} && git rev-parse --abbrev-ref HEAD 2>&1 && git config -f .git/config remote.origin.url", nil, nil, nil
2205 current_uri = data.lines.to_a.last
2207 if data =~ /^Interrupted|^Timeout/
2210 [false, [data.chomp, "PlugClean required."].join($/)]
2212 elsif !compare_git_uri(current_uri, uri)
2213 [false, ["Invalid URI: #{current_uri}",
2215 "PlugClean required."].join($/)]
2218 log.call name, 'Updating ...', :update
2219 fetch_opt = (tag && File.exist?(File.join(dir, '.git/shallow'))) ? '--depth 99999999' : ''
2220 bt.call "#{chdir} && git fetch #{fetch_opt} #{progress} 2>&1", name, :update, nil
2226 d = esc dir.sub(%r{[\\/]+$}, '')
2227 log.call name, 'Installing ...', :install
2228 bt.call "git clone #{clone_opt unless tag} #{progress} #{uri} #{d} 2>&1", name, :install, proc {
2232 mtx.synchronize { VIM::command("let s:update.new['#{name}'] = 1") } if !exists && ok
2233 log.call name, result, ok
2238 threads.each { |t| t.join rescue nil }
2240 refresh.kill if refresh
2245 function! s:shellesc_cmd(arg, script)
2246 let escaped = substitute('"'.a:arg.'"', '[&|<>()@^!"]', '^&', 'g')
2247 return substitute(escaped, '%', (a:script ? '%' : '^') . '&', 'g')
2250 function! s:shellesc_ps1(arg)
2251 return "'".substitute(escape(a:arg, '\"'), "'", "''", 'g')."'"
2254 function! s:shellesc_sh(arg)
2255 return "'".substitute(a:arg, "'", "'\\\\''", 'g')."'"
2258 " Escape the shell argument based on the shell.
2259 " Vim and Neovim's shellescape() are insufficient.
2260 " 1. shellslash determines whether to use single/double quotes.
2261 " Double-quote escaping is fragile for cmd.exe.
2262 " 2. It does not work for powershell.
2263 " 3. It does not work for *sh shells if the command is executed
2264 " via cmd.exe (ie. cmd.exe /c sh -c command command_args)
2265 " 4. It does not support batchfile syntax.
2267 " Accepts an optional dictionary with the following keys:
2268 " - shell: same as Vim/Neovim 'shell' option.
2269 " If unset, fallback to 'cmd.exe' on Windows or 'sh'.
2270 " - script: If truthy and shell is cmd.exe, escape for batchfile syntax.
2271 function! plug#shellescape(arg, ...)
2272 if a:arg =~# '^[A-Za-z0-9_/:.-]\+$'
2275 let opts = a:0 > 0 && type(a:1) == s:TYPE.dict ? a:1 : {}
2276 let shell = get(opts, 'shell', s:is_win ? 'cmd.exe' : 'sh')
2277 let script = get(opts, 'script', 1)
2278 if shell =~# 'cmd\(\.exe\)\?$'
2279 return s:shellesc_cmd(a:arg, script)
2280 elseif s:is_powershell(shell)
2281 return s:shellesc_ps1(a:arg)
2283 return s:shellesc_sh(a:arg)
2286 function! s:glob_dir(path)
2287 return map(filter(s:glob(a:path, '**'), 'isdirectory(v:val)'), 's:dirpath(v:val)')
2290 function! s:progress_bar(line, bar, total)
2291 call setline(a:line, '[' . s:lpad(a:bar, a:total) . ']')
2294 function! s:compare_git_uri(a, b)
2295 " See `git help clone'
2296 " https:// [user@] github.com[:port] / junegunn/vim-plug [.git]
2297 " [git@] github.com[:port] : junegunn/vim-plug [.git]
2298 " file:// / junegunn/vim-plug [/]
2299 " / junegunn/vim-plug [/]
2300 let pat = '^\%(\w\+://\)\='.'\%([^@/]*@\)\='.'\([^:/]*\%(:[0-9]*\)\=\)'.'[:/]'.'\(.\{-}\)'.'\%(\.git\)\=/\?$'
2301 let ma = matchlist(a:a, pat)
2302 let mb = matchlist(a:b, pat)
2303 return ma[1:2] ==# mb[1:2]
2306 function! s:format_message(bullet, name, message)
2308 return [printf('%s %s: %s', a:bullet, a:name, s:lastline(a:message))]
2310 let lines = map(s:lines(a:message), '" ".v:val')
2311 return extend([printf('x %s:', a:name)], lines)
2315 function! s:with_cd(cmd, dir, ...)
2316 let script = a:0 > 0 ? a:1 : 1
2317 let pwsh = s:is_powershell(&shell)
2318 let cd = s:is_win && !pwsh ? 'cd /d' : 'cd'
2319 let sep = pwsh ? ';' : '&&'
2320 return printf('%s %s %s %s', cd, plug#shellescape(a:dir, {'script': script, 'shell': &shell}), sep, a:cmd)
2323 function! s:system(cmd, ...)
2326 let [sh, shellcmdflag, shrd] = s:chsh(1)
2327 if type(a:cmd) == s:TYPE.list
2328 " Neovim's system() supports list argument to bypass the shell
2329 " but it cannot set the working directory for the command.
2330 " Assume that the command does not rely on the shell.
2331 if has('nvim') && a:0 == 0
2332 return system(a:cmd)
2334 let cmd = join(map(copy(a:cmd), 'plug#shellescape(v:val, {"shell": &shell, "script": 0})'))
2335 if s:is_powershell(&shell)
2336 let cmd = '& ' . cmd
2342 let cmd = s:with_cd(cmd, a:1, type(a:cmd) != s:TYPE.list)
2344 if s:is_win && type(a:cmd) != s:TYPE.list
2345 let [batchfile, cmd] = s:batchfile(cmd)
2349 let [&shell, &shellcmdflag, &shellredir] = [sh, shellcmdflag, shrd]
2350 if s:is_win && filereadable(batchfile)
2351 call delete(batchfile)
2356 function! s:system_chomp(...)
2357 let ret = call('s:system', a:000)
2358 return v:shell_error ? '' : substitute(ret, '\n$', '', '')
2361 function! s:git_validate(spec, check_branch)
2363 if isdirectory(a:spec.dir)
2364 let result = [s:git_local_branch(a:spec.dir), s:git_origin_url(a:spec.dir)]
2365 let remote = result[-1]
2367 let err = join([remote, 'PlugClean required.'], "\n")
2368 elseif !s:compare_git_uri(remote, a:spec.uri)
2369 let err = join(['Invalid URI: '.remote,
2370 \ 'Expected: '.a:spec.uri,
2371 \ 'PlugClean required.'], "\n")
2372 elseif a:check_branch && has_key(a:spec, 'commit')
2373 let sha = s:git_revision(a:spec.dir)
2375 let err = join(add(result, 'PlugClean required.'), "\n")
2376 elseif !s:hash_match(sha, a:spec.commit)
2377 let err = join([printf('Invalid HEAD (expected: %s, actual: %s)',
2378 \ a:spec.commit[:6], sha[:6]),
2379 \ 'PlugUpdate required.'], "\n")
2381 elseif a:check_branch
2382 let current_branch = result[0]
2384 let origin_branch = s:git_origin_branch(a:spec)
2385 if has_key(a:spec, 'tag')
2386 let tag = s:system_chomp('git describe --exact-match --tags HEAD 2>&1', a:spec.dir)
2387 if a:spec.tag !=# tag && a:spec.tag !~ '\*'
2388 let err = printf('Invalid tag: %s (expected: %s). Try PlugUpdate.',
2389 \ (empty(tag) ? 'N/A' : tag), a:spec.tag)
2392 elseif origin_branch !=# current_branch
2393 let err = printf('Invalid branch: %s (expected: %s). Try PlugUpdate.',
2394 \ current_branch, origin_branch)
2397 let ahead_behind = split(s:lastline(s:system([
2398 \ 'git', 'rev-list', '--count', '--left-right',
2399 \ printf('HEAD...origin/%s', origin_branch)
2400 \ ], a:spec.dir)), '\t')
2401 if v:shell_error || len(ahead_behind) != 2
2402 let err = "Failed to compare with the origin. The default branch might have changed.\nPlugClean required."
2404 let [ahead, behind] = ahead_behind
2406 " Only mention PlugClean if diverged, otherwise it's likely to be
2407 " pushable (and probably not that messed up).
2409 \ "Diverged from origin/%s (%d commit(s) ahead and %d commit(s) behind!\n"
2410 \ .'Backup local changes and run PlugClean and PlugUpdate to reinstall it.', origin_branch, ahead, behind)
2412 let err = printf("Ahead of origin/%s by %d commit(s).\n"
2413 \ .'Cannot update until local changes are pushed.',
2414 \ origin_branch, ahead)
2420 let err = 'Not found'
2422 return [err, err =~# 'PlugClean']
2425 function! s:rm_rf(dir)
2426 if isdirectory(a:dir)
2427 return s:system(s:is_win
2428 \ ? 'rmdir /S /Q '.plug#shellescape(a:dir)
2429 \ : ['rm', '-rf', a:dir])
2433 function! s:clean(force)
2435 call append(0, 'Searching for invalid plugins in '.g:plug_home)
2438 " List of valid directories
2441 let [cnt, total] = [0, len(g:plugs)]
2442 for [name, spec] in items(g:plugs)
2443 if !s:is_managed(name) || get(spec, 'frozen', 0)
2444 call add(dirs, spec.dir)
2446 let [err, clean] = s:git_validate(spec, 1)
2448 let errs[spec.dir] = s:lines(err)[0]
2450 call add(dirs, spec.dir)
2454 call s:progress_bar(2, repeat('=', cnt), total)
2461 let allowed[s:dirpath(s:plug_fnamemodify(dir, ':h:h'))] = 1
2462 let allowed[dir] = 1
2463 for child in s:glob_dir(dir)
2464 let allowed[child] = 1
2469 let found = sort(s:glob_dir(g:plug_home))
2471 let f = remove(found, 0)
2472 if !has_key(allowed, f) && isdirectory(f)
2474 call append(line('$'), '- ' . f)
2476 call append(line('$'), ' ' . errs[f])
2478 let found = filter(found, 'stridx(v:val, f) != 0')
2485 call append(line('$'), 'Already clean.')
2487 let s:clean_count = 0
2488 call append(3, ['Directories to delete:', ''])
2490 if a:force || s:ask_no_interrupt('Delete all directories?')
2491 call s:delete([6, line('$')], 1)
2493 call setline(4, 'Cancelled.')
2494 nnoremap <silent> <buffer> d :set opfunc=<sid>delete_op<cr>g@
2495 nmap <silent> <buffer> dd d_
2496 xnoremap <silent> <buffer> d :<c-u>call <sid>delete_op(visualmode(), 1)<cr>
2497 echo 'Delete the lines (d{motion}) to delete the corresponding directories'
2501 setlocal nomodifiable
2504 function! s:delete_op(type, ...)
2505 call s:delete(a:0 ? [line("'<"), line("'>")] : [line("'["), line("']")], 0)
2508 function! s:delete(range, force)
2509 let [l1, l2] = a:range
2513 let line = getline(l1)
2514 if line =~ '^- ' && isdirectory(line[2:])
2517 let answer = force ? 1 : s:ask('Delete '.line[2:].'?', 1)
2518 let force = force || answer > 1
2520 let err = s:rm_rf(line[2:])
2523 call setline(l1, '~'.line[1:])
2524 let s:clean_count += 1
2527 call append(l1 - 1, s:format_message('x', line[1:], err))
2528 let l2 += len(s:lines(err))
2531 let msg = printf('Removed %d directories.', s:clean_count)
2533 let msg .= printf(' Failed to remove %d directories.', err_count)
2535 call setline(4, msg)
2536 setlocal nomodifiable
2543 function! s:upgrade()
2544 echo 'Downloading the latest version of vim-plug'
2546 let tmp = s:plug_tempname()
2547 let new = tmp . '/plug.vim'
2550 let out = s:system(['git', 'clone', '--depth', '1', s:plug_src, tmp])
2552 return s:err('Error upgrading vim-plug: '. out)
2555 if readfile(s:me) ==# readfile(new)
2556 echo 'vim-plug is already up-to-date'
2559 call rename(s:me, s:me . '.old')
2560 call rename(new, s:me)
2562 echo 'vim-plug has been upgraded'
2566 silent! call s:rm_rf(tmp)
2570 function! s:upgrade_specs()
2571 for spec in values(g:plugs)
2572 let spec.frozen = get(spec, 'frozen', 0)
2576 function! s:status()
2578 call append(0, 'Checking plugins')
2583 let [cnt, total] = [0, len(g:plugs)]
2584 for [name, spec] in items(g:plugs)
2585 let is_dir = isdirectory(spec.dir)
2586 if has_key(spec, 'uri')
2588 let [err, _] = s:git_validate(spec, 1)
2589 let [valid, msg] = [empty(err), empty(err) ? 'OK' : err]
2591 let [valid, msg] = [0, 'Not found. Try PlugInstall.']
2595 let [valid, msg] = [1, 'OK']
2597 let [valid, msg] = [0, 'Not found.']
2602 " `s:loaded` entry can be missing if PlugUpgraded
2603 if is_dir && get(s:loaded, name, -1) == 0
2605 let msg .= ' (not loaded)'
2607 call s:progress_bar(2, repeat('=', cnt), total)
2608 call append(3, s:format_message(valid ? '-' : 'x', name, msg))
2612 call setline(1, 'Finished. '.ecnt.' error(s).')
2614 setlocal nomodifiable
2616 echo "Press 'L' on each line to load plugin, or 'U' to update"
2617 nnoremap <silent> <buffer> L :call <SID>status_load(line('.'))<cr>
2618 xnoremap <silent> <buffer> L :call <SID>status_load(line('.'))<cr>
2622 function! s:extract_name(str, prefix, suffix)
2623 return matchstr(a:str, '^'.a:prefix.' \zs[^:]\+\ze:.*'.a:suffix.'$')
2626 function! s:status_load(lnum)
2627 let line = getline(a:lnum)
2628 let name = s:extract_name(line, '-', '(not loaded)')
2630 call plug#load(name)
2632 call setline(a:lnum, substitute(line, ' (not loaded)$', '', ''))
2633 setlocal nomodifiable
2637 function! s:status_update() range
2638 let lines = getline(a:firstline, a:lastline)
2639 let names = filter(map(lines, 's:extract_name(v:val, "[x-]", "")'), '!empty(v:val)')
2642 execute 'PlugUpdate' join(names)
2646 function! s:is_preview_window_open()
2654 function! s:find_name(lnum)
2655 for lnum in reverse(range(1, a:lnum))
2656 let line = getline(lnum)
2660 let name = s:extract_name(line, '-', '')
2668 function! s:preview_commit()
2669 if b:plug_preview < 0
2670 let b:plug_preview = !s:is_preview_window_open()
2673 let sha = matchstr(getline('.'), '^ \X*\zs[0-9a-f]\{7,9}')
2675 let name = matchstr(getline('.'), '^- \zs[^:]*\ze:$')
2679 let title = 'HEAD@{1}..'
2680 let command = 'git diff --no-color HEAD@{1}'
2683 let command = 'git show --no-color --pretty=medium '.sha
2684 let name = s:find_name(line('.'))
2687 if empty(name) || !has_key(g:plugs, name) || !isdirectory(g:plugs[name].dir)
2691 if !s:is_preview_window_open()
2692 execute get(g:, 'plug_pwindow', 'vertical rightbelow new')
2695 execute 'pedit' title
2698 setlocal previewwindow filetype=git buftype=nofile bufhidden=wipe nobuflisted modifiable
2701 let [sh, shellcmdflag, shrd] = s:chsh(1)
2702 let cmd = 'cd '.plug#shellescape(g:plugs[name].dir).' && '.command
2704 let [batchfile, cmd] = s:batchfile(cmd)
2706 execute 'silent %!' cmd
2708 let [&shell, &shellcmdflag, &shellredir] = [sh, shellcmdflag, shrd]
2709 if s:is_win && filereadable(batchfile)
2710 call delete(batchfile)
2713 setlocal nomodifiable
2714 nnoremap <silent> <buffer> q :q<cr>
2718 function! s:section(flags)
2719 call search('\(^[x-] \)\@<=[^:]\+:', a:flags)
2722 function! s:format_git_log(line)
2724 let tokens = split(a:line, nr2char(1))
2726 return indent.substitute(a:line, '\s*$', '', '')
2728 let [graph, sha, refs, subject, date] = tokens
2729 let tag = matchstr(refs, 'tag: [^,)]\+')
2730 let tag = empty(tag) ? ' ' : ' ('.tag.') '
2731 return printf('%s%s%s%s%s (%s)', indent, graph, sha, tag, subject, date)
2734 function! s:append_ul(lnum, text)
2735 call append(a:lnum, ['', a:text, repeat('-', len(a:text))])
2740 call append(0, ['Collecting changes ...', ''])
2743 let total = filter(copy(g:plugs), 's:is_managed(v:key) && isdirectory(v:val.dir)')
2744 call s:progress_bar(2, bar, len(total))
2745 for origin in [1, 0]
2746 let plugs = reverse(sort(items(filter(copy(total), (origin ? '' : '!').'(has_key(v:val, "commit") || has_key(v:val, "tag"))'))))
2750 call s:append_ul(2, origin ? 'Pending updates:' : 'Last update:')
2752 let branch = s:git_origin_branch(v)
2754 let range = origin ? '..origin/'.branch : 'HEAD@{1}..'
2755 let cmd = ['git', 'log', '--graph', '--color=never']
2756 if s:git_version_requirement(2, 10, 0)
2757 call add(cmd, '--no-show-signature')
2759 call extend(cmd, ['--pretty=format:%x01%h%x01%d%x01%s%x01%cr', range])
2760 if has_key(v, 'rtp')
2761 call extend(cmd, ['--', v.rtp])
2763 let diff = s:system_chomp(cmd, v.dir)
2765 let ref = has_key(v, 'tag') ? (' (tag: '.v.tag.')') : has_key(v, 'commit') ? (' '.v.commit) : ''
2766 call append(5, extend(['', '- '.k.':'.ref], map(s:lines(diff), 's:format_git_log(v:val)')))
2767 let cnts[origin] += 1
2771 call s:progress_bar(2, bar, len(total))
2776 call append(5, ['', 'N/A'])
2779 call setline(1, printf('%d plugin(s) updated.', cnts[0])
2780 \ . (cnts[1] ? printf(' %d plugin(s) have pending updates.', cnts[1]) : ''))
2782 if cnts[0] || cnts[1]
2783 nnoremap <silent> <buffer> <plug>(plug-preview) :silent! call <SID>preview_commit()<cr>
2784 if empty(maparg("\<cr>", 'n'))
2785 nmap <buffer> <cr> <plug>(plug-preview)
2787 if empty(maparg('o', 'n'))
2788 nmap <buffer> o <plug>(plug-preview)
2792 nnoremap <silent> <buffer> X :call <SID>revert()<cr>
2793 echo "Press 'X' on each block to revert the update"
2796 setlocal nomodifiable
2799 function! s:revert()
2800 if search('^Pending updates', 'bnW')
2804 let name = s:find_name(line('.'))
2805 if empty(name) || !has_key(g:plugs, name) ||
2806 \ input(printf('Revert the update of %s? (y/N) ', name)) !~? '^y'
2810 call s:system('git reset --hard HEAD@{1} && git checkout '.plug#shellescape(g:plugs[name].branch).' --', g:plugs[name].dir)
2813 setlocal nomodifiable
2817 function! s:snapshot(force, ...) abort
2820 call append(0, ['" Generated by vim-plug',
2821 \ '" '.strftime("%c"),
2822 \ '" :source this file in vim to restore the snapshot',
2823 \ '" or execute: vim -S snapshot.vim',
2824 \ '', '', 'PlugUpdate!'])
2826 let anchor = line('$') - 3
2827 let names = sort(keys(filter(copy(g:plugs),
2828 \'has_key(v:val, "uri") && isdirectory(v:val.dir)')))
2829 for name in reverse(names)
2830 let sha = has_key(g:plugs[name], 'commit') ? g:plugs[name].commit : s:git_revision(g:plugs[name].dir)
2832 call append(anchor, printf("silent! let g:plugs['%s'].commit = '%s'", name, sha))
2838 let fn = s:plug_expand(a:1)
2839 if filereadable(fn) && !(a:force || s:ask(a:1.' already exists. Overwrite?'))
2842 call writefile(getline(1, '$'), fn)
2843 echo 'Saved as '.a:1
2844 silent execute 'e' s:esc(fn)
2849 function! s:split_rtp()
2850 return split(&rtp, '\\\@<!,')
2853 let s:first_rtp = s:escrtp(get(s:split_rtp(), 0, ''))
2854 let s:last_rtp = s:escrtp(get(s:split_rtp(), -1, ''))
2856 if exists('g:plugs')
2857 let g:plugs_order = get(g:, 'plugs_order', keys(g:plugs))
2858 call s:upgrade_specs()
2859 call s:define_commands()
2862 let &cpo = s:cpo_save