All patches and comments are welcome. Please squash your changes to logical
commits before using git-format-patch and git-send-email to
patches@git.madduck.net.
If you'd read over the Git project's submission guidelines and adhered to them,
I'd be especially grateful.
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 \ has('patch-7.4.1898')
376 \ ? 'command! -nargs=* -range -bang -complete=file %s call s:lod_cmd(%s, "<bang>", <line1>, <line2>, <q-args>, <q-mods> ,%s)'
377 \ : 'command! -nargs=* -range -bang -complete=file %s call s:lod_cmd(%s, "<bang>", <line1>, <line2>, <q-args>, %s)'
378 \ , cmd, string(cmd), string(names))
381 for [map, names] in items(lod.map)
382 for [mode, map_prefix, key_prefix] in
383 \ [['i', '<C-\><C-O>', ''], ['n', '', ''], ['v', '', 'gv'], ['o', '', '']]
385 \ '%snoremap <silent> %s %s:<C-U>call <SID>lod_map(%s, %s, %s, "%s")<CR>',
386 \ mode, map, map_prefix, string(map), string(names), mode != 'i', key_prefix)
390 for [ft, names] in items(lod.ft)
392 execute printf('autocmd FileType %s call <SID>lod_ft(%s, %s)',
393 \ ft, string(ft), string(names))
398 filetype plugin indent on
399 if has('vim_starting')
400 if has('syntax') && !exists('g:syntax_on')
404 call s:reload_plugins()
408 function! s:loaded_names()
409 return filter(copy(g:plugs_order), 'get(s:loaded, v:val, 0)')
412 function! s:load_plugin(spec)
413 call s:source(s:rtp(a:spec), 'plugin/**/*.vim', 'after/plugin/**/*.vim')
415 call s:source(s:rtp(a:spec), 'plugin/**/*.lua', 'after/plugin/**/*.lua')
419 function! s:reload_plugins()
420 for name in s:loaded_names()
421 call s:load_plugin(g:plugs[name])
425 function! s:trim(str)
426 return substitute(a:str, '[\/]\+$', '', '')
429 function! s:version_requirement(val, min)
430 for idx in range(0, len(a:min) - 1)
431 let v = get(a:val, idx, 0)
432 if v < a:min[idx] | return 0
433 elseif v > a:min[idx] | return 1
439 function! s:git_version_requirement(...)
440 if !exists('s:git_version')
441 let s:git_version = map(split(split(s:system(['git', '--version']))[2], '\.'), 'str2nr(v:val)')
443 return s:version_requirement(s:git_version, a:000)
446 function! s:progress_opt(base)
447 return a:base && !s:is_win &&
448 \ s:git_version_requirement(1, 7, 1) ? '--progress' : ''
451 function! s:rtp(spec)
452 return s:path(a:spec.dir . get(a:spec, 'rtp', ''))
456 function! s:path(path)
457 return s:trim(substitute(a:path, '/', '\', 'g'))
460 function! s:dirpath(path)
461 return s:path(a:path) . '\'
464 function! s:is_local_plug(repo)
465 return a:repo =~? '^[a-z]:\|^[%~]'
469 function! s:wrap_cmds(cmds)
472 \ 'setlocal enabledelayedexpansion']
473 \ + (type(a:cmds) == type([]) ? a:cmds : [a:cmds])
476 if !exists('s:codepage')
477 let s:codepage = libcallnr('kernel32.dll', 'GetACP', 0)
479 return map(cmds, printf('iconv(v:val."\r", "%s", "cp%d")', &encoding, s:codepage))
481 return map(cmds, 'v:val."\r"')
484 function! s:batchfile(cmd)
485 let batchfile = s:plug_tempname().'.bat'
486 call writefile(s:wrap_cmds(a:cmd), batchfile)
487 let cmd = plug#shellescape(batchfile, {'shell': &shell, 'script': 0})
488 if s:is_powershell(&shell)
491 return [batchfile, cmd]
494 function! s:path(path)
495 return s:trim(a:path)
498 function! s:dirpath(path)
499 return substitute(a:path, '[/\\]*$', '/', '')
502 function! s:is_local_plug(repo)
503 return a:repo[0] =~ '[/$~]'
509 echom '[vim-plug] '.a:msg
513 function! s:warn(cmd, msg)
515 execute a:cmd 'a:msg'
519 function! s:esc(path)
520 return escape(a:path, ' ')
523 function! s:escrtp(path)
524 return escape(a:path, ' ,')
527 function! s:remove_rtp()
528 for name in s:loaded_names()
529 let rtp = s:rtp(g:plugs[name])
530 execute 'set rtp-='.s:escrtp(rtp)
531 let after = globpath(rtp, 'after')
532 if isdirectory(after)
533 execute 'set rtp-='.s:escrtp(after)
538 function! s:reorg_rtp()
539 if !empty(s:first_rtp)
540 execute 'set rtp-='.s:first_rtp
541 execute 'set rtp-='.s:last_rtp
544 " &rtp is modified from outside
545 if exists('s:prtp') && s:prtp !=# &rtp
550 let s:middle = get(s:, 'middle', &rtp)
551 let rtps = map(s:loaded_names(), 's:rtp(g:plugs[v:val])')
552 let afters = filter(map(copy(rtps), 'globpath(v:val, "after")'), '!empty(v:val)')
553 let rtp = join(map(rtps, 'escape(v:val, ",")'), ',')
555 \ . join(map(afters, 'escape(v:val, ",")'), ',')
556 let &rtp = substitute(substitute(rtp, ',,*', ',', 'g'), '^,\|,$', '', 'g')
559 if !empty(s:first_rtp)
560 execute 'set rtp^='.s:first_rtp
561 execute 'set rtp+='.s:last_rtp
565 function! s:doautocmd(...)
566 if exists('#'.join(a:000, '#'))
567 execute 'doautocmd' ((v:version > 703 || has('patch442')) ? '<nomodeline>' : '') join(a:000)
571 function! s:dobufread(names)
573 let path = s:rtp(g:plugs[name])
574 for dir in ['ftdetect', 'ftplugin', 'after/ftdetect', 'after/ftplugin']
575 if len(finddir(dir, path))
576 if exists('#BufRead')
585 function! plug#load(...)
587 return s:err('Argument missing: plugin name(s) required')
589 if !exists('g:plugs')
590 return s:err('plug#begin was not called')
592 let names = a:0 == 1 && type(a:1) == s:TYPE.list ? a:1 : a:000
593 let unknowns = filter(copy(names), '!has_key(g:plugs, v:val)')
595 let s = len(unknowns) > 1 ? 's' : ''
596 return s:err(printf('Unknown plugin%s: %s', s, join(unknowns, ', ')))
598 let unloaded = filter(copy(names), '!get(s:loaded, v:val, 0)')
601 call s:lod([name], ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin'])
603 call s:dobufread(unloaded)
609 function! s:remove_triggers(name)
610 if !has_key(s:triggers, a:name)
613 for cmd in s:triggers[a:name].cmd
614 execute 'silent! delc' cmd
616 for map in s:triggers[a:name].map
617 execute 'silent! unmap' map
618 execute 'silent! iunmap' map
620 call remove(s:triggers, a:name)
623 function! s:lod(names, types, ...)
625 call s:remove_triggers(name)
626 let s:loaded[name] = 1
631 let rtp = s:rtp(g:plugs[name])
633 call s:source(rtp, dir.'/**/*.vim')
634 if has('nvim-0.5.0') " see neovim#14686
635 call s:source(rtp, dir.'/**/*.lua')
639 if !s:source(rtp, a:1) && !empty(s:glob(rtp, a:2))
640 execute 'runtime' a:1
642 call s:source(rtp, a:2)
644 call s:doautocmd('User', name)
648 function! s:lod_ft(pat, names)
649 let syn = 'syntax/'.a:pat.'.vim'
650 call s:lod(a:names, ['plugin', 'after/plugin'], syn, 'after/'.syn)
651 execute 'autocmd! PlugLOD FileType' a:pat
652 call s:doautocmd('filetypeplugin', 'FileType')
653 call s:doautocmd('filetypeindent', 'FileType')
656 if has('patch-7.4.1898')
657 function! s:lod_cmd(cmd, bang, l1, l2, args, mods, names)
658 call s:lod(a:names, ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin'])
659 call s:dobufread(a:names)
660 execute printf('%s %s%s%s %s', a:mods, (a:l1 == a:l2 ? '' : (a:l1.','.a:l2)), a:cmd, a:bang, a:args)
663 function! s:lod_cmd(cmd, bang, l1, l2, args, names)
664 call s:lod(a:names, ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin'])
665 call s:dobufread(a:names)
666 execute printf('%s%s%s %s', (a:l1 == a:l2 ? '' : (a:l1.','.a:l2)), a:cmd, a:bang, a:args)
670 function! s:lod_map(map, names, with_prefix, prefix)
671 call s:lod(a:names, ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin'])
672 call s:dobufread(a:names)
679 let extra .= nr2char(c)
683 let prefix = v:count ? v:count : ''
684 let prefix .= '"'.v:register.a:prefix
687 let prefix = "\<esc>" . prefix
689 let prefix .= v:operator
691 call feedkeys(prefix, 'n')
693 call feedkeys(substitute(a:map, '^<Plug>', "\<Plug>", '') . extra)
696 function! plug#(repo, ...)
698 return s:err('Invalid number of arguments (1..2)')
702 let repo = s:trim(a:repo)
703 let opts = a:0 == 1 ? s:parse_options(a:1) : s:base_spec
704 let name = get(opts, 'as', s:plug_fnamemodify(repo, ':t:s?\.git$??'))
705 let spec = extend(s:infer_properties(name, repo), opts)
706 if !has_key(g:plugs, name)
707 call add(g:plugs_order, name)
709 let g:plugs[name] = spec
710 let s:loaded[name] = get(s:loaded, name, 0)
712 return s:err(repo . ' ' . v:exception)
716 function! s:parse_options(arg)
717 let opts = copy(s:base_spec)
718 let type = type(a:arg)
719 let opt_errfmt = 'Invalid argument for "%s" option of :Plug (expected: %s)'
720 if type == s:TYPE.string
722 throw printf(opt_errfmt, 'tag', 'string')
725 elseif type == s:TYPE.dict
726 for opt in ['branch', 'tag', 'commit', 'rtp', 'dir', 'as']
727 if has_key(a:arg, opt)
728 \ && (type(a:arg[opt]) != s:TYPE.string || empty(a:arg[opt]))
729 throw printf(opt_errfmt, opt, 'string')
732 for opt in ['on', 'for']
733 if has_key(a:arg, opt)
734 \ && type(a:arg[opt]) != s:TYPE.list
735 \ && (type(a:arg[opt]) != s:TYPE.string || empty(a:arg[opt]))
736 throw printf(opt_errfmt, opt, 'string or list')
739 if has_key(a:arg, 'do')
740 \ && type(a:arg.do) != s:TYPE.funcref
741 \ && (type(a:arg.do) != s:TYPE.string || empty(a:arg.do))
742 throw printf(opt_errfmt, 'do', 'string or funcref')
744 call extend(opts, a:arg)
745 if has_key(opts, 'dir')
746 let opts.dir = s:dirpath(s:plug_expand(opts.dir))
749 throw 'Invalid argument type (expected: string or dictionary)'
754 function! s:infer_properties(name, repo)
756 if s:is_local_plug(repo)
757 return { 'dir': s:dirpath(s:plug_expand(repo)) }
763 throw printf('Invalid argument: %s (implicit `vim-scripts'' expansion is deprecated)', repo)
765 let fmt = get(g:, 'plug_url_format', 'https://git::@github.com/%s.git')
766 let uri = printf(fmt, repo)
768 return { 'dir': s:dirpath(g:plug_home.'/'.a:name), 'uri': uri }
772 function! s:install(force, names)
773 call s:update_impl(0, a:force, a:names)
776 function! s:update(force, names)
777 call s:update_impl(1, a:force, a:names)
780 function! plug#helptags()
781 if !exists('g:plugs')
782 return s:err('plug#begin was not called')
784 for spec in values(g:plugs)
785 let docd = join([s:rtp(spec), 'doc'], '/')
787 silent! execute 'helptags' s:esc(docd)
795 syntax region plug1 start=/\%1l/ end=/\%2l/ contains=plugNumber
796 syntax region plug2 start=/\%2l/ end=/\%3l/ contains=plugBracket,plugX,plugAbort
797 syn match plugNumber /[0-9]\+[0-9.]*/ contained
798 syn match plugBracket /[[\]]/ contained
799 syn match plugX /x/ contained
800 syn match plugAbort /\~/ contained
801 syn match plugDash /^-\{1}\ /
802 syn match plugPlus /^+/
803 syn match plugStar /^*/
804 syn match plugMessage /\(^- \)\@<=.*/
805 syn match plugName /\(^- \)\@<=[^ ]*:/
806 syn match plugSha /\%(: \)\@<=[0-9a-f]\{4,}$/
807 syn match plugTag /(tag: [^)]\+)/
808 syn match plugInstall /\(^+ \)\@<=[^:]*/
809 syn match plugUpdate /\(^* \)\@<=[^:]*/
810 syn match plugCommit /^ \X*[0-9a-f]\{7,9} .*/ contains=plugRelDate,plugEdge,plugTag
811 syn match plugEdge /^ \X\+$/
812 syn match plugEdge /^ \X*/ contained nextgroup=plugSha
813 syn match plugSha /[0-9a-f]\{7,9}/ contained
814 syn match plugRelDate /([^)]*)$/ contained
815 syn match plugNotLoaded /(not loaded)$/
816 syn match plugError /^x.*/
817 syn region plugDeleted start=/^\~ .*/ end=/^\ze\S/
818 syn match plugH2 /^.*:\n-\+$/
819 syn match plugH2 /^-\{2,}/
820 syn keyword Function PlugInstall PlugStatus PlugUpdate PlugClean
821 hi def link plug1 Title
822 hi def link plug2 Repeat
823 hi def link plugH2 Type
824 hi def link plugX Exception
825 hi def link plugAbort Ignore
826 hi def link plugBracket Structure
827 hi def link plugNumber Number
829 hi def link plugDash Special
830 hi def link plugPlus Constant
831 hi def link plugStar Boolean
833 hi def link plugMessage Function
834 hi def link plugName Label
835 hi def link plugInstall Function
836 hi def link plugUpdate Type
838 hi def link plugError Error
839 hi def link plugDeleted Ignore
840 hi def link plugRelDate Comment
841 hi def link plugEdge PreProc
842 hi def link plugSha Identifier
843 hi def link plugTag Constant
845 hi def link plugNotLoaded Comment
848 function! s:lpad(str, len)
849 return a:str . repeat(' ', a:len - len(a:str))
852 function! s:lines(msg)
853 return split(a:msg, "[\r\n]")
856 function! s:lastline(msg)
857 return get(s:lines(a:msg), -1, '')
860 function! s:new_window()
861 execute get(g:, 'plug_window', '-tabnew')
864 function! s:plug_window_exists()
865 let buflist = tabpagebuflist(s:plug_tab)
866 return !empty(buflist) && index(buflist, s:plug_buf) >= 0
869 function! s:switch_in()
870 if !s:plug_window_exists()
874 if winbufnr(0) != s:plug_buf
875 let s:pos = [tabpagenr(), winnr(), winsaveview()]
876 execute 'normal!' s:plug_tab.'gt'
877 let winnr = bufwinnr(s:plug_buf)
878 execute winnr.'wincmd w'
879 call add(s:pos, winsaveview())
881 let s:pos = [winsaveview()]
888 function! s:switch_out(...)
889 call winrestview(s:pos[-1])
890 setlocal nomodifiable
896 execute 'normal!' s:pos[0].'gt'
897 execute s:pos[1] 'wincmd w'
898 call winrestview(s:pos[2])
902 function! s:finish_bindings()
903 nnoremap <silent> <buffer> R :call <SID>retry()<cr>
904 nnoremap <silent> <buffer> D :PlugDiff<cr>
905 nnoremap <silent> <buffer> S :PlugStatus<cr>
906 nnoremap <silent> <buffer> U :call <SID>status_update()<cr>
907 xnoremap <silent> <buffer> U :call <SID>status_update()<cr>
908 nnoremap <silent> <buffer> ]] :silent! call <SID>section('')<cr>
909 nnoremap <silent> <buffer> [[ :silent! call <SID>section('b')<cr>
912 function! s:prepare(...)
913 if empty(s:plug_getcwd())
914 throw 'Invalid current working directory. Cannot proceed.'
917 for evar in ['$GIT_DIR', '$GIT_WORK_TREE']
919 throw evar.' detected. Cannot proceed.'
925 if b:plug_preview == 1
933 nnoremap <silent> <buffer> q :call <SID>close_pane()<cr>
935 call s:finish_bindings()
937 let b:plug_preview = -1
938 let s:plug_tab = tabpagenr()
939 let s:plug_buf = winbufnr(0)
942 for k in ['<cr>', 'L', 'o', 'X', 'd', 'dd']
943 execute 'silent! unmap <buffer>' k
945 setlocal buftype=nofile bufhidden=wipe nobuflisted nolist noswapfile nowrap cursorline modifiable nospell
946 if exists('+colorcolumn')
947 setlocal colorcolumn=
950 if exists('g:syntax_on')
955 function! s:close_pane()
956 if b:plug_preview == 1
958 let b:plug_preview = -1
959 elseif exists('s:jobs') && !empty(s:jobs)
966 function! s:assign_name()
968 let prefix = '[Plugins]'
971 while bufexists(name)
972 let name = printf('%s (%s)', prefix, idx)
975 silent! execute 'f' fnameescape(name)
978 function! s:chsh(swap)
979 let prev = [&shell, &shellcmdflag, &shellredir]
984 if s:is_powershell(&shell)
985 let &shellredir = '2>&1 | Out-File -Encoding UTF8 %s'
986 elseif &shell =~# 'sh' || &shell =~# 'cmd\(\.exe\)\?$'
987 set shellredir=>%s\ 2>&1
993 function! s:bang(cmd, ...)
996 let [sh, shellcmdflag, shrd] = s:chsh(a:0)
997 " FIXME: Escaping is incomplete. We could use shellescape with eval,
998 " but it won't work on Windows.
999 let cmd = a:0 ? s:with_cd(a:cmd, a:1) : a:cmd
1001 let [batchfile, cmd] = s:batchfile(cmd)
1003 let g:_plug_bang = (s:is_win && has('gui_running') ? 'silent ' : '').'!'.escape(cmd, '#!%')
1004 execute "normal! :execute g:_plug_bang\<cr>\<cr>"
1007 let [&shell, &shellcmdflag, &shellredir] = [sh, shellcmdflag, shrd]
1008 if s:is_win && filereadable(batchfile)
1009 call delete(batchfile)
1012 return v:shell_error ? 'Exit status: ' . v:shell_error : ''
1015 function! s:regress_bar()
1016 let bar = substitute(getline(2)[1:-2], '.*\zs=', 'x', '')
1017 call s:progress_bar(2, bar, len(bar))
1020 function! s:is_updated(dir)
1021 return !empty(s:system_chomp(['git', 'log', '--pretty=format:%h', 'HEAD...HEAD@{1}'], a:dir))
1024 function! s:do(pull, force, todo)
1026 " Reset &rtp to invalidate Neovim cache of loaded Lua modules
1027 " See https://github.com/junegunn/vim-plug/pull/1157#issuecomment-1809226110
1030 for [name, spec] in items(a:todo)
1031 if !isdirectory(spec.dir)
1034 let installed = has_key(s:update.new, name)
1035 let updated = installed ? 0 :
1036 \ (a:pull && index(s:update.errors, name) < 0 && s:is_updated(spec.dir))
1037 if a:force || installed || updated
1038 execute 'cd' s:esc(spec.dir)
1039 call append(3, '- Post-update hook for '. name .' ... ')
1041 let type = type(spec.do)
1042 if type == s:TYPE.string
1043 if spec.do[0] == ':'
1044 if !get(s:loaded, name, 0)
1045 let s:loaded[name] = 1
1048 call s:load_plugin(spec)
1052 let error = v:exception
1054 if !s:plug_window_exists()
1056 throw 'Warning: vim-plug was terminated by the post-update hook of '.name
1059 let error = s:bang(spec.do)
1061 elseif type == s:TYPE.funcref
1063 call s:load_plugin(spec)
1064 let status = installed ? 'installed' : (updated ? 'updated' : 'unchanged')
1065 call spec.do({ 'name': name, 'status': status, 'force': a:force })
1067 let error = v:exception
1070 let error = 'Invalid hook type'
1073 call setline(4, empty(error) ? (getline(4) . 'OK')
1074 \ : ('x' . getline(4)[1:] . error))
1076 call add(s:update.errors, name)
1077 call s:regress_bar()
1084 function! s:hash_match(a, b)
1085 return stridx(a:a, a:b) == 0 || stridx(a:b, a:a) == 0
1088 function! s:disable_credential_helper()
1089 return s:git_version_requirement(2) && get(g:, 'plug_disable_credential_helper', 1)
1092 function! s:checkout(spec)
1093 let sha = a:spec.commit
1094 let output = s:git_revision(a:spec.dir)
1096 if !empty(output) && !s:hash_match(sha, s:lines(output)[0])
1097 let credential_helper = s:disable_credential_helper() ? '-c credential.helper= ' : ''
1098 let output = s:system(
1099 \ 'git '.credential_helper.'fetch --depth 999999 && git checkout '.plug#shellescape(sha).' --', a:spec.dir)
1100 let error = v:shell_error
1102 return [output, error]
1105 function! s:finish(pull)
1106 let new_frozen = len(filter(keys(s:update.new), 'g:plugs[v:val].frozen'))
1108 let s = new_frozen > 1 ? 's' : ''
1109 call append(3, printf('- Installed %d frozen plugin%s', new_frozen, s))
1111 call append(3, '- Finishing ... ') | 4
1113 call plug#helptags()
1115 call setline(4, getline(4) . 'Done!')
1118 if !empty(s:update.errors)
1119 call add(msgs, "Press 'R' to retry.")
1121 if a:pull && len(s:update.new) < len(filter(getline(5, '$'),
1122 \ "v:val =~ '^- ' && v:val !~# 'Already up.to.date'"))
1123 call add(msgs, "Press 'D' to see the updated changes.")
1125 echo join(msgs, ' ')
1126 call s:finish_bindings()
1130 if empty(s:update.errors)
1134 call s:update_impl(s:update.pull, s:update.force,
1135 \ extend(copy(s:update.errors), [s:update.threads]))
1138 function! s:is_managed(name)
1139 return has_key(g:plugs[a:name], 'uri')
1142 function! s:names(...)
1143 return sort(filter(keys(g:plugs), 'stridx(v:val, a:1) == 0 && s:is_managed(v:val)'))
1146 function! s:check_ruby()
1147 silent! ruby require 'thread'; VIM::command("let g:plug_ruby = '#{RUBY_VERSION}'")
1148 if !exists('g:plug_ruby')
1150 return s:warn('echom', 'Warning: Ruby interface is broken')
1152 let ruby_version = split(g:plug_ruby, '\.')
1154 return s:version_requirement(ruby_version, [1, 8, 7])
1157 function! s:update_impl(pull, force, args) abort
1158 let sync = index(a:args, '--sync') >= 0 || has('vim_starting')
1159 let args = filter(copy(a:args), 'v:val != "--sync"')
1160 let threads = (len(args) > 0 && args[-1] =~ '^[1-9][0-9]*$') ?
1161 \ remove(args, -1) : get(g:, 'plug_threads', 16)
1163 let managed = filter(deepcopy(g:plugs), 's:is_managed(v:key)')
1164 let todo = empty(args) ? filter(managed, '!v:val.frozen || !isdirectory(v:val.dir)') :
1165 \ filter(managed, 'index(args, v:key) >= 0')
1168 return s:warn('echo', 'No plugin to '. (a:pull ? 'update' : 'install'))
1171 if !s:is_win && s:git_version_requirement(2, 3)
1172 let s:git_terminal_prompt = exists('$GIT_TERMINAL_PROMPT') ? $GIT_TERMINAL_PROMPT : ''
1173 let $GIT_TERMINAL_PROMPT = 0
1174 for plug in values(todo)
1175 let plug.uri = substitute(plug.uri,
1176 \ '^https://git::@github\.com', 'https://github.com', '')
1180 if !isdirectory(g:plug_home)
1182 call mkdir(g:plug_home, 'p')
1184 return s:err(printf('Invalid plug directory: %s. '.
1185 \ 'Try to call plug#begin with a valid directory', g:plug_home))
1189 if has('nvim') && !exists('*jobwait') && threads > 1
1190 call s:warn('echom', '[vim-plug] Update Neovim for parallel installer')
1193 let use_job = s:nvim || s:vim8
1194 let python = (has('python') || has('python3')) && !use_job
1195 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()
1198 \ 'start': reltime(),
1200 \ 'todo': copy(todo),
1205 \ 'threads': (python || ruby || use_job) ? min([len(todo), threads]) : 1,
1211 call append(0, ['', ''])
1215 " Set remote name, overriding a possible user git config's clone.defaultRemoteName
1216 let s:clone_opt = ['--origin', 'origin']
1217 if get(g:, 'plug_shallow', 1)
1218 call extend(s:clone_opt, ['--depth', '1'])
1219 if s:git_version_requirement(1, 7, 10)
1220 call add(s:clone_opt, '--no-single-branch')
1224 if has('win32unix') || has('wsl')
1225 call extend(s:clone_opt, ['-c', 'core.eol=lf', '-c', 'core.autocrlf=input'])
1228 let s:submodule_opt = s:git_version_requirement(2, 8) ? ' --jobs='.threads : ''
1230 " Python version requirement (>= 2.7)
1231 if python && !has('python3') && !ruby && !use_job && s:update.threads > 1
1233 silent python import platform; print platform.python_version()
1235 let python = s:version_requirement(
1236 \ map(split(split(pyv)[0], '\.'), 'str2nr(v:val)'), [2, 6])
1239 if (python || ruby) && s:update.threads > 1
1246 call s:update_ruby()
1248 call s:update_python()
1251 let lines = getline(4, '$')
1255 let name = s:extract_name(line, '.', '')
1256 if empty(name) || !has_key(printed, name)
1257 call append('$', line)
1259 let printed[name] = 1
1260 if line[0] == 'x' && index(s:update.errors, name) < 0
1261 call add(s:update.errors, name)
1268 call s:update_finish()
1272 while use_job && sync
1281 function! s:log4(name, msg)
1282 call setline(4, printf('- %s (%s)', a:msg, a:name))
1286 function! s:update_finish()
1287 if exists('s:git_terminal_prompt')
1288 let $GIT_TERMINAL_PROMPT = s:git_terminal_prompt
1291 call append(3, '- Updating ...') | 4
1292 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))'))
1293 let [pos, _] = s:logpos(name)
1299 if has_key(spec, 'commit')
1300 call s:log4(name, 'Checking out '.spec.commit)
1301 let [out, error] = s:checkout(spec)
1302 elseif has_key(spec, 'tag')
1305 let tags = s:lines(s:system('git tag --list '.plug#shellescape(tag).' --sort -version:refname 2>&1', spec.dir))
1306 if !v:shell_error && !empty(tags)
1308 call s:log4(name, printf('Latest tag for %s -> %s', spec.tag, tag))
1312 call s:log4(name, 'Checking out '.tag)
1313 let out = s:system('git checkout -q '.plug#shellescape(tag).' -- 2>&1', spec.dir)
1314 let error = v:shell_error
1316 if !error && filereadable(spec.dir.'/.gitmodules') &&
1317 \ (s:update.force || has_key(s:update.new, name) || s:is_updated(spec.dir))
1318 call s:log4(name, 'Updating submodules. This may take a while.')
1319 let out .= s:bang('git submodule update --init --recursive'.s:submodule_opt.' 2>&1', spec.dir)
1320 let error = v:shell_error
1322 let msg = s:format_message(v:shell_error ? 'x': '-', name, out)
1324 call add(s:update.errors, name)
1325 call s:regress_bar()
1326 silent execute pos 'd _'
1327 call append(4, msg) | 4
1329 call setline(pos, msg[0])
1335 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")'))
1337 call s:warn('echom', v:exception)
1338 call s:warn('echo', '')
1341 call s:finish(s:update.pull)
1342 call setline(1, 'Updated. Elapsed time: ' . split(reltimestr(reltime(s:update.start)))[0] . ' sec.')
1343 call s:switch_out('normal! gg')
1347 function! s:mark_aborted(name, message)
1348 let attrs = { 'running': 0, 'error': 1, 'abort': 1, 'lines': [a:message] }
1349 let s:jobs[a:name] = extend(get(s:jobs, a:name, {}), attrs)
1352 function! s:job_abort(cancel)
1353 if (!s:nvim && !s:vim8) || !exists('s:jobs')
1357 for [name, j] in items(s:jobs)
1359 silent! call jobstop(j.jobid)
1361 silent! call job_stop(j.jobid)
1364 call s:rm_rf(g:plugs[name].dir)
1367 call s:mark_aborted(name, 'Aborted')
1372 for todo in values(s:update.todo)
1380 function! s:last_non_empty_line(lines)
1381 let len = len(a:lines)
1382 for idx in range(len)
1383 let line = a:lines[len-idx-1]
1391 function! s:bullet_for(job, ...)
1393 return a:job.new ? '+' : '*'
1395 if get(a:job, 'abort', 0)
1398 return a:job.error ? 'x' : get(a:000, 0, '-')
1401 function! s:job_out_cb(self, data) abort
1403 let data = remove(self.lines, -1) . a:data
1404 let lines = map(split(data, "\n", 1), 'split(v:val, "\r", 1)[-1]')
1405 call extend(self.lines, lines)
1406 " To reduce the number of buffer updates
1407 let self.tick = get(self, 'tick', -1) + 1
1408 if !self.running || self.tick % len(s:jobs) == 0
1409 let result = self.error ? join(self.lines, "\n") : s:last_non_empty_line(self.lines)
1411 call s:log(s:bullet_for(self), self.name, result)
1416 function! s:job_exit_cb(self, data) abort
1417 let a:self.running = 0
1418 let a:self.error = a:data != 0
1419 call s:reap(a:self.name)
1423 function! s:job_cb(fn, job, ch, data)
1424 if !s:plug_window_exists() " plug window closed
1425 return s:job_abort(0)
1427 call call(a:fn, [a:job, a:data])
1430 function! s:nvim_cb(job_id, data, event) dict abort
1431 return (a:event == 'stdout' || a:event == 'stderr') ?
1432 \ s:job_cb('s:job_out_cb', self, 0, join(a:data, "\n")) :
1433 \ s:job_cb('s:job_exit_cb', self, 0, a:data)
1436 function! s:spawn(name, spec, queue, opts)
1437 let job = { 'name': a:name, 'spec': a:spec, 'running': 1, 'error': 0, 'lines': [''],
1438 \ 'new': get(a:opts, 'new', 0), 'queue': copy(a:queue) }
1439 let Item = remove(job.queue, 0)
1440 let argv = type(Item) == s:TYPE.funcref ? call(Item, [a:spec]) : Item
1441 let s:jobs[a:name] = job
1444 if has_key(a:opts, 'dir')
1445 let job.cwd = a:opts.dir
1448 \ 'on_stdout': function('s:nvim_cb'),
1449 \ 'on_stderr': function('s:nvim_cb'),
1450 \ 'on_exit': function('s:nvim_cb'),
1452 let jid = s:plug_call('jobstart', argv, job)
1458 let job.lines = [jid < 0 ? argv[0].' is not executable' :
1459 \ 'Invalid arguments (or job table is full)']
1462 let cmd = join(map(copy(argv), 'plug#shellescape(v:val, {"script": 0})'))
1463 if has_key(a:opts, 'dir')
1464 let cmd = s:with_cd(cmd, a:opts.dir, 0)
1466 let argv = s:is_win ? ['cmd', '/s', '/c', '"'.cmd.'"'] : ['sh', '-c', cmd]
1467 let jid = job_start(s:is_win ? join(argv, ' ') : argv, {
1468 \ 'out_cb': function('s:job_cb', ['s:job_out_cb', job]),
1469 \ 'err_cb': function('s:job_cb', ['s:job_out_cb', job]),
1470 \ 'exit_cb': function('s:job_cb', ['s:job_exit_cb', job]),
1471 \ 'err_mode': 'raw',
1474 if job_status(jid) == 'run'
1479 let job.lines = ['Failed to start job']
1482 let job.lines = s:lines(call('s:system', has_key(a:opts, 'dir') ? [argv, a:opts.dir] : [argv]))
1483 let job.error = v:shell_error != 0
1488 function! s:reap(name)
1489 let job = remove(s:jobs, a:name)
1491 call add(s:update.errors, a:name)
1492 elseif get(job, 'new', 0)
1493 let s:update.new[a:name] = 1
1496 let more = len(get(job, 'queue', []))
1497 let result = job.error ? join(job.lines, "\n") : s:last_non_empty_line(job.lines)
1499 call s:log(s:bullet_for(job), a:name, result)
1502 if !job.error && more
1503 let job.spec.queue = job.queue
1504 let s:update.todo[a:name] = job.spec
1506 let s:update.bar .= s:bullet_for(job, '=')
1513 let total = len(s:update.all)
1514 call setline(1, (s:update.pull ? 'Updating' : 'Installing').
1515 \ ' plugins ('.len(s:update.bar).'/'.total.')')
1516 call s:progress_bar(2, s:update.bar, total)
1521 function! s:logpos(name)
1523 for i in range(4, max > 4 ? max : 4)
1524 if getline(i) =~# '^[-+x*] '.a:name.':'
1525 for j in range(i + 1, max > 5 ? max : 5)
1526 if getline(j) !~ '^ '
1536 function! s:log(bullet, name, lines)
1538 let [b, e] = s:logpos(a:name)
1540 silent execute printf('%d,%d d _', b, e)
1541 if b > winheight('.')
1547 " FIXME For some reason, nomodifiable is set after :d in vim8
1549 call append(b - 1, s:format_message(a:bullet, a:name, a:lines))
1554 function! s:update_vim()
1561 function! s:checkout_command(spec)
1562 let a:spec.branch = s:git_origin_branch(a:spec)
1563 return ['git', 'checkout', '-q', a:spec.branch, '--']
1566 function! s:merge_command(spec)
1567 let a:spec.branch = s:git_origin_branch(a:spec)
1568 return ['git', 'merge', '--ff-only', 'origin/'.a:spec.branch]
1572 let pull = s:update.pull
1573 let prog = s:progress_opt(s:nvim || s:vim8)
1574 while 1 " Without TCO, Vim stack is bound to explode
1575 if empty(s:update.todo)
1576 if empty(s:jobs) && !s:update.fin
1577 call s:update_finish()
1578 let s:update.fin = 1
1583 let name = keys(s:update.todo)[0]
1584 let spec = remove(s:update.todo, name)
1585 if get(spec, 'abort', 0)
1586 call s:mark_aborted(name, 'Skipped')
1591 let queue = get(spec, 'queue', [])
1592 let new = empty(globpath(spec.dir, '.git', 1))
1595 call s:log(new ? '+' : '*', name, pull ? 'Updating ...' : 'Installing ...')
1599 let has_tag = has_key(spec, 'tag')
1601 call s:spawn(name, spec, queue, { 'dir': spec.dir })
1603 let [error, _] = s:git_validate(spec, 0)
1606 let cmd = s:disable_credential_helper() ? ['git', '-c', 'credential.helper=', 'fetch'] : ['git', 'fetch']
1607 if has_tag && !empty(globpath(spec.dir, '.git/shallow'))
1608 call extend(cmd, ['--depth', '99999999'])
1613 let queue = [cmd, split('git remote set-head origin -a')]
1614 if !has_tag && !has_key(spec, 'commit')
1615 call extend(queue, [function('s:checkout_command'), function('s:merge_command')])
1617 call s:spawn(name, spec, queue, { 'dir': spec.dir })
1619 let s:jobs[name] = { 'running': 0, 'lines': ['Already installed'], 'error': 0 }
1622 let s:jobs[name] = { 'running': 0, 'lines': s:lines(error), 'error': 1 }
1625 let cmd = ['git', 'clone']
1627 call extend(cmd, s:clone_opt)
1632 call s:spawn(name, spec, [extend(cmd, [spec.uri, s:trim(spec.dir)]), function('s:checkout_command'), function('s:merge_command')], { 'new': 1 })
1635 if !s:jobs[name].running
1638 if len(s:jobs) >= s:update.threads
1644 function! s:update_python()
1645 let py_exe = has('python') ? 'python' : 'python3'
1646 execute py_exe "<< EOF"
1653 import Queue as queue
1660 import threading as thr
1665 G_NVIM = vim.eval("has('nvim')") == '1'
1666 G_PULL = vim.eval('s:update.pull') == '1'
1667 G_RETRIES = int(vim.eval('get(g:, "plug_retries", 2)')) + 1
1668 G_TIMEOUT = int(vim.eval('get(g:, "plug_timeout", 60)'))
1669 G_CLONE_OPT = ' '.join(vim.eval('s:clone_opt'))
1670 G_PROGRESS = vim.eval('s:progress_opt(1)')
1671 G_LOG_PROB = 1.0 / int(vim.eval('s:update.threads'))
1672 G_STOP = thr.Event()
1673 G_IS_WIN = vim.eval('s:is_win') == '1'
1675 class PlugError(Exception):
1676 def __init__(self, msg):
1678 class CmdTimedOut(PlugError):
1680 class CmdFailed(PlugError):
1682 class InvalidURI(PlugError):
1684 class Action(object):
1685 INSTALL, UPDATE, ERROR, DONE = ['+', '*', 'x', '-']
1687 class Buffer(object):
1688 def __init__(self, lock, num_plugs, is_pull):
1690 self.event = 'Updating' if is_pull else 'Installing'
1692 self.maxy = int(vim.eval('winheight(".")'))
1693 self.num_plugs = num_plugs
1695 def __where(self, name):
1696 """ Find first line with name in current buffer. Return line num. """
1697 found, lnum = False, 0
1698 matcher = re.compile('^[-+x*] {0}:'.format(name))
1699 for line in vim.current.buffer:
1700 if matcher.search(line) is not None:
1710 curbuf = vim.current.buffer
1711 curbuf[0] = self.event + ' plugins ({0}/{1})'.format(len(self.bar), self.num_plugs)
1713 num_spaces = self.num_plugs - len(self.bar)
1714 curbuf[1] = '[{0}{1}]'.format(self.bar, num_spaces * ' ')
1717 vim.command('normal! 2G')
1718 vim.command('redraw')
1720 def write(self, action, name, lines):
1721 first, rest = lines[0], lines[1:]
1722 msg = ['{0} {1}{2}{3}'.format(action, name, ': ' if first else '', first)]
1723 msg.extend([' ' + line for line in rest])
1726 if action == Action.ERROR:
1728 vim.command("call add(s:update.errors, '{0}')".format(name))
1729 elif action == Action.DONE:
1732 curbuf = vim.current.buffer
1733 lnum = self.__where(name)
1734 if lnum != -1: # Found matching line num
1736 if lnum > self.maxy and action in set([Action.INSTALL, Action.UPDATE]):
1740 curbuf.append(msg, lnum)
1746 class Command(object):
1747 CD = 'cd /d' if G_IS_WIN else 'cd'
1749 def __init__(self, cmd, cmd_dir=None, timeout=60, cb=None, clean=None):
1752 self.cmd = '{0} {1} && {2}'.format(Command.CD, cmd_dir, self.cmd)
1753 self.timeout = timeout
1754 self.callback = cb if cb else (lambda msg: None)
1755 self.clean = clean if clean else (lambda: None)
1760 """ Returns true only if command still running. """
1761 return self.proc and self.proc.poll() is None
1763 def execute(self, ntries=3):
1764 """ Execute the command with ntries if CmdTimedOut.
1765 Returns the output of the command if no Exception.
1767 attempt, finished, limit = 0, False, self.timeout
1772 result = self.try_command()
1776 if attempt != ntries:
1778 self.timeout += limit
1782 def notify_retry(self):
1783 """ Retry required for command, notify user. """
1784 for count in range(3, 0, -1):
1786 raise KeyboardInterrupt
1787 msg = 'Timeout. Will retry in {0} second{1} ...'.format(
1788 count, 's' if count != 1 else '')
1789 self.callback([msg])
1791 self.callback(['Retrying ...'])
1793 def try_command(self):
1794 """ Execute a cmd & poll for callback. Returns list of output.
1795 Raises CmdFailed -> return code for Popen isn't 0
1796 Raises CmdTimedOut -> command exceeded timeout without new output
1801 tfile = tempfile.NamedTemporaryFile(mode='w+b')
1802 preexec_fn = not G_IS_WIN and os.setsid or None
1803 self.proc = subprocess.Popen(self.cmd, stdout=tfile,
1804 stderr=subprocess.STDOUT,
1805 stdin=subprocess.PIPE, shell=True,
1806 preexec_fn=preexec_fn)
1807 thrd = thr.Thread(target=(lambda proc: proc.wait()), args=(self.proc,))
1810 thread_not_started = True
1811 while thread_not_started:
1814 thread_not_started = False
1815 except RuntimeError:
1820 raise KeyboardInterrupt
1822 if first_line or random.random() < G_LOG_PROB:
1824 line = '' if G_IS_WIN else nonblock_read(tfile.name)
1826 self.callback([line])
1828 time_diff = time.time() - os.path.getmtime(tfile.name)
1829 if time_diff > self.timeout:
1830 raise CmdTimedOut(['Timeout!'])
1835 result = [line.decode('utf-8', 'replace').rstrip() for line in tfile]
1837 if self.proc.returncode != 0:
1838 raise CmdFailed([''] + result)
1845 def terminate(self):
1846 """ Terminate process and cleanup. """
1849 os.kill(self.proc.pid, signal.SIGINT)
1851 os.killpg(self.proc.pid, signal.SIGTERM)
1854 class Plugin(object):
1855 def __init__(self, name, args, buf_q, lock):
1860 self.tag = args.get('tag', 0)
1864 if os.path.exists(self.args['dir']):
1869 thread_vim_command("let s:update.new['{0}'] = 1".format(self.name))
1870 except PlugError as exc:
1871 self.write(Action.ERROR, self.name, exc.msg)
1872 except KeyboardInterrupt:
1874 self.write(Action.ERROR, self.name, ['Interrupted!'])
1876 # Any exception except those above print stack trace
1877 msg = 'Trace:\n{0}'.format(traceback.format_exc().rstrip())
1878 self.write(Action.ERROR, self.name, msg.split('\n'))
1882 target = self.args['dir']
1883 if target[-1] == '\\':
1884 target = target[0:-1]
1889 shutil.rmtree(target)
1894 self.write(Action.INSTALL, self.name, ['Installing ...'])
1895 callback = functools.partial(self.write, Action.INSTALL, self.name)
1896 cmd = 'git clone {0} {1} {2} {3} 2>&1'.format(
1897 '' if self.tag else G_CLONE_OPT, G_PROGRESS, self.args['uri'],
1899 com = Command(cmd, None, G_TIMEOUT, callback, clean(target))
1900 result = com.execute(G_RETRIES)
1901 self.write(Action.DONE, self.name, result[-1:])
1904 cmd = 'git rev-parse --abbrev-ref HEAD 2>&1 && git config -f .git/config remote.origin.url'
1905 command = Command(cmd, self.args['dir'], G_TIMEOUT,)
1906 result = command.execute(G_RETRIES)
1910 actual_uri = self.repo_uri()
1911 expect_uri = self.args['uri']
1912 regex = re.compile(r'^(?:\w+://)?(?:[^@/]*@)?([^:/]*(?::[0-9]*)?)[:/](.*?)(?:\.git)?/?$')
1913 ma = regex.match(actual_uri)
1914 mb = regex.match(expect_uri)
1915 if ma is None or mb is None or ma.groups() != mb.groups():
1917 'Invalid URI: {0}'.format(actual_uri),
1918 'Expected {0}'.format(expect_uri),
1919 'PlugClean required.']
1920 raise InvalidURI(msg)
1923 self.write(Action.UPDATE, self.name, ['Updating ...'])
1924 callback = functools.partial(self.write, Action.UPDATE, self.name)
1925 fetch_opt = '--depth 99999999' if self.tag and os.path.isfile(os.path.join(self.args['dir'], '.git/shallow')) else ''
1926 cmd = 'git fetch {0} {1} 2>&1'.format(fetch_opt, G_PROGRESS)
1927 com = Command(cmd, self.args['dir'], G_TIMEOUT, callback)
1928 result = com.execute(G_RETRIES)
1929 self.write(Action.DONE, self.name, result[-1:])
1931 self.write(Action.DONE, self.name, ['Already installed'])
1933 def write(self, action, name, msg):
1934 self.buf_q.put((action, name, msg))
1936 class PlugThread(thr.Thread):
1937 def __init__(self, tname, args):
1938 super(PlugThread, self).__init__()
1943 thr.current_thread().name = self.tname
1944 buf_q, work_q, lock = self.args
1947 while not G_STOP.is_set():
1948 name, args = work_q.get_nowait()
1949 plug = Plugin(name, args, buf_q, lock)
1955 class RefreshThread(thr.Thread):
1956 def __init__(self, lock):
1957 super(RefreshThread, self).__init__()
1964 thread_vim_command('noautocmd normal! a')
1968 self.running = False
1971 def thread_vim_command(cmd):
1972 vim.session.threadsafe_call(lambda: vim.command(cmd))
1974 def thread_vim_command(cmd):
1978 return '"' + name.replace('"', '\"') + '"'
1980 def nonblock_read(fname):
1981 """ Read a file with nonblock flag. Return the last line. """
1982 fread = os.open(fname, os.O_RDONLY | os.O_NONBLOCK)
1983 buf = os.read(fread, 100000).decode('utf-8', 'replace')
1986 line = buf.rstrip('\r\n')
1987 left = max(line.rfind('\r'), line.rfind('\n'))
1995 thr.current_thread().name = 'main'
1996 nthreads = int(vim.eval('s:update.threads'))
1997 plugs = vim.eval('s:update.todo')
1998 mac_gui = vim.eval('s:mac_gui') == '1'
2001 buf = Buffer(lock, len(plugs), G_PULL)
2002 buf_q, work_q = queue.Queue(), queue.Queue()
2003 for work in plugs.items():
2006 start_cnt = thr.active_count()
2007 for num in range(nthreads):
2008 tname = 'PlugT-{0:02}'.format(num)
2009 thread = PlugThread(tname, (buf_q, work_q, lock))
2012 rthread = RefreshThread(lock)
2015 while not buf_q.empty() or thr.active_count() != start_cnt:
2017 action, name, msg = buf_q.get(True, 0.25)
2018 buf.write(action, name, ['OK'] if not msg else msg)
2022 except KeyboardInterrupt:
2033 function! s:update_ruby()
2036 SEP = ["\r", "\n", nil]
2040 char = readchar rescue return
2041 if SEP.include? char.chr
2050 end unless defined?(PlugStream)
2053 %["#{arg.gsub('"', '\"')}"]
2058 if /mswin|mingw|bccwin/ =~ RUBY_PLATFORM
2059 pids.each { |pid| Process.kill 'INT', pid.to_i rescue nil }
2061 unless `which pgrep 2> /dev/null`.empty?
2063 until children.empty?
2064 children = children.map { |pid|
2065 `pgrep -P #{pid}`.lines.map { |l| l.chomp }
2070 pids.each { |pid| Process.kill 'TERM', pid.to_i rescue nil }
2074 def compare_git_uri a, b
2075 regex = %r{^(?:\w+://)?(?:[^@/]*@)?([^:/]*(?::[0-9]*)?)[:/](.*?)(?:\.git)?/?$}
2076 regex.match(a).to_a.drop(1) == regex.match(b).to_a.drop(1)
2083 iswin = VIM::evaluate('s:is_win').to_i == 1
2084 pull = VIM::evaluate('s:update.pull').to_i == 1
2085 base = VIM::evaluate('g:plug_home')
2086 all = VIM::evaluate('s:update.todo')
2087 limit = VIM::evaluate('get(g:, "plug_timeout", 60)')
2088 tries = VIM::evaluate('get(g:, "plug_retries", 2)') + 1
2089 nthr = VIM::evaluate('s:update.threads').to_i
2090 maxy = VIM::evaluate('winheight(".")').to_i
2091 vim7 = VIM::evaluate('v:version').to_i <= 703 && RUBY_PLATFORM =~ /darwin/
2092 cd = iswin ? 'cd /d' : 'cd'
2093 tot = VIM::evaluate('len(s:update.todo)') || 0
2095 skip = 'Already installed'
2097 take1 = proc { mtx.synchronize { running && all.shift } }
2100 $curbuf[1] = "#{pull ? 'Updating' : 'Installing'} plugins (#{cnt}/#{tot})"
2101 $curbuf[2] = '[' + bar.ljust(tot) + ']'
2102 VIM::command('normal! 2G')
2103 VIM::command('redraw')
2105 where = proc { |name| (1..($curbuf.length)).find { |l| $curbuf[l] =~ /^[-+x*] #{name}:/ } }
2106 log = proc { |name, result, type|
2108 ing = ![true, false].include?(type)
2109 bar += type ? '=' : 'x' unless ing
2111 when :install then '+' when :update then '*'
2112 when true, nil then '-' else
2113 VIM::command("call add(s:update.errors, '#{name}')")
2117 if type || type.nil?
2118 ["#{b} #{name}: #{result.lines.to_a.last || 'OK'}"]
2119 elsif result =~ /^Interrupted|^Timeout/
2120 ["#{b} #{name}: #{result}"]
2122 ["#{b} #{name}"] + result.lines.map { |l| " " << l }
2124 if lnum = where.call(name)
2126 lnum = 4 if ing && lnum > maxy
2128 result.each_with_index do |line, offset|
2129 $curbuf.append((lnum || 4) - 1 + offset, line.gsub(/\e\[./, '').chomp)
2134 bt = proc { |cmd, name, type, cleanup|
2142 Timeout::timeout(timeout) do
2143 tmp = VIM::evaluate('tempname()')
2144 system("(#{cmd}) > #{tmp}")
2145 data = File.read(tmp).chomp
2146 File.unlink tmp rescue nil
2149 fd = IO.popen(cmd).extend(PlugStream)
2151 log_prob = 1.0 / nthr
2152 while line = Timeout::timeout(timeout) { fd.get_line }
2154 log.call name, line.chomp, type if name && (first_line || rand < log_prob)
2159 [$? == 0, data.chomp]
2160 rescue Timeout::Error, Interrupt => e
2161 if fd && !fd.closed?
2165 cleanup.call if cleanup
2166 if e.is_a?(Timeout::Error) && tried < tries
2167 3.downto(1) do |countdown|
2168 s = countdown > 1 ? 's' : ''
2169 log.call name, "Timeout. Will retry in #{countdown} second#{s} ...", type
2172 log.call name, 'Retrying ...', type
2175 [false, e.is_a?(Interrupt) ? "Interrupted!" : "Timeout!"]
2178 main = Thread.current
2180 watcher = Thread.new {
2182 while VIM::evaluate('getchar(1)')
2186 require 'io/console' # >= Ruby 1.9
2187 nil until IO.console.getch == 3.chr
2191 threads.each { |t| t.raise Interrupt } unless vim7
2193 threads.each { |t| t.join rescue nil }
2196 refresh = Thread.new {
2199 break unless running
2200 VIM::command('noautocmd normal! a')
2204 } if VIM::evaluate('s:mac_gui') == 1
2206 clone_opt = VIM::evaluate('s:clone_opt').join(' ')
2207 progress = VIM::evaluate('s:progress_opt(1)')
2210 threads << Thread.new {
2211 while pair = take1.call
2213 dir, uri, tag = pair.last.values_at *%w[dir uri tag]
2214 exists = File.directory? dir
2217 chdir = "#{cd} #{iswin ? dir : esc(dir)}"
2218 ret, data = bt.call "#{chdir} && git rev-parse --abbrev-ref HEAD 2>&1 && git config -f .git/config remote.origin.url", nil, nil, nil
2219 current_uri = data.lines.to_a.last
2221 if data =~ /^Interrupted|^Timeout/
2224 [false, [data.chomp, "PlugClean required."].join($/)]
2226 elsif !compare_git_uri(current_uri, uri)
2227 [false, ["Invalid URI: #{current_uri}",
2229 "PlugClean required."].join($/)]
2232 log.call name, 'Updating ...', :update
2233 fetch_opt = (tag && File.exist?(File.join(dir, '.git/shallow'))) ? '--depth 99999999' : ''
2234 bt.call "#{chdir} && git fetch #{fetch_opt} #{progress} 2>&1", name, :update, nil
2240 d = esc dir.sub(%r{[\\/]+$}, '')
2241 log.call name, 'Installing ...', :install
2242 bt.call "git clone #{clone_opt unless tag} #{progress} #{uri} #{d} 2>&1", name, :install, proc {
2246 mtx.synchronize { VIM::command("let s:update.new['#{name}'] = 1") } if !exists && ok
2247 log.call name, result, ok
2252 threads.each { |t| t.join rescue nil }
2254 refresh.kill if refresh
2259 function! s:shellesc_cmd(arg, script)
2260 let escaped = substitute('"'.a:arg.'"', '[&|<>()@^!"]', '^&', 'g')
2261 return substitute(escaped, '%', (a:script ? '%' : '^') . '&', 'g')
2264 function! s:shellesc_ps1(arg)
2265 return "'".substitute(escape(a:arg, '\"'), "'", "''", 'g')."'"
2268 function! s:shellesc_sh(arg)
2269 return "'".substitute(a:arg, "'", "'\\\\''", 'g')."'"
2272 " Escape the shell argument based on the shell.
2273 " Vim and Neovim's shellescape() are insufficient.
2274 " 1. shellslash determines whether to use single/double quotes.
2275 " Double-quote escaping is fragile for cmd.exe.
2276 " 2. It does not work for powershell.
2277 " 3. It does not work for *sh shells if the command is executed
2278 " via cmd.exe (ie. cmd.exe /c sh -c command command_args)
2279 " 4. It does not support batchfile syntax.
2281 " Accepts an optional dictionary with the following keys:
2282 " - shell: same as Vim/Neovim 'shell' option.
2283 " If unset, fallback to 'cmd.exe' on Windows or 'sh'.
2284 " - script: If truthy and shell is cmd.exe, escape for batchfile syntax.
2285 function! plug#shellescape(arg, ...)
2286 if a:arg =~# '^[A-Za-z0-9_/:.-]\+$'
2289 let opts = a:0 > 0 && type(a:1) == s:TYPE.dict ? a:1 : {}
2290 let shell = get(opts, 'shell', s:is_win ? 'cmd.exe' : 'sh')
2291 let script = get(opts, 'script', 1)
2292 if shell =~# 'cmd\(\.exe\)\?$'
2293 return s:shellesc_cmd(a:arg, script)
2294 elseif s:is_powershell(shell)
2295 return s:shellesc_ps1(a:arg)
2297 return s:shellesc_sh(a:arg)
2300 function! s:glob_dir(path)
2301 return map(filter(s:glob(a:path, '**'), 'isdirectory(v:val)'), 's:dirpath(v:val)')
2304 function! s:progress_bar(line, bar, total)
2305 call setline(a:line, '[' . s:lpad(a:bar, a:total) . ']')
2308 function! s:compare_git_uri(a, b)
2309 " See `git help clone'
2310 " https:// [user@] github.com[:port] / junegunn/vim-plug [.git]
2311 " [git@] github.com[:port] : junegunn/vim-plug [.git]
2312 " file:// / junegunn/vim-plug [/]
2313 " / junegunn/vim-plug [/]
2314 let pat = '^\%(\w\+://\)\='.'\%([^@/]*@\)\='.'\([^:/]*\%(:[0-9]*\)\=\)'.'[:/]'.'\(.\{-}\)'.'\%(\.git\)\=/\?$'
2315 let ma = matchlist(a:a, pat)
2316 let mb = matchlist(a:b, pat)
2317 return ma[1:2] ==# mb[1:2]
2320 function! s:format_message(bullet, name, message)
2322 return [printf('%s %s: %s', a:bullet, a:name, s:lastline(a:message))]
2324 let lines = map(s:lines(a:message), '" ".v:val')
2325 return extend([printf('x %s:', a:name)], lines)
2329 function! s:with_cd(cmd, dir, ...)
2330 let script = a:0 > 0 ? a:1 : 1
2331 let pwsh = s:is_powershell(&shell)
2332 let cd = s:is_win && !pwsh ? 'cd /d' : 'cd'
2333 let sep = pwsh ? ';' : '&&'
2334 return printf('%s %s %s %s', cd, plug#shellescape(a:dir, {'script': script, 'shell': &shell}), sep, a:cmd)
2337 function! s:system(cmd, ...)
2340 let [sh, shellcmdflag, shrd] = s:chsh(1)
2341 if type(a:cmd) == s:TYPE.list
2342 " Neovim's system() supports list argument to bypass the shell
2343 " but it cannot set the working directory for the command.
2344 " Assume that the command does not rely on the shell.
2345 if has('nvim') && a:0 == 0
2346 return system(a:cmd)
2348 let cmd = join(map(copy(a:cmd), 'plug#shellescape(v:val, {"shell": &shell, "script": 0})'))
2349 if s:is_powershell(&shell)
2350 let cmd = '& ' . cmd
2356 let cmd = s:with_cd(cmd, a:1, type(a:cmd) != s:TYPE.list)
2358 if s:is_win && type(a:cmd) != s:TYPE.list
2359 let [batchfile, cmd] = s:batchfile(cmd)
2363 let [&shell, &shellcmdflag, &shellredir] = [sh, shellcmdflag, shrd]
2364 if s:is_win && filereadable(batchfile)
2365 call delete(batchfile)
2370 function! s:system_chomp(...)
2371 let ret = call('s:system', a:000)
2372 return v:shell_error ? '' : substitute(ret, '\n$', '', '')
2375 function! s:git_validate(spec, check_branch)
2377 if isdirectory(a:spec.dir)
2378 let result = [s:git_local_branch(a:spec.dir), s:git_origin_url(a:spec.dir)]
2379 let remote = result[-1]
2381 let err = join([remote, 'PlugClean required.'], "\n")
2382 elseif !s:compare_git_uri(remote, a:spec.uri)
2383 let err = join(['Invalid URI: '.remote,
2384 \ 'Expected: '.a:spec.uri,
2385 \ 'PlugClean required.'], "\n")
2386 elseif a:check_branch && has_key(a:spec, 'commit')
2387 let sha = s:git_revision(a:spec.dir)
2389 let err = join(add(result, 'PlugClean required.'), "\n")
2390 elseif !s:hash_match(sha, a:spec.commit)
2391 let err = join([printf('Invalid HEAD (expected: %s, actual: %s)',
2392 \ a:spec.commit[:6], sha[:6]),
2393 \ 'PlugUpdate required.'], "\n")
2395 elseif a:check_branch
2396 let current_branch = result[0]
2398 let origin_branch = s:git_origin_branch(a:spec)
2399 if has_key(a:spec, 'tag')
2400 let tag = s:system_chomp('git describe --exact-match --tags HEAD 2>&1', a:spec.dir)
2401 if a:spec.tag !=# tag && a:spec.tag !~ '\*'
2402 let err = printf('Invalid tag: %s (expected: %s). Try PlugUpdate.',
2403 \ (empty(tag) ? 'N/A' : tag), a:spec.tag)
2406 elseif origin_branch !=# current_branch
2407 let err = printf('Invalid branch: %s (expected: %s). Try PlugUpdate.',
2408 \ current_branch, origin_branch)
2411 let ahead_behind = split(s:lastline(s:system([
2412 \ 'git', 'rev-list', '--count', '--left-right',
2413 \ printf('HEAD...origin/%s', origin_branch)
2414 \ ], a:spec.dir)), '\t')
2415 if v:shell_error || len(ahead_behind) != 2
2416 let err = "Failed to compare with the origin. The default branch might have changed.\nPlugClean required."
2418 let [ahead, behind] = ahead_behind
2420 " Only mention PlugClean if diverged, otherwise it's likely to be
2421 " pushable (and probably not that messed up).
2423 \ "Diverged from origin/%s (%d commit(s) ahead and %d commit(s) behind!\n"
2424 \ .'Backup local changes and run PlugClean and PlugUpdate to reinstall it.', origin_branch, ahead, behind)
2426 let err = printf("Ahead of origin/%s by %d commit(s).\n"
2427 \ .'Cannot update until local changes are pushed.',
2428 \ origin_branch, ahead)
2434 let err = 'Not found'
2436 return [err, err =~# 'PlugClean']
2439 function! s:rm_rf(dir)
2440 if isdirectory(a:dir)
2441 return s:system(s:is_win
2442 \ ? 'rmdir /S /Q '.plug#shellescape(a:dir)
2443 \ : ['rm', '-rf', a:dir])
2447 function! s:clean(force)
2449 call append(0, 'Searching for invalid plugins in '.g:plug_home)
2452 " List of valid directories
2455 let [cnt, total] = [0, len(g:plugs)]
2456 for [name, spec] in items(g:plugs)
2457 if !s:is_managed(name) || get(spec, 'frozen', 0)
2458 call add(dirs, spec.dir)
2460 let [err, clean] = s:git_validate(spec, 1)
2462 let errs[spec.dir] = s:lines(err)[0]
2464 call add(dirs, spec.dir)
2468 call s:progress_bar(2, repeat('=', cnt), total)
2475 let allowed[s:dirpath(s:plug_fnamemodify(dir, ':h:h'))] = 1
2476 let allowed[dir] = 1
2477 for child in s:glob_dir(dir)
2478 let allowed[child] = 1
2483 let found = sort(s:glob_dir(g:plug_home))
2485 let f = remove(found, 0)
2486 if !has_key(allowed, f) && isdirectory(f)
2488 call append(line('$'), '- ' . f)
2490 call append(line('$'), ' ' . errs[f])
2492 let found = filter(found, 'stridx(v:val, f) != 0')
2499 call append(line('$'), 'Already clean.')
2501 let s:clean_count = 0
2502 call append(3, ['Directories to delete:', ''])
2504 if a:force || s:ask_no_interrupt('Delete all directories?')
2505 call s:delete([6, line('$')], 1)
2507 call setline(4, 'Cancelled.')
2508 nnoremap <silent> <buffer> d :set opfunc=<sid>delete_op<cr>g@
2509 nmap <silent> <buffer> dd d_
2510 xnoremap <silent> <buffer> d :<c-u>call <sid>delete_op(visualmode(), 1)<cr>
2511 echo 'Delete the lines (d{motion}) to delete the corresponding directories'
2515 setlocal nomodifiable
2518 function! s:delete_op(type, ...)
2519 call s:delete(a:0 ? [line("'<"), line("'>")] : [line("'["), line("']")], 0)
2522 function! s:delete(range, force)
2523 let [l1, l2] = a:range
2527 let line = getline(l1)
2528 if line =~ '^- ' && isdirectory(line[2:])
2531 let answer = force ? 1 : s:ask('Delete '.line[2:].'?', 1)
2532 let force = force || answer > 1
2534 let err = s:rm_rf(line[2:])
2537 call setline(l1, '~'.line[1:])
2538 let s:clean_count += 1
2541 call append(l1 - 1, s:format_message('x', line[1:], err))
2542 let l2 += len(s:lines(err))
2545 let msg = printf('Removed %d directories.', s:clean_count)
2547 let msg .= printf(' Failed to remove %d directories.', err_count)
2549 call setline(4, msg)
2550 setlocal nomodifiable
2557 function! s:upgrade()
2558 echo 'Downloading the latest version of vim-plug'
2560 let tmp = s:plug_tempname()
2561 let new = tmp . '/plug.vim'
2564 let out = s:system(['git', 'clone', '--depth', '1', s:plug_src, tmp])
2566 return s:err('Error upgrading vim-plug: '. out)
2569 if readfile(s:me) ==# readfile(new)
2570 echo 'vim-plug is already up-to-date'
2573 call rename(s:me, s:me . '.old')
2574 call rename(new, s:me)
2576 echo 'vim-plug has been upgraded'
2580 silent! call s:rm_rf(tmp)
2584 function! s:upgrade_specs()
2585 for spec in values(g:plugs)
2586 let spec.frozen = get(spec, 'frozen', 0)
2590 function! s:status()
2592 call append(0, 'Checking plugins')
2597 let [cnt, total] = [0, len(g:plugs)]
2598 for [name, spec] in items(g:plugs)
2599 let is_dir = isdirectory(spec.dir)
2600 if has_key(spec, 'uri')
2602 let [err, _] = s:git_validate(spec, 1)
2603 let [valid, msg] = [empty(err), empty(err) ? 'OK' : err]
2605 let [valid, msg] = [0, 'Not found. Try PlugInstall.']
2609 let [valid, msg] = [1, 'OK']
2611 let [valid, msg] = [0, 'Not found.']
2616 " `s:loaded` entry can be missing if PlugUpgraded
2617 if is_dir && get(s:loaded, name, -1) == 0
2619 let msg .= ' (not loaded)'
2621 call s:progress_bar(2, repeat('=', cnt), total)
2622 call append(3, s:format_message(valid ? '-' : 'x', name, msg))
2626 call setline(1, 'Finished. '.ecnt.' error(s).')
2628 setlocal nomodifiable
2630 echo "Press 'L' on each line to load plugin, or 'U' to update"
2631 nnoremap <silent> <buffer> L :call <SID>status_load(line('.'))<cr>
2632 xnoremap <silent> <buffer> L :call <SID>status_load(line('.'))<cr>
2636 function! s:extract_name(str, prefix, suffix)
2637 return matchstr(a:str, '^'.a:prefix.' \zs[^:]\+\ze:.*'.a:suffix.'$')
2640 function! s:status_load(lnum)
2641 let line = getline(a:lnum)
2642 let name = s:extract_name(line, '-', '(not loaded)')
2644 call plug#load(name)
2646 call setline(a:lnum, substitute(line, ' (not loaded)$', '', ''))
2647 setlocal nomodifiable
2651 function! s:status_update() range
2652 let lines = getline(a:firstline, a:lastline)
2653 let names = filter(map(lines, 's:extract_name(v:val, "[x-]", "")'), '!empty(v:val)')
2656 execute 'PlugUpdate' join(names)
2660 function! s:is_preview_window_open()
2668 function! s:find_name(lnum)
2669 for lnum in reverse(range(1, a:lnum))
2670 let line = getline(lnum)
2674 let name = s:extract_name(line, '-', '')
2682 function! s:preview_commit()
2683 if b:plug_preview < 0
2684 let b:plug_preview = !s:is_preview_window_open()
2687 let sha = matchstr(getline('.'), '^ \X*\zs[0-9a-f]\{7,9}')
2689 let name = matchstr(getline('.'), '^- \zs[^:]*\ze:$')
2693 let title = 'HEAD@{1}..'
2694 let command = 'git diff --no-color HEAD@{1}'
2697 let command = 'git show --no-color --pretty=medium '.sha
2698 let name = s:find_name(line('.'))
2701 if empty(name) || !has_key(g:plugs, name) || !isdirectory(g:plugs[name].dir)
2705 if !s:is_preview_window_open()
2706 execute get(g:, 'plug_pwindow', 'vertical rightbelow new')
2709 execute 'pedit' title
2712 setlocal previewwindow filetype=git buftype=nofile bufhidden=wipe nobuflisted modifiable
2715 let [sh, shellcmdflag, shrd] = s:chsh(1)
2716 let cmd = 'cd '.plug#shellescape(g:plugs[name].dir).' && '.command
2718 let [batchfile, cmd] = s:batchfile(cmd)
2720 execute 'silent %!' cmd
2722 let [&shell, &shellcmdflag, &shellredir] = [sh, shellcmdflag, shrd]
2723 if s:is_win && filereadable(batchfile)
2724 call delete(batchfile)
2727 setlocal nomodifiable
2728 nnoremap <silent> <buffer> q :q<cr>
2732 function! s:section(flags)
2733 call search('\(^[x-] \)\@<=[^:]\+:', a:flags)
2736 function! s:format_git_log(line)
2738 let tokens = split(a:line, nr2char(1))
2740 return indent.substitute(a:line, '\s*$', '', '')
2742 let [graph, sha, refs, subject, date] = tokens
2743 let tag = matchstr(refs, 'tag: [^,)]\+')
2744 let tag = empty(tag) ? ' ' : ' ('.tag.') '
2745 return printf('%s%s%s%s%s (%s)', indent, graph, sha, tag, subject, date)
2748 function! s:append_ul(lnum, text)
2749 call append(a:lnum, ['', a:text, repeat('-', len(a:text))])
2754 call append(0, ['Collecting changes ...', ''])
2757 let total = filter(copy(g:plugs), 's:is_managed(v:key) && isdirectory(v:val.dir)')
2758 call s:progress_bar(2, bar, len(total))
2759 for origin in [1, 0]
2760 let plugs = reverse(sort(items(filter(copy(total), (origin ? '' : '!').'(has_key(v:val, "commit") || has_key(v:val, "tag"))'))))
2764 call s:append_ul(2, origin ? 'Pending updates:' : 'Last update:')
2766 let branch = s:git_origin_branch(v)
2768 let range = origin ? '..origin/'.branch : 'HEAD@{1}..'
2769 let cmd = ['git', 'log', '--graph', '--color=never']
2770 if s:git_version_requirement(2, 10, 0)
2771 call add(cmd, '--no-show-signature')
2773 call extend(cmd, ['--pretty=format:%x01%h%x01%d%x01%s%x01%cr', range])
2774 if has_key(v, 'rtp')
2775 call extend(cmd, ['--', v.rtp])
2777 let diff = s:system_chomp(cmd, v.dir)
2779 let ref = has_key(v, 'tag') ? (' (tag: '.v.tag.')') : has_key(v, 'commit') ? (' '.v.commit) : ''
2780 call append(5, extend(['', '- '.k.':'.ref], map(s:lines(diff), 's:format_git_log(v:val)')))
2781 let cnts[origin] += 1
2785 call s:progress_bar(2, bar, len(total))
2790 call append(5, ['', 'N/A'])
2793 call setline(1, printf('%d plugin(s) updated.', cnts[0])
2794 \ . (cnts[1] ? printf(' %d plugin(s) have pending updates.', cnts[1]) : ''))
2796 if cnts[0] || cnts[1]
2797 nnoremap <silent> <buffer> <plug>(plug-preview) :silent! call <SID>preview_commit()<cr>
2798 if empty(maparg("\<cr>", 'n'))
2799 nmap <buffer> <cr> <plug>(plug-preview)
2801 if empty(maparg('o', 'n'))
2802 nmap <buffer> o <plug>(plug-preview)
2806 nnoremap <silent> <buffer> X :call <SID>revert()<cr>
2807 echo "Press 'X' on each block to revert the update"
2810 setlocal nomodifiable
2813 function! s:revert()
2814 if search('^Pending updates', 'bnW')
2818 let name = s:find_name(line('.'))
2819 if empty(name) || !has_key(g:plugs, name) ||
2820 \ input(printf('Revert the update of %s? (y/N) ', name)) !~? '^y'
2824 call s:system('git reset --hard HEAD@{1} && git checkout '.plug#shellescape(g:plugs[name].branch).' --', g:plugs[name].dir)
2827 setlocal nomodifiable
2831 function! s:snapshot(force, ...) abort
2834 call append(0, ['" Generated by vim-plug',
2835 \ '" '.strftime("%c"),
2836 \ '" :source this file in vim to restore the snapshot',
2837 \ '" or execute: vim -S snapshot.vim',
2838 \ '', '', 'PlugUpdate!'])
2840 let anchor = line('$') - 3
2841 let names = sort(keys(filter(copy(g:plugs),
2842 \'has_key(v:val, "uri") && isdirectory(v:val.dir)')))
2843 for name in reverse(names)
2844 let sha = has_key(g:plugs[name], 'commit') ? g:plugs[name].commit : s:git_revision(g:plugs[name].dir)
2846 call append(anchor, printf("silent! let g:plugs['%s'].commit = '%s'", name, sha))
2852 let fn = s:plug_expand(a:1)
2853 if filereadable(fn) && !(a:force || s:ask(a:1.' already exists. Overwrite?'))
2856 call writefile(getline(1, '$'), fn)
2857 echo 'Saved as '.a:1
2858 silent execute 'e' s:esc(fn)
2863 function! s:split_rtp()
2864 return split(&rtp, '\\\@<!,')
2867 let s:first_rtp = s:escrtp(get(s:split_rtp(), 0, ''))
2868 let s:last_rtp = s:escrtp(get(s:split_rtp(), -1, ''))
2870 if exists('g:plugs')
2871 let g:plugs_order = get(g:, 'plugs_order', keys(g:plugs))
2872 call s:upgrade_specs()
2873 call s:define_commands()
2876 let &cpo = s:cpo_save