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')
72 if s:is_win && &shellslash
74 let s:me = resolve(expand('<sfile>:p'))
77 let s:me = resolve(expand('<sfile>:p'))
79 let s:base_spec = { 'branch': '', 'frozen': 0 }
84 \ 'funcref': type(function('call'))
86 let s:loaded = get(s:, 'loaded', {})
87 let s:triggers = get(s:, 'triggers', {})
89 function! s:is_powershell(shell)
90 return a:shell =~# 'powershell\(\.exe\)\?$' || a:shell =~# 'pwsh\(\.exe\)\?$'
93 function! s:isabsolute(dir) abort
94 return a:dir =~# '^/' || (has('win32') && a:dir =~? '^\%(\\\|[A-Z]:\)')
97 function! s:git_dir(dir) abort
98 let gitdir = s:trim(a:dir) . '/.git'
99 if isdirectory(gitdir)
102 if !filereadable(gitdir)
105 let gitdir = matchstr(get(readfile(gitdir), 0, ''), '^gitdir: \zs.*')
106 if len(gitdir) && !s:isabsolute(gitdir)
107 let gitdir = a:dir . '/' . gitdir
109 return isdirectory(gitdir) ? gitdir : ''
112 function! s:git_origin_url(dir) abort
113 let gitdir = s:git_dir(a:dir)
114 let config = gitdir . '/config'
115 if empty(gitdir) || !filereadable(config)
118 return matchstr(join(readfile(config)), '\[remote "origin"\].\{-}url\s*=\s*\zs\S*\ze')
121 function! s:git_revision(dir) abort
122 let gitdir = s:git_dir(a:dir)
123 let head = gitdir . '/HEAD'
124 if empty(gitdir) || !filereadable(head)
128 let line = get(readfile(head), 0, '')
129 let ref = matchstr(line, '^ref: \zs.*')
134 if filereadable(gitdir . '/' . ref)
135 return get(readfile(gitdir . '/' . ref), 0, '')
138 if filereadable(gitdir . '/packed-refs')
139 for line in readfile(gitdir . '/packed-refs')
140 if line =~# ' ' . ref
141 return matchstr(line, '^[0-9a-f]*')
149 function! s:git_local_branch(dir) abort
150 let gitdir = s:git_dir(a:dir)
151 let head = gitdir . '/HEAD'
152 if empty(gitdir) || !filereadable(head)
155 let branch = matchstr(get(readfile(head), 0, ''), '^ref: refs/heads/\zs.*')
156 return len(branch) ? branch : 'HEAD'
159 function! s:git_origin_branch(spec)
160 if len(a:spec.branch)
164 " The file may not be present if this is a local repository
165 let gitdir = s:git_dir(a:spec.dir)
166 let origin_head = gitdir.'/refs/remotes/origin/HEAD'
167 if len(gitdir) && filereadable(origin_head)
168 return matchstr(get(readfile(origin_head), 0, ''),
169 \ '^ref: refs/remotes/origin/\zs.*')
172 " The command may not return the name of a branch in detached HEAD state
173 let result = s:lines(s:system('git symbolic-ref --short HEAD', a:spec.dir))
174 return s:shell_error ? '' : result[-1]
178 function! s:plug_call(fn, ...)
179 let shellslash = &shellslash
182 return call(a:fn, a:000)
184 let &shellslash = shellslash
188 function! s:plug_call(fn, ...)
189 return call(a:fn, a:000)
193 function! s:plug_getcwd()
194 return s:plug_call('getcwd')
197 function! s:plug_fnamemodify(fname, mods)
198 return s:plug_call('fnamemodify', a:fname, a:mods)
201 function! s:plug_expand(fmt)
202 return s:plug_call('expand', a:fmt, 1)
205 function! s:plug_tempname()
206 return s:plug_call('tempname')
209 function! plug#begin(...)
211 let home = s:path(s:plug_fnamemodify(s:plug_expand(a:1), ':p'))
212 elseif exists('g:plug_home')
213 let home = s:path(g:plug_home)
215 let home = stdpath('data') . '/plugged'
217 let home = s:path(split(&rtp, ',')[0]) . '/plugged'
219 return s:err('Unable to determine plug home. Try calling plug#begin() with a path argument.')
221 if s:plug_fnamemodify(home, ':t') ==# 'plugin' && s:plug_fnamemodify(home, ':h') ==# s:first_rtp
222 return s:err('Invalid plug home. '.home.' is a standard Vim runtime path and is not allowed.')
225 let g:plug_home = home
227 let g:plugs_order = []
230 call s:define_commands()
234 function! s:define_commands()
235 command! -nargs=+ -bar Plug call plug#(<args>)
236 if !executable('git')
237 return s:err('`git` executable not found. Most commands will not be available. To suppress this message, prepend `silent!` to `call plug#begin(...)`.')
241 \ && (&shell =~# 'cmd\(\.exe\)\?$' || s:is_powershell(&shell))
242 return s:err('vim-plug does not support shell, ' . &shell . ', when shellslash is set.')
245 \ && (has('win32') || has('win32unix'))
246 \ && !has('multi_byte')
247 return s:err('Vim needs +multi_byte feature on Windows to run shell commands. Enable +iconv for best results.')
249 command! -nargs=* -bar -bang -complete=customlist,s:names PlugInstall call s:install(<bang>0, [<f-args>])
250 command! -nargs=* -bar -bang -complete=customlist,s:names PlugUpdate call s:update(<bang>0, [<f-args>])
251 command! -nargs=0 -bar -bang PlugClean call s:clean(<bang>0)
252 command! -nargs=0 -bar PlugUpgrade if s:upgrade() | execute 'source' s:esc(s:me) | endif
253 command! -nargs=0 -bar PlugStatus call s:status()
254 command! -nargs=0 -bar PlugDiff call s:diff()
255 command! -nargs=? -bar -bang -complete=file PlugSnapshot call s:snapshot(<bang>0, <f-args>)
259 return type(a:v) == s:TYPE.list ? a:v : [a:v]
263 return type(a:v) == s:TYPE.string ? a:v : join(a:v, "\n") . "\n"
266 function! s:glob(from, pattern)
267 return s:lines(globpath(a:from, a:pattern))
270 function! s:source(from, ...)
273 for vim in s:glob(a:from, pattern)
274 execute 'source' s:esc(vim)
281 function! s:assoc(dict, key, val)
282 let a:dict[a:key] = add(get(a:dict, a:key, []), a:val)
285 function! s:ask(message, ...)
288 let answer = input(a:message.(a:0 ? ' (y/N/a) ' : ' (y/N) '))
292 return (a:0 && answer =~? '^a') ? 2 : (answer =~? '^y') ? 1 : 0
295 function! s:ask_no_interrupt(...)
297 return call('s:ask', a:000)
303 function! s:lazy(plug, opt)
304 return has_key(a:plug, a:opt) &&
305 \ (empty(s:to_a(a:plug[a:opt])) ||
306 \ !isdirectory(a:plug.dir) ||
307 \ len(s:glob(s:rtp(a:plug), 'plugin')) ||
308 \ len(s:glob(s:rtp(a:plug), 'after/plugin')))
312 if !exists('g:plugs')
313 return s:err('plug#end() called without calling plug#begin() first')
316 if exists('#PlugLOD')
322 let lod = { 'ft': {}, 'map': {}, 'cmd': {} }
324 if get(g:, 'did_load_filetypes', 0)
327 for name in g:plugs_order
328 if !has_key(g:plugs, name)
331 let plug = g:plugs[name]
332 if get(s:loaded, name, 0) || !s:lazy(plug, 'on') && !s:lazy(plug, 'for')
333 let s:loaded[name] = 1
337 if has_key(plug, 'on')
338 let s:triggers[name] = { 'map': [], 'cmd': [] }
339 for cmd in s:to_a(plug.on)
340 if cmd =~? '^<Plug>.\+'
341 if empty(mapcheck(cmd)) && empty(mapcheck(cmd, 'i'))
342 call s:assoc(lod.map, cmd, name)
344 call add(s:triggers[name].map, cmd)
345 elseif cmd =~# '^[A-Z]'
346 let cmd = substitute(cmd, '!*$', '', '')
347 if exists(':'.cmd) != 2
348 call s:assoc(lod.cmd, cmd, name)
350 call add(s:triggers[name].cmd, cmd)
352 call s:err('Invalid `on` option: '.cmd.
353 \ '. Should start with an uppercase letter or `<Plug>`.')
358 if has_key(plug, 'for')
359 let types = s:to_a(plug.for)
361 augroup filetypedetect
362 call s:source(s:rtp(plug), 'ftdetect/**/*.vim', 'after/ftdetect/**/*.vim')
364 call s:source(s:rtp(plug), 'ftdetect/**/*.lua', 'after/ftdetect/**/*.lua')
369 call s:assoc(lod.ft, type, name)
374 for [cmd, names] in items(lod.cmd)
376 \ has('patch-7.4.1898')
377 \ ? 'command! -nargs=* -range -bang -complete=file %s call s:lod_cmd(%s, "<bang>", <line1>, <line2>, <q-args>, <q-mods> ,%s)'
378 \ : 'command! -nargs=* -range -bang -complete=file %s call s:lod_cmd(%s, "<bang>", <line1>, <line2>, <q-args>, %s)'
379 \ , cmd, string(cmd), string(names))
382 for [map, names] in items(lod.map)
383 for [mode, map_prefix, key_prefix] in
384 \ [['i', '<C-\><C-O>', ''], ['n', '', ''], ['v', '', 'gv'], ['o', '', '']]
386 \ '%snoremap <silent> %s %s:<C-U>call <SID>lod_map(%s, %s, %s, "%s")<CR>',
387 \ mode, map, map_prefix, string(map), string(names), mode != 'i', key_prefix)
391 for [ft, names] in items(lod.ft)
393 execute printf('autocmd FileType %s call <SID>lod_ft(%s, %s)',
394 \ ft, string(ft), string(names))
399 filetype plugin indent on
400 if has('vim_starting')
401 if has('syntax') && !exists('g:syntax_on')
405 call s:reload_plugins()
409 function! s:loaded_names()
410 return filter(copy(g:plugs_order), 'get(s:loaded, v:val, 0)')
413 function! s:load_plugin(spec)
414 call s:source(s:rtp(a:spec), 'plugin/**/*.vim', 'after/plugin/**/*.vim')
416 call s:source(s:rtp(a:spec), 'plugin/**/*.lua', 'after/plugin/**/*.lua')
420 function! s:reload_plugins()
421 for name in s:loaded_names()
422 call s:load_plugin(g:plugs[name])
426 function! s:trim(str)
427 return substitute(a:str, '[\/]\+$', '', '')
430 function! s:version_requirement(val, min)
431 for idx in range(0, len(a:min) - 1)
432 let v = get(a:val, idx, 0)
433 if v < a:min[idx] | return 0
434 elseif v > a:min[idx] | return 1
440 function! s:git_version_requirement(...)
441 if !exists('s:git_version')
442 let s:git_version = map(split(split(s:system(['git', '--version']))[2], '\.'), 'str2nr(v:val)')
444 return s:version_requirement(s:git_version, a:000)
447 function! s:progress_opt(base)
448 return a:base && !s:is_win &&
449 \ s:git_version_requirement(1, 7, 1) ? '--progress' : ''
452 function! s:rtp(spec)
453 return s:path(a:spec.dir . get(a:spec, 'rtp', ''))
457 function! s:path(path)
458 return s:trim(substitute(a:path, '/', '\', 'g'))
461 function! s:dirpath(path)
462 return s:path(a:path) . '\'
465 function! s:is_local_plug(repo)
466 return a:repo =~? '^[a-z]:\|^[%~]'
470 function! s:wrap_cmds(cmds)
473 \ 'setlocal enabledelayedexpansion']
474 \ + (type(a:cmds) == type([]) ? a:cmds : [a:cmds])
477 if !exists('s:codepage')
478 let s:codepage = libcallnr('kernel32.dll', 'GetACP', 0)
480 return map(cmds, printf('iconv(v:val."\r", "%s", "cp%d")', &encoding, s:codepage))
482 return map(cmds, 'v:val."\r"')
485 function! s:batchfile(cmd)
486 let batchfile = s:plug_tempname().'.bat'
487 call writefile(s:wrap_cmds(a:cmd), batchfile)
488 let cmd = plug#shellescape(batchfile, {'shell': &shell, 'script': 0})
489 if s:is_powershell(&shell)
492 return [batchfile, cmd]
495 function! s:path(path)
496 return s:trim(a:path)
499 function! s:dirpath(path)
500 return substitute(a:path, '[/\\]*$', '/', '')
503 function! s:is_local_plug(repo)
504 return a:repo[0] =~ '[/$~]'
510 echom '[vim-plug] '.a:msg
514 function! s:warn(cmd, msg)
516 execute a:cmd 'a:msg'
520 function! s:esc(path)
521 return escape(a:path, ' ')
524 function! s:escrtp(path)
525 return escape(a:path, ' ,')
528 function! s:remove_rtp()
529 for name in s:loaded_names()
530 let rtp = s:rtp(g:plugs[name])
531 execute 'set rtp-='.s:escrtp(rtp)
532 let after = globpath(rtp, 'after')
533 if isdirectory(after)
534 execute 'set rtp-='.s:escrtp(after)
539 function! s:reorg_rtp()
540 if !empty(s:first_rtp)
541 execute 'set rtp-='.s:first_rtp
542 execute 'set rtp-='.s:last_rtp
545 " &rtp is modified from outside
546 if exists('s:prtp') && s:prtp !=# &rtp
551 let s:middle = get(s:, 'middle', &rtp)
552 let rtps = map(s:loaded_names(), 's:rtp(g:plugs[v:val])')
553 let afters = map(filter(copy(rtps), 'isdirectory(v:val."/after")'), 's:path(v:val."/after")')
554 let rtp = join(map(rtps, 'escape(v:val, ",")'), ',')
556 \ . join(map(afters, 'escape(v:val, ",")'), ',')
557 let &rtp = substitute(substitute(rtp, ',,*', ',', 'g'), '^,\|,$', '', 'g')
560 if !empty(s:first_rtp)
561 execute 'set rtp^='.s:first_rtp
562 execute 'set rtp+='.s:last_rtp
566 function! s:doautocmd(...)
567 if exists('#'.join(a:000, '#'))
568 execute 'doautocmd' ((v:version > 703 || has('patch442')) ? '<nomodeline>' : '') join(a:000)
572 function! s:dobufread(names)
573 " Nothing to detect in an unnamed empty buffer, e.g. the one on startup
574 if empty(bufname('%')) && line('$') == 1 && empty(getline(1))
578 let path = s:rtp(g:plugs[name])
579 for dir in ['ftdetect', 'ftplugin', 'after/ftdetect', 'after/ftplugin']
580 if isdirectory(path.'/'.dir)
581 if exists('#BufRead')
590 function! plug#load(...)
592 return s:err('Argument missing: plugin name(s) required')
594 if !exists('g:plugs')
595 return s:err('plug#begin was not called')
597 let names = a:0 == 1 && type(a:1) == s:TYPE.list ? a:1 : a:000
598 let unknowns = filter(copy(names), '!has_key(g:plugs, v:val)')
600 let s = len(unknowns) > 1 ? 's' : ''
601 return s:err(printf('Unknown plugin%s: %s', s, join(unknowns, ', ')))
603 let unloaded = filter(copy(names), '!get(s:loaded, v:val, 0)')
606 call s:lod([name], ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin'])
608 call s:dobufread(unloaded)
614 function! s:remove_triggers(name)
615 if !has_key(s:triggers, a:name)
618 for cmd in s:triggers[a:name].cmd
619 execute 'silent! delc' cmd
621 for map in s:triggers[a:name].map
622 execute 'silent! unmap' map
623 execute 'silent! iunmap' map
625 call remove(s:triggers, a:name)
628 function! s:lod(names, types, ...)
630 call s:remove_triggers(name)
631 let s:loaded[name] = 1
636 let rtp = s:rtp(g:plugs[name])
638 call s:source(rtp, dir.'/**/*.vim')
639 if has('nvim-0.5.0') " see neovim#14686
640 call s:source(rtp, dir.'/**/*.lua')
644 if !s:source(rtp, a:1) && !empty(s:glob(rtp, a:2))
645 execute 'runtime' a:1
647 call s:source(rtp, a:2)
649 call s:doautocmd('User', name)
653 function! s:lod_ft(pat, names)
654 let syn = 'syntax/'.a:pat.'.vim'
655 call s:lod(a:names, ['plugin', 'after/plugin'], syn, 'after/'.syn)
656 execute 'autocmd! PlugLOD FileType' a:pat
657 call s:doautocmd('filetypeplugin', 'FileType')
658 call s:doautocmd('filetypeindent', 'FileType')
661 if has('patch-7.4.1898')
662 function! s:lod_cmd(cmd, bang, l1, l2, args, mods, names)
663 call s:lod(a:names, ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin'])
664 call s:dobufread(a:names)
665 execute printf('%s %s%s%s %s', a:mods, (a:l1 == a:l2 ? '' : (a:l1.','.a:l2)), a:cmd, a:bang, a:args)
668 function! s:lod_cmd(cmd, bang, l1, l2, args, names)
669 call s:lod(a:names, ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin'])
670 call s:dobufread(a:names)
671 execute printf('%s%s%s %s', (a:l1 == a:l2 ? '' : (a:l1.','.a:l2)), a:cmd, a:bang, a:args)
675 function! s:lod_map(map, names, with_prefix, prefix)
676 call s:lod(a:names, ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin'])
677 call s:dobufread(a:names)
684 let extra .= nr2char(c)
688 let prefix = v:count ? v:count : ''
689 let prefix .= '"'.v:register.a:prefix
692 let prefix = "\<esc>" . prefix
694 let prefix .= v:operator
696 call feedkeys(prefix, 'n')
698 call feedkeys(substitute(a:map, '^<Plug>', "\<Plug>", '') . extra)
701 function! plug#(repo, ...)
703 return s:err('Invalid number of arguments (1..2)')
707 let repo = s:trim(a:repo)
708 let opts = a:0 == 1 ? s:parse_options(a:1) : s:base_spec
709 let name = get(opts, 'as', s:plug_fnamemodify(repo, ':t:s?\.git$??'))
710 let spec = extend(s:infer_properties(name, repo), opts)
711 if !has_key(g:plugs, name)
712 call add(g:plugs_order, name)
714 let g:plugs[name] = spec
715 let s:loaded[name] = get(s:loaded, name, 0)
717 return s:err(repo . ' ' . v:exception)
721 function! s:parse_options(arg)
722 let opts = copy(s:base_spec)
723 let type = type(a:arg)
724 let opt_errfmt = 'Invalid argument for "%s" option of :Plug (expected: %s)'
725 if type == s:TYPE.string
727 throw printf(opt_errfmt, 'tag', 'string')
730 elseif type == s:TYPE.dict
731 for opt in ['branch', 'tag', 'commit', 'rtp', 'dir', 'as']
732 if has_key(a:arg, opt)
733 \ && (type(a:arg[opt]) != s:TYPE.string || empty(a:arg[opt]))
734 throw printf(opt_errfmt, opt, 'string')
737 for opt in ['on', 'for']
738 if has_key(a:arg, opt)
739 \ && type(a:arg[opt]) != s:TYPE.list
740 \ && (type(a:arg[opt]) != s:TYPE.string || empty(a:arg[opt]))
741 throw printf(opt_errfmt, opt, 'string or list')
744 if has_key(a:arg, 'do')
745 \ && type(a:arg.do) != s:TYPE.funcref
746 \ && (type(a:arg.do) != s:TYPE.string || empty(a:arg.do))
747 throw printf(opt_errfmt, 'do', 'string or funcref')
749 call extend(opts, a:arg)
750 if has_key(opts, 'dir')
751 let opts.dir = s:dirpath(s:plug_expand(opts.dir))
754 throw 'Invalid argument type (expected: string or dictionary)'
759 function! s:infer_properties(name, repo)
761 if s:is_local_plug(repo)
762 return { 'dir': s:dirpath(s:plug_expand(repo)) }
768 throw printf('Invalid argument: %s (implicit `vim-scripts'' expansion is deprecated)', repo)
770 let fmt = get(g:, 'plug_url_format', 'https://git::@github.com/%s.git')
771 let uri = printf(fmt, repo)
773 return { 'dir': s:dirpath(g:plug_home.'/'.a:name), 'uri': uri }
777 function! s:install(force, names)
778 call s:update_impl(0, a:force, a:names)
781 function! s:update(force, names)
782 call s:update_impl(1, a:force, a:names)
785 function! plug#helptags()
786 if !exists('g:plugs')
787 return s:err('plug#begin was not called')
789 for spec in values(g:plugs)
790 let docd = join([s:rtp(spec), 'doc'], '/')
792 silent! execute 'helptags' s:esc(docd)
800 syntax region plug1 start=/\%1l/ end=/\%2l/ contains=plugNumber
801 syntax region plug2 start=/\%2l/ end=/\%3l/ contains=plugBracket,plugX,plugAbort
802 syn match plugNumber /[0-9]\+[0-9.]*/ contained
803 syn match plugBracket /[[\]]/ contained
804 syn match plugX /x/ contained
805 syn match plugAbort /\~/ contained
806 syn match plugDash /^-\{1}\ /
807 syn match plugPlus /^+/
808 syn match plugStar /^*/
809 syn match plugMessage /\(^- \)\@<=.*/
810 syn match plugName /\(^- \)\@<=[^ ]*:/
811 syn match plugSha /\%(: \)\@<=[0-9a-f]\{4,}$/
812 syn match plugTag /(tag: [^)]\+)/
813 syn match plugInstall /\(^+ \)\@<=[^:]*/
814 syn match plugUpdate /\(^* \)\@<=[^:]*/
815 syn match plugCommit /^ \X*[0-9a-f]\{7,9} .*/ contains=plugRelDate,plugEdge,plugTag
816 syn match plugEdge /^ \X\+$/
817 syn match plugEdge /^ \X*/ contained nextgroup=plugSha
818 syn match plugSha /[0-9a-f]\{7,9}/ contained
819 syn match plugRelDate /([^)]*)$/ contained
820 syn match plugNotLoaded /(not loaded)$/
821 syn match plugError /^x.*/
822 syn region plugDeleted start=/^\~ .*/ end=/^\ze\S/
823 syn match plugH2 /^.*:\n-\+$/
824 syn match plugH2 /^-\{2,}/
825 syn keyword Function PlugInstall PlugStatus PlugUpdate PlugClean
826 hi def link plug1 Title
827 hi def link plug2 Repeat
828 hi def link plugH2 Type
829 hi def link plugX Exception
830 hi def link plugAbort Ignore
831 hi def link plugBracket Structure
832 hi def link plugNumber Number
834 hi def link plugDash Special
835 hi def link plugPlus Constant
836 hi def link plugStar Boolean
838 hi def link plugMessage Function
839 hi def link plugName Label
840 hi def link plugInstall Function
841 hi def link plugUpdate Type
843 hi def link plugError Error
844 hi def link plugDeleted Ignore
845 hi def link plugRelDate Comment
846 hi def link plugEdge PreProc
847 hi def link plugSha Identifier
848 hi def link plugTag Constant
850 hi def link plugNotLoaded Comment
853 function! s:lpad(str, len)
854 return a:str . repeat(' ', a:len - len(a:str))
857 function! s:lines(msg)
858 return split(a:msg, "[\r\n]")
861 function! s:lastline(msg)
862 return get(s:lines(a:msg), -1, '')
865 function! s:new_window()
866 execute get(g:, 'plug_window', '-tabnew')
869 function! s:plug_window_exists()
870 let buflist = tabpagebuflist(s:plug_tab)
871 return !empty(buflist) && index(buflist, s:plug_buf) >= 0
874 function! s:switch_in()
875 if !s:plug_window_exists()
879 if winbufnr(0) != s:plug_buf
880 let s:pos = [tabpagenr(), winnr(), winsaveview()]
881 execute 'normal!' s:plug_tab.'gt'
882 let winnr = bufwinnr(s:plug_buf)
883 execute winnr.'wincmd w'
884 call add(s:pos, winsaveview())
886 let s:pos = [winsaveview()]
893 function! s:switch_out(...)
894 call winrestview(s:pos[-1])
895 setlocal nomodifiable
901 execute 'normal!' s:pos[0].'gt'
902 execute s:pos[1] 'wincmd w'
903 call winrestview(s:pos[2])
907 function! s:finish_bindings()
908 nnoremap <silent> <buffer> R :call <SID>retry()<cr>
909 nnoremap <silent> <buffer> D :PlugDiff<cr>
910 nnoremap <silent> <buffer> S :PlugStatus<cr>
911 nnoremap <silent> <buffer> U :call <SID>status_update()<cr>
912 xnoremap <silent> <buffer> U :call <SID>status_update()<cr>
913 nnoremap <silent> <buffer> ]] :silent! call <SID>section('')<cr>
914 nnoremap <silent> <buffer> [[ :silent! call <SID>section('b')<cr>
917 function! s:prepare(...)
918 if empty(s:plug_getcwd())
919 throw 'Invalid current working directory. Cannot proceed.'
922 for evar in ['$GIT_DIR', '$GIT_WORK_TREE']
924 throw evar.' detected. Cannot proceed.'
930 if b:plug_preview == 1
938 nnoremap <silent> <buffer> q :call <SID>close_pane()<cr>
940 call s:finish_bindings()
942 let b:plug_preview = -1
943 let s:plug_tab = tabpagenr()
944 let s:plug_buf = winbufnr(0)
947 for k in ['<cr>', 'L', 'o', 'X', 'd', 'dd']
948 execute 'silent! unmap <buffer>' k
950 setlocal buftype=nofile bufhidden=wipe nobuflisted nolist noswapfile nowrap cursorline modifiable nospell
951 if exists('+colorcolumn')
952 setlocal colorcolumn=
955 if exists('g:syntax_on')
960 function! s:close_pane()
961 if b:plug_preview == 1
963 let b:plug_preview = -1
964 elseif exists('s:jobs') && !empty(s:jobs)
971 function! s:assign_name()
973 let prefix = '[Plugins]'
976 while bufexists(name)
977 let name = printf('%s (%s)', prefix, idx)
980 silent! execute 'f' fnameescape(name)
983 function! s:chsh(swap)
984 let prev = [&shell, &shellcmdflag, &shellredir]
989 if s:is_powershell(&shell)
990 let &shellredir = '2>&1 | Out-File -Encoding UTF8 %s'
991 elseif &shell =~# 'sh' || &shell =~# 'cmd\(\.exe\)\?$'
992 set shellredir=>%s\ 2>&1
998 function! s:bang(cmd, ...)
1001 let [sh, shellcmdflag, shrd] = s:chsh(a:0)
1002 " FIXME: Escaping is incomplete. We could use shellescape with eval,
1003 " but it won't work on Windows.
1004 let cmd = a:0 ? s:with_cd(a:cmd, a:1, {'shell': s:is_win ? 'cmd.exe' : &shell}) : a:cmd
1006 let [batchfile, cmd] = s:batchfile(cmd)
1008 let g:_plug_bang = (s:is_win && has('gui_running') ? 'silent ' : '').'!'.escape(cmd, '#!%')
1009 execute "normal! :execute g:_plug_bang\<cr>\<cr>"
1012 let [&shell, &shellcmdflag, &shellredir] = [sh, shellcmdflag, shrd]
1013 if s:is_win && filereadable(batchfile)
1014 call delete(batchfile)
1017 return v:shell_error ? 'Exit status: ' . v:shell_error : ''
1020 function! s:regress_bar()
1021 let bar = substitute(getline(2)[1:-2], '.*\zs=', 'x', '')
1022 call s:progress_bar(2, bar, len(bar))
1025 function! s:is_updated(dir)
1026 return !empty(s:system_chomp(['git', 'log', '--pretty=format:%h', 'HEAD...HEAD@{1}'], a:dir))
1029 function! s:do(pull, force, todo)
1031 " Reset &rtp to invalidate Neovim cache of loaded Lua modules
1032 " See https://github.com/junegunn/vim-plug/pull/1157#issuecomment-1809226110
1035 for [name, spec] in items(a:todo)
1036 if !isdirectory(spec.dir)
1039 let installed = has_key(s:update.new, name)
1040 let updated = installed ? 0 :
1041 \ (a:pull && index(s:update.errors, name) < 0 && s:is_updated(spec.dir))
1042 if a:force || installed || updated
1043 execute 'cd' s:esc(spec.dir)
1044 call append(3, '- Post-update hook for '. name .' ... ')
1046 let type = type(spec.do)
1047 if type == s:TYPE.string
1048 if spec.do[0] == ':'
1049 if !get(s:loaded, name, 0)
1050 let s:loaded[name] = 1
1053 call s:load_plugin(spec)
1057 let error = v:exception
1059 if !s:plug_window_exists()
1061 throw 'Warning: vim-plug was terminated by the post-update hook of '.name
1064 let error = s:bang(spec.do)
1066 elseif type == s:TYPE.funcref
1068 call s:load_plugin(spec)
1069 let status = installed ? 'installed' : (updated ? 'updated' : 'unchanged')
1070 call spec.do({ 'name': name, 'status': status, 'force': a:force })
1072 let error = v:exception
1075 let error = 'Invalid hook type'
1078 call setline(4, empty(error) ? (getline(4) . 'OK')
1079 \ : ('x' . getline(4)[1:] . error))
1081 call add(s:update.errors, name)
1082 call s:regress_bar()
1089 function! s:hash_match(a, b)
1090 return stridx(a:a, a:b) == 0 || stridx(a:b, a:a) == 0
1093 function! s:disable_credential_helper()
1094 return s:git_version_requirement(2) && get(g:, 'plug_disable_credential_helper', 1)
1097 function! s:checkout(spec)
1098 let sha = a:spec.commit
1099 let output = s:git_revision(a:spec.dir)
1101 if !empty(output) && !s:hash_match(sha, s:lines(output)[0])
1102 let credential_helper = s:disable_credential_helper() ? '-c credential.helper= ' : ''
1103 let output = s:system(
1104 \ 'git '.credential_helper.'fetch --depth 999999 && git checkout '.plug#shellescape(sha).' --', a:spec.dir)
1105 let error = s:shell_error
1107 return [output, error]
1110 function! s:finish(pull)
1111 let new_frozen = len(filter(keys(s:update.new), 'g:plugs[v:val].frozen'))
1113 let s = new_frozen > 1 ? 's' : ''
1114 call append(3, printf('- Installed %d frozen plugin%s', new_frozen, s))
1116 call append(3, '- Finishing ... ') | 4
1118 call plug#helptags()
1120 call setline(4, getline(4) . 'Done!')
1123 if !empty(s:update.errors)
1124 call add(msgs, "Press 'R' to retry.")
1126 if a:pull && len(s:update.new) < len(filter(getline(5, '$'),
1127 \ "v:val =~ '^- ' && v:val !~# 'Already up.to.date'"))
1128 call add(msgs, "Press 'D' to see the updated changes.")
1130 echo join(msgs, ' ')
1131 call s:finish_bindings()
1135 if empty(s:update.errors)
1139 call s:update_impl(s:update.pull, s:update.force,
1140 \ extend(copy(s:update.errors), [s:update.threads]))
1143 function! s:is_managed(name)
1144 return has_key(g:plugs[a:name], 'uri')
1147 function! s:names(...)
1148 return sort(filter(keys(g:plugs), 'stridx(v:val, a:1) == 0 && s:is_managed(v:val)'))
1151 function! s:check_ruby()
1152 silent! ruby require 'thread'; VIM::command("let g:plug_ruby = '#{RUBY_VERSION}'")
1153 if !exists('g:plug_ruby')
1155 return s:warn('echom', 'Warning: Ruby interface is broken')
1157 let ruby_version = split(g:plug_ruby, '\.')
1159 return s:version_requirement(ruby_version, [1, 8, 7])
1162 function! s:update_impl(pull, force, args) abort
1163 let sync = index(a:args, '--sync') >= 0 || has('vim_starting')
1164 let args = filter(copy(a:args), 'v:val != "--sync"')
1165 let threads = (len(args) > 0 && args[-1] =~ '^[1-9][0-9]*$') ?
1166 \ remove(args, -1) : get(g:, 'plug_threads', 16)
1168 let managed = filter(deepcopy(g:plugs), 's:is_managed(v:key)')
1169 let todo = empty(args) ? filter(managed, '!v:val.frozen || !isdirectory(v:val.dir)') :
1170 \ filter(managed, 'index(args, v:key) >= 0')
1173 return s:warn('echo', 'No plugin to '. (a:pull ? 'update' : 'install'))
1176 if !s:is_win && s:git_version_requirement(2, 3)
1177 let s:git_terminal_prompt = exists('$GIT_TERMINAL_PROMPT') ? $GIT_TERMINAL_PROMPT : ''
1178 let $GIT_TERMINAL_PROMPT = 0
1179 for plug in values(todo)
1180 let plug.uri = substitute(plug.uri,
1181 \ '^https://git::@github\.com', 'https://github.com', '')
1185 if !isdirectory(g:plug_home)
1187 call mkdir(g:plug_home, 'p')
1189 return s:err(printf('Invalid plug directory: %s. '.
1190 \ 'Try to call plug#begin with a valid directory', g:plug_home))
1194 if has('nvim') && !exists('*jobwait') && threads > 1
1195 call s:warn('echom', '[vim-plug] Update Neovim for parallel installer')
1198 let use_job = s:nvim || s:vim8
1199 let python = !use_job && (has('python3') || has('python'))
1200 let ruby = !use_job && (v:version >= 703 || v:version == 702 && has('patch374')) && !(s:is_win && has('gui_running')) && threads > 1 && has('ruby') && s:check_ruby()
1203 \ 'start': reltime(),
1205 \ 'todo': copy(todo),
1210 \ 'threads': (python || ruby || use_job) ? min([len(todo), threads]) : 1,
1216 call append(0, ['', ''])
1220 " Set remote name, overriding a possible user git config's clone.defaultRemoteName
1221 let s:clone_opt = ['--origin', 'origin']
1222 if get(g:, 'plug_shallow', 1)
1223 call extend(s:clone_opt, ['--depth', '1'])
1224 if s:git_version_requirement(1, 7, 10)
1225 call add(s:clone_opt, '--no-single-branch')
1229 if has('win32unix') || has('wsl')
1230 call extend(s:clone_opt, ['-c', 'core.eol=lf', '-c', 'core.autocrlf=input'])
1233 let s:submodule_opt = s:git_version_requirement(2, 8) ? ' --jobs='.threads : ''
1235 " Python version requirement (>= 2.7)
1236 if python && !ruby && s:update.threads > 1 && !has('python3')
1238 silent python import platform; print platform.python_version()
1240 let python = s:version_requirement(
1241 \ map(split(split(pyv)[0], '\.'), 'str2nr(v:val)'), [2, 6])
1244 if (python || ruby) && s:update.threads > 1
1251 call s:update_ruby()
1253 call s:update_python()
1256 let lines = getline(4, '$')
1260 let name = s:extract_name(line, '.', '')
1261 if empty(name) || !has_key(printed, name)
1262 call append('$', line)
1264 let printed[name] = 1
1265 if line[0] == 'x' && index(s:update.errors, name) < 0
1266 call add(s:update.errors, name)
1273 call s:update_finish()
1277 while use_job && sync
1286 function! s:log4(name, msg)
1287 call setline(4, printf('- %s (%s)', a:msg, a:name))
1291 function! s:update_finish()
1292 if exists('s:git_terminal_prompt')
1293 let $GIT_TERMINAL_PROMPT = s:git_terminal_prompt
1296 call append(3, '- Updating ...') | 4
1297 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))'))
1298 let [pos, _] = s:logpos(name)
1304 if has_key(spec, 'commit')
1305 call s:log4(name, 'Checking out '.spec.commit)
1306 let [out, error] = s:checkout(spec)
1307 elseif has_key(spec, 'tag')
1310 let tags = s:lines(s:system('git tag --list '.plug#shellescape(tag).' --sort -version:refname 2>&1', spec.dir))
1311 if !s:shell_error && !empty(tags)
1313 call s:log4(name, printf('Latest tag for %s -> %s', spec.tag, tag))
1317 call s:log4(name, 'Checking out '.tag)
1318 let out = s:system('git checkout -q '.plug#shellescape(tag).' -- 2>&1', spec.dir)
1319 let error = s:shell_error
1321 if !error && filereadable(spec.dir.'/.gitmodules') &&
1322 \ (s:update.force || has_key(s:update.new, name) || s:is_updated(spec.dir))
1323 call s:log4(name, 'Updating submodules. This may take a while.')
1324 let out .= s:bang('git submodule update --init --recursive'.s:submodule_opt.' 2>&1', spec.dir)
1325 let error = v:shell_error
1327 let msg = s:format_message(error ? 'x': '-', name, out)
1329 call add(s:update.errors, name)
1330 call s:regress_bar()
1331 silent execute pos 'd _'
1332 call append(4, msg) | 4
1334 call setline(pos, msg[0])
1340 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")'))
1342 call s:warn('echom', v:exception)
1343 call s:warn('echo', '')
1346 call s:finish(s:update.pull)
1347 call setline(1, 'Updated. Elapsed time: ' . split(reltimestr(reltime(s:update.start)))[0] . ' sec.')
1348 call s:switch_out('normal! gg')
1352 function! s:mark_aborted(name, message)
1353 let attrs = { 'running': 0, 'error': 1, 'abort': 1, 'lines': [a:message] }
1354 let s:jobs[a:name] = extend(get(s:jobs, a:name, {}), attrs)
1357 function! s:job_abort(cancel)
1358 if (!s:nvim && !s:vim8) || !exists('s:jobs')
1362 for [name, j] in items(s:jobs)
1364 silent! call jobstop(j.jobid)
1366 silent! call job_stop(j.jobid)
1369 call s:rm_rf(g:plugs[name].dir)
1372 call s:mark_aborted(name, 'Aborted')
1377 for todo in values(s:update.todo)
1385 function! s:last_non_empty_line(lines)
1386 let len = len(a:lines)
1387 for idx in range(len)
1388 let line = a:lines[len-idx-1]
1396 function! s:bullet_for(job, ...)
1398 return a:job.new ? '+' : '*'
1400 if get(a:job, 'abort', 0)
1403 return a:job.error ? 'x' : get(a:000, 0, '-')
1406 function! s:job_out_cb(self, data) abort
1408 let data = remove(self.lines, -1) . a:data
1409 let lines = map(split(data, "\n", 1), 'split(v:val, "\r", 1)[-1]')
1410 call extend(self.lines, lines)
1411 " To reduce the number of buffer updates
1412 let self.tick = get(self, 'tick', -1) + 1
1413 if !self.running || self.tick % len(s:jobs) == 0
1414 let result = self.error ? join(self.lines, "\n") : s:last_non_empty_line(self.lines)
1416 call s:log(s:bullet_for(self), self.name, result)
1421 function! s:job_exit_cb(self, data) abort
1422 let a:self.running = 0
1423 let a:self.error = a:data != 0
1424 call s:reap(a:self.name)
1428 function! s:job_cb(fn, job, ch, data)
1429 if !s:plug_window_exists() " plug window closed
1430 return s:job_abort(0)
1432 call call(a:fn, [a:job, a:data])
1435 function! s:nvim_cb(job_id, data, event) dict abort
1436 return (a:event == 'stdout' || a:event == 'stderr') ?
1437 \ s:job_cb('s:job_out_cb', self, 0, join(a:data, "\n")) :
1438 \ s:job_cb('s:job_exit_cb', self, 0, a:data)
1441 function! s:spawn(name, spec, queue, opts)
1442 let job = { 'name': a:name, 'spec': a:spec, 'running': 1, 'error': 0, 'lines': [''],
1443 \ 'new': get(a:opts, 'new', 0), 'queue': copy(a:queue) }
1444 let Item = remove(job.queue, 0)
1445 let argv = type(Item) == s:TYPE.funcref ? call(Item, [a:spec]) : Item
1446 let s:jobs[a:name] = job
1449 if has_key(a:opts, 'dir')
1450 let job.cwd = a:opts.dir
1453 \ 'on_stdout': function('s:nvim_cb'),
1454 \ 'on_stderr': function('s:nvim_cb'),
1455 \ 'on_exit': function('s:nvim_cb'),
1457 let jid = s:plug_call('jobstart', argv, job)
1463 let job.lines = [jid < 0 ? argv[0].' is not executable' :
1464 \ 'Invalid arguments (or job table is full)']
1467 let cmd = join(map(copy(argv), 'plug#shellescape(v:val, {"script": 0})'))
1468 if has_key(a:opts, 'dir')
1469 let cmd = s:with_cd(cmd, a:opts.dir, {'shell': s:is_win ? 'cmd.exe' : 'sh', 'script': 0})
1471 let argv = s:is_win ? ['cmd', '/s', '/c', '"'.cmd.'"'] : ['sh', '-c', cmd]
1472 let jid = job_start(s:is_win ? join(argv, ' ') : argv, {
1473 \ 'out_cb': function('s:job_cb', ['s:job_out_cb', job]),
1474 \ 'err_cb': function('s:job_cb', ['s:job_out_cb', job]),
1475 \ 'exit_cb': function('s:job_cb', ['s:job_exit_cb', job]),
1476 \ 'err_mode': 'raw',
1479 if job_status(jid) == 'run'
1484 let job.lines = ['Failed to start job']
1487 let job.lines = s:lines(call('s:system', has_key(a:opts, 'dir') ? [argv, a:opts.dir] : [argv]))
1488 let job.error = s:shell_error != 0
1493 function! s:reap(name)
1494 let job = remove(s:jobs, a:name)
1496 call add(s:update.errors, a:name)
1497 elseif get(job, 'new', 0)
1498 let s:update.new[a:name] = 1
1501 let more = len(get(job, 'queue', []))
1502 let result = job.error ? join(job.lines, "\n") : s:last_non_empty_line(job.lines)
1504 call s:log(s:bullet_for(job), a:name, result)
1507 if !job.error && more
1508 let job.spec.queue = job.queue
1509 let s:update.todo[a:name] = job.spec
1511 let s:update.bar .= s:bullet_for(job, '=')
1518 let total = len(s:update.all)
1519 call setline(1, (s:update.pull ? 'Updating' : 'Installing').
1520 \ ' plugins ('.len(s:update.bar).'/'.total.')')
1521 call s:progress_bar(2, s:update.bar, total)
1526 function! s:logpos(name)
1528 for i in range(4, max > 4 ? max : 4)
1529 if getline(i) =~# '^[-+x*] '.a:name.':'
1530 for j in range(i + 1, max > 5 ? max : 5)
1531 if getline(j) !~ '^ '
1541 function! s:log(bullet, name, lines)
1543 let [b, e] = s:logpos(a:name)
1545 silent execute printf('%d,%d d _', b, e)
1546 if b > winheight('.')
1552 " FIXME For some reason, nomodifiable is set after :d in vim8
1554 call append(b - 1, s:format_message(a:bullet, a:name, a:lines))
1559 function! s:update_vim()
1566 function! s:checkout_command(spec)
1567 let a:spec.branch = s:git_origin_branch(a:spec)
1568 return ['git', 'checkout', '-q', a:spec.branch, '--']
1571 function! s:merge_command(spec)
1572 let a:spec.branch = s:git_origin_branch(a:spec)
1573 return ['git', 'merge', '--ff-only', 'origin/'.a:spec.branch]
1577 let pull = s:update.pull
1578 let prog = s:progress_opt(s:nvim || s:vim8)
1579 while 1 " Without TCO, Vim stack is bound to explode
1580 if empty(s:update.todo)
1581 if empty(s:jobs) && !s:update.fin
1582 call s:update_finish()
1583 let s:update.fin = 1
1588 let name = keys(s:update.todo)[0]
1589 let spec = remove(s:update.todo, name)
1590 if get(spec, 'abort', 0)
1591 call s:mark_aborted(name, 'Skipped')
1596 let queue = get(spec, 'queue', [])
1597 let new = empty(globpath(spec.dir, '.git', 1))
1600 call s:log(new ? '+' : '*', name, pull ? 'Updating ...' : 'Installing ...')
1604 let has_tag = has_key(spec, 'tag')
1606 call s:spawn(name, spec, queue, { 'dir': spec.dir })
1608 let [error, _] = s:git_validate(spec, 0)
1611 let cmd = s:disable_credential_helper() ? ['git', '-c', 'credential.helper=', 'fetch'] : ['git', 'fetch']
1612 if has_tag && !empty(globpath(spec.dir, '.git/shallow'))
1613 call extend(cmd, ['--depth', '99999999'])
1618 let queue = [cmd, split('git remote set-head origin -a')]
1619 if !has_tag && !has_key(spec, 'commit')
1620 call extend(queue, [function('s:checkout_command'), function('s:merge_command')])
1622 call s:spawn(name, spec, queue, { 'dir': spec.dir })
1624 let s:jobs[name] = { 'running': 0, 'lines': ['Already installed'], 'error': 0 }
1627 let s:jobs[name] = { 'running': 0, 'lines': s:lines(error), 'error': 1 }
1630 let cmd = ['git', 'clone']
1632 call extend(cmd, s:clone_opt)
1637 call s:spawn(name, spec, [extend(cmd, [spec.uri, s:trim(spec.dir)]), function('s:checkout_command'), function('s:merge_command')], { 'new': 1 })
1640 if !s:jobs[name].running
1643 if len(s:jobs) >= s:update.threads
1649 function! s:update_python()
1650 let py_exe = has('python3') ? 'python3' : 'python'
1651 execute py_exe "<< EOF"
1658 import Queue as queue
1665 import threading as thr
1670 G_NVIM = vim.eval("has('nvim')") == '1'
1671 G_PULL = vim.eval('s:update.pull') == '1'
1672 G_RETRIES = int(vim.eval('get(g:, "plug_retries", 2)')) + 1
1673 G_TIMEOUT = int(vim.eval('get(g:, "plug_timeout", 60)'))
1674 G_CLONE_OPT = ' '.join(vim.eval('s:clone_opt'))
1675 G_PROGRESS = vim.eval('s:progress_opt(1)')
1676 G_LOG_PROB = 1.0 / int(vim.eval('s:update.threads'))
1677 G_STOP = thr.Event()
1678 G_IS_WIN = vim.eval('s:is_win') == '1'
1680 class PlugError(Exception):
1681 def __init__(self, msg):
1683 class CmdTimedOut(PlugError):
1685 class CmdFailed(PlugError):
1687 class InvalidURI(PlugError):
1689 class Action(object):
1690 INSTALL, UPDATE, ERROR, DONE = ['+', '*', 'x', '-']
1692 class Buffer(object):
1693 def __init__(self, lock, num_plugs, is_pull):
1695 self.event = 'Updating' if is_pull else 'Installing'
1697 self.maxy = int(vim.eval('winheight(".")'))
1698 self.num_plugs = num_plugs
1700 def __where(self, name):
1701 """ Find first line with name in current buffer. Return line num. """
1702 found, lnum = False, 0
1703 matcher = re.compile('^[-+x*] {0}:'.format(name))
1704 for line in vim.current.buffer:
1705 if matcher.search(line) is not None:
1715 curbuf = vim.current.buffer
1716 curbuf[0] = self.event + ' plugins ({0}/{1})'.format(len(self.bar), self.num_plugs)
1718 num_spaces = self.num_plugs - len(self.bar)
1719 curbuf[1] = '[{0}{1}]'.format(self.bar, num_spaces * ' ')
1722 vim.command('normal! 2G')
1723 vim.command('redraw')
1725 def write(self, action, name, lines):
1726 first, rest = lines[0], lines[1:]
1727 msg = ['{0} {1}{2}{3}'.format(action, name, ': ' if first else '', first)]
1728 msg.extend([' ' + line for line in rest])
1731 if action == Action.ERROR:
1733 vim.command("call add(s:update.errors, '{0}')".format(name))
1734 elif action == Action.DONE:
1737 curbuf = vim.current.buffer
1738 lnum = self.__where(name)
1739 if lnum != -1: # Found matching line num
1741 if lnum > self.maxy and action in set([Action.INSTALL, Action.UPDATE]):
1745 curbuf.append(msg, lnum)
1751 class Command(object):
1752 CD = 'cd /d' if G_IS_WIN else 'cd'
1754 def __init__(self, cmd, cmd_dir=None, timeout=60, cb=None, clean=None):
1757 self.cmd = '{0} {1} && {2}'.format(Command.CD, cmd_dir, self.cmd)
1758 self.timeout = timeout
1759 self.callback = cb if cb else (lambda msg: None)
1760 self.clean = clean if clean else (lambda: None)
1765 """ Returns true only if command still running. """
1766 return self.proc and self.proc.poll() is None
1768 def execute(self, ntries=3):
1769 """ Execute the command with ntries if CmdTimedOut.
1770 Returns the output of the command if no Exception.
1772 attempt, finished, limit = 0, False, self.timeout
1777 result = self.try_command()
1781 if attempt != ntries:
1783 self.timeout += limit
1787 def notify_retry(self):
1788 """ Retry required for command, notify user. """
1789 for count in range(3, 0, -1):
1791 raise KeyboardInterrupt
1792 msg = 'Timeout. Will retry in {0} second{1} ...'.format(
1793 count, 's' if count != 1 else '')
1794 self.callback([msg])
1796 self.callback(['Retrying ...'])
1798 def try_command(self):
1799 """ Execute a cmd & poll for callback. Returns list of output.
1800 Raises CmdFailed -> return code for Popen isn't 0
1801 Raises CmdTimedOut -> command exceeded timeout without new output
1806 tfile = tempfile.NamedTemporaryFile(mode='w+b')
1807 preexec_fn = not G_IS_WIN and os.setsid or None
1808 self.proc = subprocess.Popen(self.cmd, stdout=tfile,
1809 stderr=subprocess.STDOUT,
1810 stdin=subprocess.PIPE, shell=True,
1811 preexec_fn=preexec_fn)
1812 thrd = thr.Thread(target=(lambda proc: proc.wait()), args=(self.proc,))
1815 thread_not_started = True
1816 while thread_not_started:
1819 thread_not_started = False
1820 except RuntimeError:
1825 raise KeyboardInterrupt
1827 if first_line or random.random() < G_LOG_PROB:
1829 line = '' if G_IS_WIN else nonblock_read(tfile.name)
1831 self.callback([line])
1833 time_diff = time.time() - os.path.getmtime(tfile.name)
1834 if time_diff > self.timeout:
1835 raise CmdTimedOut(['Timeout!'])
1840 result = [line.decode('utf-8', 'replace').rstrip() for line in tfile]
1842 if self.proc.returncode != 0:
1843 raise CmdFailed([''] + result)
1850 def terminate(self):
1851 """ Terminate process and cleanup. """
1854 os.kill(self.proc.pid, signal.SIGINT)
1856 os.killpg(self.proc.pid, signal.SIGTERM)
1859 class Plugin(object):
1860 def __init__(self, name, args, buf_q, lock):
1865 self.tag = args.get('tag', 0)
1869 if os.path.exists(self.args['dir']):
1874 thread_vim_command("let s:update.new['{0}'] = 1".format(self.name))
1875 except PlugError as exc:
1876 self.write(Action.ERROR, self.name, exc.msg)
1877 except KeyboardInterrupt:
1879 self.write(Action.ERROR, self.name, ['Interrupted!'])
1881 # Any exception except those above print stack trace
1882 msg = 'Trace:\n{0}'.format(traceback.format_exc().rstrip())
1883 self.write(Action.ERROR, self.name, msg.split('\n'))
1887 target = self.args['dir']
1888 if target[-1] == '\\':
1889 target = target[0:-1]
1894 shutil.rmtree(target)
1899 self.write(Action.INSTALL, self.name, ['Installing ...'])
1900 callback = functools.partial(self.write, Action.INSTALL, self.name)
1901 cmd = 'git clone {0} {1} {2} {3} 2>&1'.format(
1902 '' if self.tag else G_CLONE_OPT, G_PROGRESS, self.args['uri'],
1904 com = Command(cmd, None, G_TIMEOUT, callback, clean(target))
1905 result = com.execute(G_RETRIES)
1906 self.write(Action.DONE, self.name, result[-1:])
1909 cmd = 'git rev-parse --abbrev-ref HEAD 2>&1 && git config -f .git/config remote.origin.url'
1910 command = Command(cmd, self.args['dir'], G_TIMEOUT,)
1911 result = command.execute(G_RETRIES)
1915 actual_uri = self.repo_uri()
1916 expect_uri = self.args['uri']
1917 regex = re.compile(r'^(?:\w+://)?(?:[^@/]*@)?([^:/]*(?::[0-9]*)?)[:/](.*?)(?:\.git)?/?$')
1918 ma = regex.match(actual_uri)
1919 mb = regex.match(expect_uri)
1920 if ma is None or mb is None or ma.groups() != mb.groups():
1922 'Invalid URI: {0}'.format(actual_uri),
1923 'Expected {0}'.format(expect_uri),
1924 'PlugClean required.']
1925 raise InvalidURI(msg)
1928 self.write(Action.UPDATE, self.name, ['Updating ...'])
1929 callback = functools.partial(self.write, Action.UPDATE, self.name)
1930 fetch_opt = '--depth 99999999' if self.tag and os.path.isfile(os.path.join(self.args['dir'], '.git/shallow')) else ''
1931 cmd = 'git fetch {0} {1} 2>&1'.format(fetch_opt, G_PROGRESS)
1932 com = Command(cmd, self.args['dir'], G_TIMEOUT, callback)
1933 result = com.execute(G_RETRIES)
1934 self.write(Action.DONE, self.name, result[-1:])
1936 self.write(Action.DONE, self.name, ['Already installed'])
1938 def write(self, action, name, msg):
1939 self.buf_q.put((action, name, msg))
1941 class PlugThread(thr.Thread):
1942 def __init__(self, tname, args):
1943 super(PlugThread, self).__init__()
1948 thr.current_thread().name = self.tname
1949 buf_q, work_q, lock = self.args
1952 while not G_STOP.is_set():
1953 name, args = work_q.get_nowait()
1954 plug = Plugin(name, args, buf_q, lock)
1960 class RefreshThread(thr.Thread):
1961 def __init__(self, lock):
1962 super(RefreshThread, self).__init__()
1969 thread_vim_command('noautocmd normal! a')
1973 self.running = False
1976 def thread_vim_command(cmd):
1977 vim.session.threadsafe_call(lambda: vim.command(cmd))
1979 def thread_vim_command(cmd):
1983 return '"' + name.replace('"', '\"') + '"'
1985 def nonblock_read(fname):
1986 """ Read a file with nonblock flag. Return the last line. """
1987 fread = os.open(fname, os.O_RDONLY | os.O_NONBLOCK)
1988 buf = os.read(fread, 100000).decode('utf-8', 'replace')
1991 line = buf.rstrip('\r\n')
1992 left = max(line.rfind('\r'), line.rfind('\n'))
2000 thr.current_thread().name = 'main'
2001 nthreads = int(vim.eval('s:update.threads'))
2002 plugs = vim.eval('s:update.todo')
2003 mac_gui = vim.eval('s:mac_gui') == '1'
2006 buf = Buffer(lock, len(plugs), G_PULL)
2007 buf_q, work_q = queue.Queue(), queue.Queue()
2008 for work in plugs.items():
2011 start_cnt = thr.active_count()
2012 for num in range(nthreads):
2013 tname = 'PlugT-{0:02}'.format(num)
2014 thread = PlugThread(tname, (buf_q, work_q, lock))
2017 rthread = RefreshThread(lock)
2020 while not buf_q.empty() or thr.active_count() != start_cnt:
2022 action, name, msg = buf_q.get(True, 0.25)
2023 buf.write(action, name, ['OK'] if not msg else msg)
2027 except KeyboardInterrupt:
2038 function! s:update_ruby()
2041 SEP = ["\r", "\n", nil]
2045 char = readchar rescue return
2046 if SEP.include? char.chr
2055 end unless defined?(PlugStream)
2058 %["#{arg.gsub('"', '\"')}"]
2063 if /mswin|mingw|bccwin/ =~ RUBY_PLATFORM
2064 pids.each { |pid| Process.kill 'INT', pid.to_i rescue nil }
2066 unless `which pgrep 2> /dev/null`.empty?
2068 until children.empty?
2069 children = children.map { |pid|
2070 `pgrep -P #{pid}`.lines.map { |l| l.chomp }
2075 pids.each { |pid| Process.kill 'TERM', pid.to_i rescue nil }
2079 def compare_git_uri a, b
2080 regex = %r{^(?:\w+://)?(?:[^@/]*@)?([^:/]*(?::[0-9]*)?)[:/](.*?)(?:\.git)?/?$}
2081 regex.match(a).to_a.drop(1) == regex.match(b).to_a.drop(1)
2088 iswin = VIM::evaluate('s:is_win').to_i == 1
2089 pull = VIM::evaluate('s:update.pull').to_i == 1
2090 base = VIM::evaluate('g:plug_home')
2091 all = VIM::evaluate('s:update.todo')
2092 limit = VIM::evaluate('get(g:, "plug_timeout", 60)')
2093 tries = VIM::evaluate('get(g:, "plug_retries", 2)') + 1
2094 nthr = VIM::evaluate('s:update.threads').to_i
2095 maxy = VIM::evaluate('winheight(".")').to_i
2096 vim7 = VIM::evaluate('v:version').to_i <= 703 && RUBY_PLATFORM =~ /darwin/
2097 cd = iswin ? 'cd /d' : 'cd'
2098 tot = VIM::evaluate('len(s:update.todo)') || 0
2100 skip = 'Already installed'
2102 take1 = proc { mtx.synchronize { running && all.shift } }
2105 $curbuf[1] = "#{pull ? 'Updating' : 'Installing'} plugins (#{cnt}/#{tot})"
2106 $curbuf[2] = '[' + bar.ljust(tot) + ']'
2107 VIM::command('normal! 2G')
2108 VIM::command('redraw')
2110 where = proc { |name| (1..($curbuf.length)).find { |l| $curbuf[l] =~ /^[-+x*] #{name}:/ } }
2111 log = proc { |name, result, type|
2113 ing = ![true, false].include?(type)
2114 bar += type ? '=' : 'x' unless ing
2116 when :install then '+' when :update then '*'
2117 when true, nil then '-' else
2118 VIM::command("call add(s:update.errors, '#{name}')")
2122 if type || type.nil?
2123 ["#{b} #{name}: #{result.lines.to_a.last || 'OK'}"]
2124 elsif result =~ /^Interrupted|^Timeout/
2125 ["#{b} #{name}: #{result}"]
2127 ["#{b} #{name}"] + result.lines.map { |l| " " << l }
2129 if lnum = where.call(name)
2131 lnum = 4 if ing && lnum > maxy
2133 result.each_with_index do |line, offset|
2134 $curbuf.append((lnum || 4) - 1 + offset, line.gsub(/\e\[./, '').chomp)
2139 bt = proc { |cmd, name, type, cleanup|
2147 Timeout::timeout(timeout) do
2148 tmp = VIM::evaluate('tempname()')
2149 system("(#{cmd}) > #{tmp}")
2150 data = File.read(tmp).chomp
2151 File.unlink tmp rescue nil
2154 fd = IO.popen(cmd).extend(PlugStream)
2156 log_prob = 1.0 / nthr
2157 while line = Timeout::timeout(timeout) { fd.get_line }
2159 log.call name, line.chomp, type if name && (first_line || rand < log_prob)
2164 [$? == 0, data.chomp]
2165 rescue Timeout::Error, Interrupt => e
2166 if fd && !fd.closed?
2170 cleanup.call if cleanup
2171 if e.is_a?(Timeout::Error) && tried < tries
2172 3.downto(1) do |countdown|
2173 s = countdown > 1 ? 's' : ''
2174 log.call name, "Timeout. Will retry in #{countdown} second#{s} ...", type
2177 log.call name, 'Retrying ...', type
2180 [false, e.is_a?(Interrupt) ? "Interrupted!" : "Timeout!"]
2183 main = Thread.current
2185 watcher = Thread.new {
2187 while VIM::evaluate('getchar(1)')
2191 require 'io/console' # >= Ruby 1.9
2192 nil until IO.console.getch == 3.chr
2196 threads.each { |t| t.raise Interrupt } unless vim7
2198 threads.each { |t| t.join rescue nil }
2201 refresh = Thread.new {
2204 break unless running
2205 VIM::command('noautocmd normal! a')
2209 } if VIM::evaluate('s:mac_gui') == 1
2211 clone_opt = VIM::evaluate('s:clone_opt').join(' ')
2212 progress = VIM::evaluate('s:progress_opt(1)')
2215 threads << Thread.new {
2216 while pair = take1.call
2218 dir, uri, tag = pair.last.values_at *%w[dir uri tag]
2219 exists = File.directory? dir
2222 chdir = "#{cd} #{iswin ? dir : esc(dir)}"
2223 ret, data = bt.call "#{chdir} && git rev-parse --abbrev-ref HEAD 2>&1 && git config -f .git/config remote.origin.url", nil, nil, nil
2224 current_uri = data.lines.to_a.last
2226 if data =~ /^Interrupted|^Timeout/
2229 [false, [data.chomp, "PlugClean required."].join($/)]
2231 elsif !compare_git_uri(current_uri, uri)
2232 [false, ["Invalid URI: #{current_uri}",
2234 "PlugClean required."].join($/)]
2237 log.call name, 'Updating ...', :update
2238 fetch_opt = (tag && File.exist?(File.join(dir, '.git/shallow'))) ? '--depth 99999999' : ''
2239 bt.call "#{chdir} && git fetch #{fetch_opt} #{progress} 2>&1", name, :update, nil
2245 d = esc dir.sub(%r{[\\/]+$}, '')
2246 log.call name, 'Installing ...', :install
2247 bt.call "git clone #{clone_opt unless tag} #{progress} #{uri} #{d} 2>&1", name, :install, proc {
2251 mtx.synchronize { VIM::command("let s:update.new['#{name}'] = 1") } if !exists && ok
2252 log.call name, result, ok
2257 threads.each { |t| t.join rescue nil }
2259 refresh.kill if refresh
2264 function! s:shellesc_cmd(arg, script)
2265 let escaped = substitute('"'.a:arg.'"', '[&|<>()@^!"]', '^&', 'g')
2266 return substitute(escaped, '%', (a:script ? '%' : '^') . '&', 'g')
2269 function! s:shellesc_ps1(arg)
2270 return "'".substitute(escape(a:arg, '\"'), "'", "''", 'g')."'"
2273 function! s:shellesc_sh(arg)
2274 return "'".substitute(a:arg, "'", "'\\\\''", 'g')."'"
2277 " Escape the shell argument based on the shell.
2278 " Vim and Neovim's shellescape() are insufficient.
2279 " 1. shellslash determines whether to use single/double quotes.
2280 " Double-quote escaping is fragile for cmd.exe.
2281 " 2. It does not work for powershell.
2282 " 3. It does not work for *sh shells if the command is executed
2283 " via cmd.exe (ie. cmd.exe /c sh -c command command_args)
2284 " 4. It does not support batchfile syntax.
2286 " Accepts an optional dictionary with the following keys:
2287 " - shell: same as Vim/Neovim 'shell' option.
2288 " If unset, fallback to 'cmd.exe' on Windows or 'sh'.
2289 " - script: If truthy and shell is cmd.exe, escape for batchfile syntax.
2290 function! plug#shellescape(arg, ...)
2291 if a:arg =~# '^[A-Za-z0-9_/:.-]\+$'
2294 let opts = a:0 > 0 && type(a:1) == s:TYPE.dict ? a:1 : {}
2295 let shell = get(opts, 'shell', s:is_win ? 'cmd.exe' : 'sh')
2296 let script = get(opts, 'script', 1)
2297 if shell =~# 'cmd\(\.exe\)\?$'
2298 return s:shellesc_cmd(a:arg, script)
2299 elseif s:is_powershell(shell)
2300 return s:shellesc_ps1(a:arg)
2302 return s:shellesc_sh(a:arg)
2305 function! s:glob_dir(path)
2306 return map(filter(s:glob(a:path, '**'), 'isdirectory(v:val)'), 's:dirpath(v:val)')
2309 function! s:progress_bar(line, bar, total)
2310 call setline(a:line, '[' . s:lpad(a:bar, a:total) . ']')
2313 function! s:compare_git_uri(a, b)
2314 " See `git help clone'
2315 " https:// [user@] github.com[:port] / junegunn/vim-plug [.git]
2316 " [git@] github.com[:port] : junegunn/vim-plug [.git]
2317 " file:// / junegunn/vim-plug [/]
2318 " / junegunn/vim-plug [/]
2319 let pat = '^\%(\w\+://\)\='.'\%([^@/]*@\)\='.'\([^:/]*\%(:[0-9]*\)\=\)'.'[:/]'.'\(.\{-}\)'.'\%(\.git\)\=/\?$'
2320 let ma = matchlist(a:a, pat)
2321 let mb = matchlist(a:b, pat)
2322 return ma[1:2] ==# mb[1:2]
2325 function! s:format_message(bullet, name, message)
2327 return [printf('%s %s: %s', a:bullet, a:name, s:lastline(a:message))]
2329 let lines = map(s:lines(a:message), '" ".v:val')
2330 return extend([printf('x %s:', a:name)], lines)
2334 function! s:with_cd(cmd, dir, ...)
2335 let opts = a:0 > 0 && type(a:1) == s:TYPE.dict ? a:1 : {}
2336 let opts.shell = get(opts, 'shell', &shell)
2337 let opts.script = get(opts, 'script', 1)
2339 let pwsh = s:is_powershell(opts.shell)
2340 let cd = s:is_win && !pwsh ? 'cd /d' : 'cd'
2341 let sep = pwsh ? ';' : '&&'
2342 let pwsh_block_required = pwsh && !has('patch-9.2.6')
2343 let start = pwsh_block_required ? '& { ' : ''
2344 let end = pwsh_block_required ? ' }' : ''
2346 return printf('%s%s %s %s %s%s', start, cd, plug#shellescape(a:dir, opts), sep, a:cmd, end)
2349 function! s:system_job(cmd) abort
2350 let tmp = tempname()
2351 let job = job_start(['/bin/sh', '-c', a:cmd], {
2356 while job_status(job) ==# 'run'
2359 let s:shell_error = job_info(job).exitval
2360 let result = filereadable(tmp) ? join(readfile(tmp, 'b'), "\n") : ''
2361 silent! call delete(tmp)
2365 function! s:system(cmd, ...)
2368 let [sh, shellcmdflag, shrd] = s:chsh(1)
2369 if type(a:cmd) == s:TYPE.list
2370 " Neovim's system() supports list argument to bypass the shell
2371 " but it cannot set the working directory for the command.
2372 " Assume that the command does not rely on the shell.
2373 if has('nvim') && a:0 == 0
2374 let ret = system(a:cmd)
2375 let s:shell_error = v:shell_error
2378 let cmd = join(map(copy(a:cmd), 'plug#shellescape(v:val, {"shell": &shell, "script": 0})'))
2379 if s:is_powershell(&shell)
2380 let cmd = '& ' . cmd
2386 let cmd = s:with_cd(cmd, a:1, {'script': type(a:cmd) != s:TYPE.list})
2388 if s:is_win && type(a:cmd) != s:TYPE.list && !s:is_powershell(&shell)
2389 let [batchfile, cmd] = s:batchfile(cmd)
2391 if s:vim8 && has('gui_running') && !s:is_win
2392 return s:system_job(cmd)
2394 let ret = system(cmd)
2395 let s:shell_error = v:shell_error
2398 let [&shell, &shellcmdflag, &shellredir] = [sh, shellcmdflag, shrd]
2399 if s:is_win && filereadable(batchfile)
2400 call delete(batchfile)
2405 function! s:system_chomp(...)
2406 let ret = call('s:system', a:000)
2407 return s:shell_error ? '' : substitute(ret, '\n$', '', '')
2410 function! s:git_validate(spec, check_branch)
2412 if isdirectory(a:spec.dir)
2413 let result = [s:git_local_branch(a:spec.dir), s:git_origin_url(a:spec.dir)]
2414 let remote = result[-1]
2416 let err = join([remote, 'PlugClean required.'], "\n")
2417 elseif !s:compare_git_uri(remote, a:spec.uri)
2418 let err = join(['Invalid URI: '.remote,
2419 \ 'Expected: '.a:spec.uri,
2420 \ 'PlugClean required.'], "\n")
2421 elseif !a:check_branch
2423 elseif has_key(a:spec, 'commit')
2424 let sha = s:git_revision(a:spec.dir)
2426 let err = join(add(result, 'PlugClean required.'), "\n")
2427 elseif !s:hash_match(sha, a:spec.commit)
2428 let err = join([printf('Invalid HEAD (expected: %s, actual: %s)',
2429 \ a:spec.commit[:6], sha[:6]),
2430 \ 'PlugUpdate required.'], "\n")
2432 elseif has_key(a:spec, 'tag')
2433 let tag = s:system_chomp('git describe --exact-match --tags HEAD 2>&1', a:spec.dir)
2434 if a:spec.tag !=# tag && a:spec.tag !~ '\*'
2435 let err = printf('Invalid tag: %s (expected: %s). Try PlugUpdate.',
2436 \ (empty(tag) ? 'N/A' : tag), a:spec.tag)
2438 elseif a:check_branch
2439 let current_branch = result[0]
2440 let origin_branch = s:git_origin_branch(a:spec)
2441 if origin_branch !=# current_branch
2442 let err = printf('Invalid branch: %s (expected: %s). Try PlugUpdate.',
2443 \ current_branch, origin_branch)
2446 let ahead_behind = split(s:lastline(s:system([
2447 \ 'git', 'rev-list', '--count', '--left-right',
2448 \ printf('HEAD...origin/%s', origin_branch)
2449 \ ], a:spec.dir)), '\t')
2450 if s:shell_error || len(ahead_behind) != 2
2451 let err = "Failed to compare with the origin. The default branch might have changed.\nPlugClean required."
2453 let [ahead, behind] = ahead_behind
2455 " Only mention PlugClean if diverged, otherwise it's likely to be
2456 " pushable (and probably not that messed up).
2458 \ "Diverged from origin/%s (%d commit(s) ahead and %d commit(s) behind!\n"
2459 \ .'Backup local changes and run PlugClean and PlugUpdate to reinstall it.', origin_branch, ahead, behind)
2461 let err = printf("Ahead of origin/%s by %d commit(s).\n"
2462 \ .'Cannot update until local changes are pushed.',
2463 \ origin_branch, ahead)
2469 let err = 'Not found'
2471 return [err, err =~# 'PlugClean']
2474 function! s:rm_rf(dir)
2475 if isdirectory(a:dir)
2476 return s:system(!s:is_win
2477 \ ? ['rm', '-rf', a:dir]
2478 \ : s:is_powershell(&shell)
2479 \ ? ['Remove-Item', '-Recurse', '-Force', a:dir]
2480 \ : 'rmdir /S /Q '.plug#shellescape(a:dir))
2484 function! s:clean(force)
2486 call append(0, 'Searching for invalid plugins in '.g:plug_home)
2489 " List of valid directories
2492 let [cnt, total] = [0, len(g:plugs)]
2493 for [name, spec] in items(g:plugs)
2494 if !s:is_managed(name) || get(spec, 'frozen', 0)
2495 call add(dirs, spec.dir)
2497 let [err, clean] = s:git_validate(spec, 1)
2499 let errs[spec.dir] = s:lines(err)[0]
2501 call add(dirs, spec.dir)
2505 call s:progress_bar(2, repeat('=', cnt), total)
2512 let allowed[s:dirpath(s:plug_fnamemodify(dir, ':h:h'))] = 1
2513 let allowed[dir] = 1
2514 for child in s:glob_dir(dir)
2515 let allowed[child] = 1
2520 let found = sort(s:glob_dir(g:plug_home))
2522 let f = remove(found, 0)
2523 if !has_key(allowed, f) && isdirectory(f)
2525 call append(line('$'), '- ' . f)
2527 call append(line('$'), ' ' . errs[f])
2529 let found = filter(found, 'stridx(v:val, f) != 0')
2536 call append(line('$'), 'Already clean.')
2538 let s:clean_count = 0
2539 call append(3, ['Directories to delete:', ''])
2541 if a:force || s:ask_no_interrupt('Delete all directories?')
2542 call s:delete([6, line('$')], 1)
2544 call setline(4, 'Cancelled.')
2545 nnoremap <silent> <buffer> d :set opfunc=<sid>delete_op<cr>g@
2546 nmap <silent> <buffer> dd d_
2547 xnoremap <silent> <buffer> d :<c-u>call <sid>delete_op(visualmode(), 1)<cr>
2548 echo 'Delete the lines (d{motion}) to delete the corresponding directories'
2552 setlocal nomodifiable
2555 function! s:delete_op(type, ...)
2556 call s:delete(a:0 ? [line("'<"), line("'>")] : [line("'["), line("']")], 0)
2559 function! s:delete(range, force)
2560 let [l1, l2] = a:range
2564 let line = getline(l1)
2565 if line =~ '^- ' && isdirectory(line[2:])
2568 let answer = force ? 1 : s:ask('Delete '.line[2:].'?', 1)
2569 let force = force || answer > 1
2571 let err = s:rm_rf(line[2:])
2574 call setline(l1, '~'.line[1:])
2575 let s:clean_count += 1
2578 call append(l1 - 1, s:format_message('x', line[1:], err))
2579 let l2 += len(s:lines(err))
2582 let msg = printf('Removed %d directories.', s:clean_count)
2584 let msg .= printf(' Failed to remove %d directories.', err_count)
2586 call setline(4, msg)
2587 setlocal nomodifiable
2594 function! s:upgrade()
2595 echo 'Downloading the latest version of vim-plug'
2597 let tmp = s:plug_tempname()
2598 let new = tmp . '/plug.vim'
2601 let out = s:system(['git', 'clone', '--depth', '1', s:plug_src, tmp])
2603 return s:err('Error upgrading vim-plug: '. out)
2606 if readfile(s:me) ==# readfile(new)
2607 echo 'vim-plug is already up-to-date'
2610 call rename(s:me, s:me . '.old')
2611 call rename(new, s:me)
2613 echo 'vim-plug has been upgraded'
2617 silent! call s:rm_rf(tmp)
2621 function! s:upgrade_specs()
2622 for spec in values(g:plugs)
2623 let spec.frozen = get(spec, 'frozen', 0)
2627 function! s:status()
2629 call append(0, 'Checking plugins')
2634 let [cnt, total] = [0, len(g:plugs)]
2635 for [name, spec] in items(g:plugs)
2636 let is_dir = isdirectory(spec.dir)
2637 if has_key(spec, 'uri')
2639 let [err, _] = s:git_validate(spec, 1)
2640 let [valid, msg] = [empty(err), empty(err) ? 'OK' : err]
2642 let [valid, msg] = [0, 'Not found. Try PlugInstall.']
2646 let [valid, msg] = [1, 'OK']
2648 let [valid, msg] = [0, 'Not found.']
2653 " `s:loaded` entry can be missing if PlugUpgraded
2654 if is_dir && get(s:loaded, name, -1) == 0
2656 let msg .= ' (not loaded)'
2658 call s:progress_bar(2, repeat('=', cnt), total)
2659 call append(3, s:format_message(valid ? '-' : 'x', name, msg))
2663 call setline(1, 'Finished. '.ecnt.' error(s).')
2665 setlocal nomodifiable
2667 echo "Press 'L' on each line to load plugin, or 'U' to update"
2668 nnoremap <silent> <buffer> L :call <SID>status_load(line('.'))<cr>
2669 xnoremap <silent> <buffer> L :call <SID>status_load(line('.'))<cr>
2673 function! s:extract_name(str, prefix, suffix)
2674 return matchstr(a:str, '^'.a:prefix.' \zs[^:]\+\ze:.*'.a:suffix.'$')
2677 function! s:status_load(lnum)
2678 let line = getline(a:lnum)
2679 let name = s:extract_name(line, '-', '(not loaded)')
2681 call plug#load(name)
2683 call setline(a:lnum, substitute(line, ' (not loaded)$', '', ''))
2684 setlocal nomodifiable
2688 function! s:status_update() range
2689 let lines = getline(a:firstline, a:lastline)
2690 let names = filter(map(lines, 's:extract_name(v:val, "[x-]", "")'), '!empty(v:val)')
2693 execute 'PlugUpdate' join(names)
2697 function! s:is_preview_window_open()
2705 function! s:find_name(lnum)
2706 for lnum in reverse(range(1, a:lnum))
2707 let line = getline(lnum)
2711 let name = s:extract_name(line, '-', '')
2719 function! s:preview_commit()
2720 if b:plug_preview < 0
2721 let b:plug_preview = !s:is_preview_window_open()
2724 let sha = matchstr(getline('.'), '^ \X*\zs[0-9a-f]\{7,9}')
2726 let name = matchstr(getline('.'), '^- \zs[^:]*\ze:$')
2730 let title = 'HEAD@{1}..'
2731 let command = 'git diff --no-color HEAD@{1}'
2734 let command = 'git show --no-color --pretty=medium '.sha
2735 let name = s:find_name(line('.'))
2738 if empty(name) || !has_key(g:plugs, name) || !isdirectory(g:plugs[name].dir)
2742 if !s:is_preview_window_open()
2743 execute get(g:, 'plug_pwindow', 'vertical rightbelow new')
2746 execute 'pedit' title
2749 setlocal previewwindow filetype=git buftype=nofile bufhidden=wipe nobuflisted modifiable
2752 let [sh, shellcmdflag, shrd] = s:chsh(1)
2753 let cmd = 'cd '.plug#shellescape(g:plugs[name].dir).' && '.command
2755 let [batchfile, cmd] = s:batchfile(cmd)
2757 execute 'silent %!' cmd
2759 let [&shell, &shellcmdflag, &shellredir] = [sh, shellcmdflag, shrd]
2760 if s:is_win && filereadable(batchfile)
2761 call delete(batchfile)
2764 setlocal nomodifiable
2765 nnoremap <silent> <buffer> q :q<cr>
2769 function! s:section(flags)
2770 call search('\(^[x-] \)\@<=[^:]\+:', a:flags)
2773 function! s:format_git_log(line)
2775 let tokens = split(a:line, nr2char(1))
2777 return indent.substitute(a:line, '\s*$', '', '')
2779 let [graph, sha, refs, subject, date] = tokens
2780 let tag = matchstr(refs, 'tag: [^,)]\+')
2781 let tag = empty(tag) ? ' ' : ' ('.tag.') '
2782 return printf('%s%s%s%s%s (%s)', indent, graph, sha, tag, subject, date)
2785 function! s:append_ul(lnum, text)
2786 call append(a:lnum, ['', a:text, repeat('-', len(a:text))])
2791 call append(0, ['Collecting changes ...', ''])
2794 let total = filter(copy(g:plugs), 's:is_managed(v:key) && isdirectory(v:val.dir)')
2795 call s:progress_bar(2, bar, len(total))
2796 for origin in [1, 0]
2797 let plugs = reverse(sort(items(filter(copy(total), (origin ? '' : '!').'(has_key(v:val, "commit") || has_key(v:val, "tag"))'))))
2801 call s:append_ul(2, origin ? 'Pending updates:' : 'Last update:')
2803 let branch = s:git_origin_branch(v)
2805 let range = origin ? '..origin/'.branch : 'HEAD@{1}..'
2806 let cmd = ['git', 'log', '--graph', '--color=never']
2807 if s:git_version_requirement(2, 10, 0)
2808 call add(cmd, '--no-show-signature')
2810 call extend(cmd, ['--pretty=format:%x01%h%x01%d%x01%s%x01%cr', range])
2811 if has_key(v, 'rtp')
2812 call extend(cmd, ['--', v.rtp])
2814 let diff = s:system_chomp(cmd, v.dir)
2816 let ref = has_key(v, 'tag') ? (' (tag: '.v.tag.')') : has_key(v, 'commit') ? (' '.v.commit) : ''
2817 call append(5, extend(['', '- '.k.':'.ref], map(s:lines(diff), 's:format_git_log(v:val)')))
2818 let cnts[origin] += 1
2822 call s:progress_bar(2, bar, len(total))
2827 call append(5, ['', 'N/A'])
2830 call setline(1, printf('%d plugin(s) updated.', cnts[0])
2831 \ . (cnts[1] ? printf(' %d plugin(s) have pending updates.', cnts[1]) : ''))
2833 if cnts[0] || cnts[1]
2834 nnoremap <silent> <buffer> <plug>(plug-preview) :silent! call <SID>preview_commit()<cr>
2835 if empty(maparg("\<cr>", 'n'))
2836 nmap <buffer> <cr> <plug>(plug-preview)
2838 if empty(maparg('o', 'n'))
2839 nmap <buffer> o <plug>(plug-preview)
2843 nnoremap <silent> <buffer> X :call <SID>revert()<cr>
2844 echo "Press 'X' on each block to revert the update"
2847 setlocal nomodifiable
2850 function! s:revert()
2851 if search('^Pending updates', 'bnW')
2855 let name = s:find_name(line('.'))
2856 if empty(name) || !has_key(g:plugs, name) ||
2857 \ input(printf('Revert the update of %s? (y/N) ', name)) !~? '^y'
2861 call s:system('git reset --hard HEAD@{1} && git checkout '.plug#shellescape(g:plugs[name].branch).' --', g:plugs[name].dir)
2864 setlocal nomodifiable
2868 function! s:snapshot(force, ...) abort
2871 call append(0, ['" Generated by vim-plug',
2872 \ '" '.strftime("%c"),
2873 \ '" :source this file in vim to restore the snapshot',
2874 \ '" or execute: vim -S snapshot.vim',
2875 \ '', '', 'PlugUpdate!'])
2877 let anchor = line('$') - 3
2878 let names = sort(keys(filter(copy(g:plugs),
2879 \'has_key(v:val, "uri") && isdirectory(v:val.dir)')))
2880 for name in reverse(names)
2881 let sha = has_key(g:plugs[name], 'commit') ? g:plugs[name].commit : s:git_revision(g:plugs[name].dir)
2883 call append(anchor, printf("silent! let g:plugs['%s'].commit = '%s'", name, sha))
2889 let fn = s:plug_expand(a:1)
2890 if filereadable(fn) && !(a:force || s:ask(a:1.' already exists. Overwrite?'))
2893 call writefile(getline(1, '$'), fn)
2894 echo 'Saved as '.a:1
2895 silent execute 'e' s:esc(fn)
2900 function! s:split_rtp()
2901 return split(&rtp, '\\\@<!,')
2904 let s:first_rtp = s:escrtp(get(s:split_rtp(), 0, ''))
2905 let s:last_rtp = s:escrtp(get(s:split_rtp(), -1, ''))
2907 if exists('g:plugs')
2908 let g:plugs_order = get(g:, 'plugs_order', keys(g:plugs))
2909 call s:upgrade_specs()
2910 call s:define_commands()
2913 let &cpo = s:cpo_save